/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, volume: 1, musicVolume: 1, sfxVolume: 1 }); /**** * Classes ****/ var Ball = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.width = ballGraphics.width; self.height = ballGraphics.height; self.speedY = 3; self.speedX = 0; self.gravity = 0.1; self.maxSpeed = 10; self.reset = function () { self.x = 2048 / 2; self.y = 500; self.speedY = 3; self.speedX = Math.random() * 4 - 2; }; self.update = function () { // Apply gravity self.speedY += self.gravity; // Cap max speed if (self.speedY > self.maxSpeed) { self.speedY = self.maxSpeed; } // Update position self.x += self.speedX; self.y += self.speedY; // Bounce off walls if (self.x < self.width / 2) { self.x = self.width / 2; self.speedX = -self.speedX; // Play wall hit sound var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('bounce').play({ volume: masterVolume * sfxVolume * 0.7 }); // Emit wall hit particles if (self.onWallHit) { self.onWallHit(self.x, self.y, 'left'); } } else if (self.x > 2048 - self.width / 2) { self.x = 2048 - self.width / 2; self.speedX = -self.speedX; // Play wall hit sound var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('bounce').play({ volume: masterVolume * sfxVolume * 0.7 }); // Emit wall hit particles if (self.onWallHit) { self.onWallHit(self.x, self.y, 'right'); } } }; return self; }); var Confetti = Container.expand(function () { var self = Container.call(this); self.particles = []; self.colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFFFFF]; self.emit = function (x, y, count) { for (var i = 0; i < count; i++) { var particle = new Container(); var size = Math.random() * 20 + 10; var color = self.colors[Math.floor(Math.random() * self.colors.length)]; var particleGraphic = particle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: size / 40, scaleY: size / 40 }); particleGraphic.tint = color; particle.x = x; particle.y = y; particle.speedX = Math.random() * 20 - 10; particle.speedY = -Math.random() * 15 - 5; particle.rotationSpeed = (Math.random() - 0.5) * 0.2; particle.life = 60 + Math.random() * 60; particle.maxLife = particle.life; self.addChild(particle); self.particles.push(particle); } }; self.update = function () { for (var i = self.particles.length - 1; i >= 0; i--) { var particle = self.particles[i]; particle.x += particle.speedX; particle.y += particle.speedY; particle.speedY += 0.2; // Gravity particle.rotation += particle.rotationSpeed; particle.life--; particle.alpha = particle.life / particle.maxLife; if (particle.life <= 0) { self.removeChild(particle); self.particles.splice(i, 1); } } }; return self; }); var ConfirmDialog = Container.expand(function () { var self = Container.call(this); // Create semi-transparent background var bg = new Container(); var bgAsset = bg.attachAsset('lava', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 2 }); bgAsset.alpha = 0.8; bgAsset.tint = 0x000000; self.addChild(bg); // Create message text var messageText = new Text2('Are you sure you want\nto reset your high score?', { size: 80, fill: 0xFFFFFF }); messageText.anchor.set(0.5, 0.5); messageText.y = -100; self.addChild(messageText); // Create yes button var yesButton = new Container(); var yesText = new Text2('YES', { size: 80, fill: 0xFF0000 }); yesText.anchor.set(0.5, 0.5); yesButton.addChild(yesText); yesButton.x = -150; yesButton.y = 100; yesButton.interactive = true; self.addChild(yesButton); // Create no button var noButton = new Container(); var noText = new Text2('NO', { size: 80, fill: 0xFFFFFF }); noText.anchor.set(0.5, 0.5); noButton.addChild(noText); noButton.x = 150; noButton.y = 100; noButton.interactive = true; self.addChild(noButton); // Button press handlers yesButton.down = function () { tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; yesButton.up = function () { var btn = this; tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { if (self.onConfirm) { self.onConfirm(); } } }); }; noButton.down = function () { tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; noButton.up = function () { var btn = this; tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { if (self.onCancel) { self.onCancel(); } } }); }; return self; }); var Lava = Container.expand(function () { var self = Container.call(this); var lavaGraphics = self.attachAsset('lava', { anchorX: 0.5, anchorY: 0.5 }); // Set initial wave properties self.waveOffset = Math.random() * Math.PI * 2; // Random starting position for wave self.baseY = 0; // Will store the initial y position self.rising = false; // Track if lava is rising or falling self.moveRange = 300; // How far the lava will move up and down self.moveSpeed = 5000; // Duration of rise/fall movement in ms // The paddle reference will be set when the lava is added to the game // Start the rise and fall cycle self.startRiseFall = function () { // Make the lava rise a little bit from its base position tween(self, { y: self.baseY - 100 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { y: self.baseY }, { duration: 2000, easing: tween.easeInOut, onFinish: self.startRiseFall }); } }); }; // Create a pulsing effect for the lava self.pulse = function () { tween(lavaGraphics, { alpha: 0.7 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(lavaGraphics, { alpha: 1 }, { duration: 800, easing: tween.easeInOut, onFinish: self.pulse }); } }); }; // Create animated wave effect self.update = function () { // Create a wave effect based on time var waveTime = LK.ticks / 30; // Adjust speed of wave var waveScale = Math.sin(waveTime) * 0.05 + 1; // Scale between 0.95 and 1.05 // Apply different wave scales to left and right sides var leftWaveScale = Math.sin(waveTime + 1) * 0.05 + 1; var rightWaveScale = Math.sin(waveTime + 2) * 0.05 + 1; // Apply the scale distortion to create wave effect lavaGraphics.scaleX = waveScale; // Small vertical oscillation to add to the tween movement var oscillation = Math.sin(waveTime * 0.5) * 0.5; // The y position is controlled by the tween, we just add small oscillation // (Not setting self.y directly here since tweens are handling the major movement) }; self.pulse(); return self; }); var MouseParticleSystem = Container.expand(function () { var self = Container.call(this); self.particles = []; self.maxParticles = 100; self.createParticle = function (x, y) { var particle = new Container(); var particleGraphic = particle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); // Use bright colors for better visibility var colors = [0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFF5500, 0x88FF00]; particleGraphic.tint = colors[Math.floor(Math.random() * colors.length)]; particle.x = x; particle.y = y; particle.alpha = 1; particle.speedX = Math.random() * 12 - 6; particle.speedY = Math.random() * 12 - 6; particle.life = 30 + Math.random() * 30; particle.maxLife = particle.life; self.addChild(particle); self.particles.push(particle); return particle; }; self.update = function () { for (var i = self.particles.length - 1; i >= 0; i--) { var particle = self.particles[i]; particle.x += particle.speedX; particle.y += particle.speedY; particle.speedX *= 0.96; particle.speedY *= 0.96; particle.life--; particle.alpha = particle.life / particle.maxLife; // Smoother size transition with tween-like effect particle.scaleX = particle.life / particle.maxLife * 0.4 + 0.1; particle.scaleY = particle.life / particle.maxLife * 0.4 + 0.1; // Add rotation for more visual interest particle.rotation += 0.05; if (particle.life <= 0) { self.removeChild(particle); self.particles.splice(i, 1); } } }; self.emitAt = function (x, y, count) { // Create more particles for better visual effect for (var i = 0; i < count; i++) { // Slightly offset each particle for a more natural burst effect var offsetX = x + (Math.random() * 20 - 10); var offsetY = y + (Math.random() * 20 - 10); self.createParticle(offsetX, offsetY); } }; return self; }); var Paddle = Container.expand(function () { var self = Container.call(this); // Create left rounded edge var paddleLeftGraphics = self.attachAsset('paddleLeft', { anchorX: 1.0, anchorY: 0.5, x: -170 }); // Create middle section var paddleMiddleGraphics = self.attachAsset('paddleMiddle', { anchorX: 0.5, anchorY: 0.5 }); // Create right rounded edge var paddleRightGraphics = self.attachAsset('paddleRight', { anchorX: 0.0, anchorY: 0.5, x: 170 }); // Adjust positions to eliminate gaps // The middle section's width is 340, and each end cap is 30 // We need to ensure they connect perfectly paddleLeftGraphics.x = -170 + 15; // Move right by half of end cap width paddleRightGraphics.x = 170 - 15; // Move left by half of end cap width // Set the overall width based on the combined elements self.width = 400; // total width (30 + 340 + 30) self.height = 30; return self; }); var ParticleSystem = Container.expand(function () { var self = Container.call(this); self.particles = []; self.maxParticles = 20; self.createParticle = function (x, y, color) { var particle = new Container(); var particleGraphic = particle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); particleGraphic.tint = color; particle.x = x; particle.y = y; particle.alpha = 1; particle.speedX = Math.random() * 10 - 5; particle.speedY = -Math.random() * 5 - 2; particle.life = 30; self.addChild(particle); self.particles.push(particle); return particle; }; self.update = function () { for (var i = self.particles.length - 1; i >= 0; i--) { var particle = self.particles[i]; particle.x += particle.speedX; particle.y += particle.speedY; particle.speedY += 0.1; particle.life--; particle.alpha = particle.life / 30; if (particle.life <= 0) { self.removeChild(particle); self.particles.splice(i, 1); } } }; self.emitAt = function (x, y, color, count) { for (var i = 0; i < count; i++) { self.createParticle(x, y, color); } }; return self; }); var SettingsPanel = Container.expand(function () { var self = Container.call(this); // Create semi-transparent background var bg = new Container(); var bgAsset = bg.attachAsset('lava', { anchorX: 0.5, anchorY: 0.5, scaleX: 5, scaleY: 5 }); bgAsset.alpha = 0.7; bgAsset.tint = 0x000000; self.addChild(bg); // Create panel title var titleText = new Text2('SETTINGS', { size: 120, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.y = -500; self.addChild(titleText); // Create close button var closeButton = new Container(); var closeText = new Text2('X', { size: 80, fill: 0xFFFFFF }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = 800; closeButton.y = -500; closeButton.interactive = true; self.addChild(closeButton); // Paddle color selection section var paddleLabel = new Text2('PADDLE COLOR', { size: 80, fill: 0xFFFFFF }); paddleLabel.anchor.set(0.5, 0.5); paddleLabel.y = -300; self.addChild(paddleLabel); // Color options for paddle var paddleColors = [0xffbd00, 0xff0000, 0x00ff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff]; var paddleColorButtons = []; for (var i = 0; i < paddleColors.length; i++) { var colorBtn = new Container(); var colorAsset = colorBtn.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); colorAsset.tint = paddleColors[i]; colorBtn.x = (i - 3) * 150; colorBtn.y = -200; colorBtn.interactive = true; colorBtn.colorValue = paddleColors[i]; colorBtn.colorType = 'paddle'; paddleColorButtons.push(colorBtn); self.addChild(colorBtn); } // Sound control section var soundLabel = new Text2('SOUND SETTINGS', { size: 80, fill: 0xFFFFFF }); soundLabel.anchor.set(0.5, 0.5); soundLabel.y = 300; self.addChild(soundLabel); // Master volume controls var masterVolumeLabel = new Text2('Master Volume', { size: 50, fill: 0xFFFFFF }); masterVolumeLabel.anchor.set(0.5, 0.5); masterVolumeLabel.y = 360; self.addChild(masterVolumeLabel); // Add master slider background var masterSliderBg = new Container(); var masterSliderBgAsset = masterSliderBg.attachAsset('paddleMiddle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.5 }); masterSliderBgAsset.tint = 0x555555; masterSliderBg.x = 0; masterSliderBg.y = 400; self.addChild(masterSliderBg); // Add master slider handle var masterSliderHandle = new Container(); var masterSliderHandleAsset = masterSliderHandle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); masterSliderHandleAsset.tint = 0xFFFFFF; masterSliderHandle.interactive = true; masterSliderHandle.y = 400; self.addChild(masterSliderHandle); // Music volume controls var musicVolumeLabel = new Text2('Music Volume', { size: 50, fill: 0xFFFFFF }); musicVolumeLabel.anchor.set(0.5, 0.5); musicVolumeLabel.y = 460; self.addChild(musicVolumeLabel); // Add music slider background var musicSliderBg = new Container(); var musicSliderBgAsset = musicSliderBg.attachAsset('paddleMiddle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.5 }); musicSliderBgAsset.tint = 0x555555; musicSliderBg.x = 0; musicSliderBg.y = 500; self.addChild(musicSliderBg); // Add music slider handle var musicSliderHandle = new Container(); var musicSliderHandleAsset = musicSliderHandle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); musicSliderHandleAsset.tint = 0x00FFFF; musicSliderHandle.interactive = true; musicSliderHandle.y = 500; self.addChild(musicSliderHandle); // SFX volume controls var sfxVolumeLabel = new Text2('SFX Volume', { size: 50, fill: 0xFFFFFF }); sfxVolumeLabel.anchor.set(0.5, 0.5); sfxVolumeLabel.y = 560; self.addChild(sfxVolumeLabel); // Add SFX slider background var sfxSliderBg = new Container(); var sfxSliderBgAsset = sfxSliderBg.attachAsset('paddleMiddle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.5 }); sfxSliderBgAsset.tint = 0x555555; sfxSliderBg.x = 0; sfxSliderBg.y = 600; self.addChild(sfxSliderBg); // Add SFX slider handle var sfxSliderHandle = new Container(); var sfxSliderHandleAsset = sfxSliderHandle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); sfxSliderHandleAsset.tint = 0xFF5500; sfxSliderHandle.interactive = true; sfxSliderHandle.y = 600; self.addChild(sfxSliderHandle); // Set initial slider positions based on stored volumes var volume = storage.volume !== undefined ? storage.volume : 1.0; var musicVolume = storage.musicVolume !== undefined ? storage.musicVolume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; masterSliderHandle.x = (volume - 0.5) * 300; // Map volume 0-1 to position -150 to 150 musicSliderHandle.x = (musicVolume - 0.5) * 300; sfxSliderHandle.x = (sfxVolume - 0.5) * 300; // Ball color selection section var ballLabel = new Text2('BALL COLOR', { size: 80, fill: 0xFFFFFF }); ballLabel.anchor.set(0.5, 0.5); ballLabel.y = 200; self.addChild(ballLabel); // Color options for ball var ballColors = [0xffffff, 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff]; var ballColorButtons = []; for (var j = 0; j < ballColors.length; j++) { var ballBtn = new Container(); var ballAsset = ballBtn.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); ballAsset.tint = ballColors[j]; ballBtn.x = (j - 3) * 150; ballBtn.y = 100; ballBtn.interactive = true; ballBtn.colorValue = ballColors[j]; ballBtn.colorType = 'ball'; ballColorButtons.push(ballBtn); self.addChild(ballBtn); } // Update UI to show selected colors self.updateSelections = function () { var paddleColor = storage.paddleColor || 0xffbd00; var ballColor = storage.ballColor || 0xffffff; // Update paddle color buttons for (var i = 0; i < paddleColorButtons.length; i++) { var btn = paddleColorButtons[i]; if (btn.colorValue === paddleColor) { tween(btn, { scaleX: 1.8, scaleY: 1.8 }, { duration: 200 }); } else { tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } } // Update ball color buttons for (var j = 0; j < ballColorButtons.length; j++) { var bBtn = ballColorButtons[j]; if (bBtn.colorValue === ballColor) { tween(bBtn, { scaleX: 1.8, scaleY: 1.8 }, { duration: 200 }); } else { tween(bBtn, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } } }; // Register color button events for all buttons function registerColorButtons() { var isDragging = false; // Volume slider handlers var currentSlider = null; // Master volume slider handlers masterSliderHandle.down = function (x, y, obj) { isDragging = true; currentSlider = this; tween(this, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100 }); }; masterSliderHandle.move = function (x, y, obj) { if (this.parent && isDragging && currentSlider === this) { var localX = x; // Clamp to slider bounds (-150 to 150) if (localX < -150) { localX = -150; } if (localX > 150) { localX = 150; } this.x = localX; // Convert position to volume (0 to 1) var newVolume = (localX + 150) / 300; // Store volume setting in storage for persistence storage.volume = newVolume; // Test sound for immediate feedback with updated volume if (LK.ticks % 15 === 0) { LK.getSound('bounce').play({ volume: newVolume }); } } }; masterSliderHandle.up = function () { if (currentSlider === this) { isDragging = false; currentSlider = null; } tween(this, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut }); }; // Music volume slider handlers musicSliderHandle.down = function (x, y, obj) { isDragging = true; currentSlider = this; tween(this, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100 }); }; musicSliderHandle.move = function (x, y, obj) { if (this.parent && isDragging && currentSlider === this) { var localX = x; // Clamp to slider bounds if (localX < -150) { localX = -150; } if (localX > 150) { localX = 150; } this.x = localX; // Convert position to volume (0 to 1) var newMusicVolume = (localX + 150) / 300; // Store music volume setting storage.musicVolume = newMusicVolume; // Give immediate feedback by adjusting current music if (LK.ticks % 15 === 0) { // Apply music volume change var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; LK.playMusic('bgmusic', { volume: masterVolume * newMusicVolume }); } } }; musicSliderHandle.up = function () { if (currentSlider === this) { isDragging = false; currentSlider = null; } tween(this, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut }); }; // SFX volume slider handlers sfxSliderHandle.down = function (x, y, obj) { isDragging = true; currentSlider = this; tween(this, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100 }); }; sfxSliderHandle.move = function (x, y, obj) { if (this.parent && isDragging && currentSlider === this) { var localX = x; // Clamp to slider bounds if (localX < -150) { localX = -150; } if (localX > 150) { localX = 150; } this.x = localX; // Convert position to volume (0 to 1) var newSfxVolume = (localX + 150) / 300; // Store sfx volume setting storage.sfxVolume = newSfxVolume; // Test sound for immediate feedback with updated volume if (LK.ticks % 15 === 0) { var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; LK.getSound('click').play({ volume: masterVolume * newSfxVolume }); } } }; sfxSliderHandle.up = function () { if (currentSlider === this) { isDragging = false; currentSlider = null; } tween(this, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut }); }; // Paddle color buttons for (var i = 0; i < paddleColorButtons.length; i++) { var pBtn = paddleColorButtons[i]; pBtn.down = function () { tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; pBtn.up = function () { var btn = this; tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { storage.paddleColor = btn.colorValue; self.updateSelections(); // Update game paddle colors if (self.onColorChange) { self.onColorChange(); } } }); }; } // Ball color buttons for (var j = 0; j < ballColorButtons.length; j++) { var bBtn = ballColorButtons[j]; bBtn.down = function () { tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; bBtn.up = function () { var btn = this; tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { storage.ballColor = btn.colorValue; self.updateSelections(); // Update game ball colors if (self.onColorChange) { self.onColorChange(); } } }); }; } } // Close button event closeButton.down = function () { tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; closeButton.up = function () { tween(this, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { // Call close callback if exists if (self.onClose) { self.onClose(); } } }); }; // Initialize registerColorButtons(); self.updateSelections(); return self; }); var StartMenu = Container.expand(function () { var self = Container.call(this); // Create title text var titleText = new Text2('Lava Bounce', { size: 200, fill: 0xFFBD00 }); titleText.anchor.set(0.5, 0.5); titleText.y = -300; self.addChild(titleText); // Create title animation function animateTitle() { tween(titleText, { y: -300 - 30 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(titleText, { y: -300 + 30 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { animateTitle(); } }); } }); } // Start the title animation animateTitle(); // Create start button var startButton = new Container(); // Remove background asset but keep the text var buttonText = new Text2('PLAY', { size: 100, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); startButton.addChild(buttonText); startButton.interactive = true; startButton.y = 0; self.addChild(startButton); // High score display var highScoreText = new Text2('HIGH SCORE: 0', { size: 60, fill: 0xFFFFFF }); highScoreText.anchor.set(0.5, 0.5); highScoreText.y = 200; self.addChild(highScoreText); // Update high score display self.updateHighScore = function (score) { highScoreText.setText('HIGH SCORE: ' + score); }; // Handle button press startButton.down = function () { // Play click sound with combined volume settings var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('click').play({ volume: masterVolume * sfxVolume }); // Scale effect tween(startButton, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; // Handle button release startButton.up = function () { // Return to original scale with bounce tween(startButton, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { // Notify game that start was pressed if (self.onStart) { self.onStart(); } } }); }; return self; }); var WallParticles = Container.expand(function () { var self = Container.call(this); self.particles = []; self.maxParticles = 15; self.createParticle = function (x, y, side) { var particle = new Container(); var particleGraphic = particle.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2 + Math.random() * 0.3, scaleY: 0.2 + Math.random() * 0.3 }); // Use white or slightly colored particles var colors = [0xFFFFFF, 0xFFFFAA, 0xAAFFFF]; particleGraphic.tint = colors[Math.floor(Math.random() * colors.length)]; particle.x = x; particle.y = y; particle.alpha = 1; // Particles fly away from the wall var speed = 2 + Math.random() * 3; if (side === 'left') { particle.speedX = speed; } else { particle.speedX = -speed; } particle.speedY = Math.random() * 6 - 3; particle.rotationSpeed = (Math.random() - 0.5) * 0.2; particle.life = 20 + Math.random() * 15; particle.maxLife = particle.life; self.addChild(particle); self.particles.push(particle); return particle; }; self.update = function () { for (var i = self.particles.length - 1; i >= 0; i--) { var particle = self.particles[i]; particle.x += particle.speedX; particle.y += particle.speedY; particle.rotation += particle.rotationSpeed; particle.speedX *= 0.95; particle.speedY *= 0.95; particle.life--; particle.alpha = particle.life / particle.maxLife; if (particle.life <= 0) { self.removeChild(particle); self.particles.splice(i, 1); } } }; self.emitWallHit = function (x, y, side, count) { for (var i = 0; i < count; i++) { self.createParticle(x, y, side); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x220000 }); /**** * Game Code ****/ // Game state // Sounds removed var gameActive = false; var settingsPanelActive = false; var confirmDialogActive = false; // Game variables var score = 0; var highScore = storage.highScore || 0; var level = 1; var hitsToNextLevel = 20; var difficulty = 1; var difficultyIncreaseTimer = null; var levelRequirement = 20; // Hits needed to complete each level var mouseParticles = new MouseParticleSystem(); // Create back button var backButton = new Container(); // Remove yellow box background, keeping just the arrow var backText = new Text2('←', { size: 60, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); backButton.addChild(backText); backButton.x = 2048 - 120; backButton.y = 80; backButton.interactive = true; backButton.visible = false; // Function to check if a display object is a child of this container function containerContains(container, child) { if (!container.children) { return false; } for (var i = 0; i < container.children.length; i++) { if (container.children[i] === child) { return true; } } return false; } ; // Load stored colors or use defaults var paddleColor = storage.paddleColor || 0xffbd00; var ballColor = storage.ballColor || 0xffffff; // Create game objects var paddle = new Paddle(); var ball = new Ball(); var lava = new Lava(); var particles = new ParticleSystem(); var wallParticles = new WallParticles(); var startMenu = new StartMenu(); var settingsPanel = new SettingsPanel(); var confirmDialog = new ConfirmDialog(); // Initialize settings panel settingsPanel.x = 2048 / 2; settingsPanel.y = 2732 / 2; settingsPanel.visible = false; settingsPanel.alpha = 0; // Apply stored colors and volume to game objects function applyStoredColors() { // Apply paddle color paddle.children.forEach(function (child) { if (child.tint !== undefined) { child.tint = paddleColor; } }); // Apply ball color if (ball.children && ball.children.length > 0) { ball.children[0].tint = ballColor; } // Apply stored volume settings - note: we don't need to set volume here // as sounds will use the stored volume when played var volume = storage.volume !== undefined ? storage.volume : 1.0; // Don't need to set volume directly on sound objects since we'll use // the volume parameter when playing sounds via LK.getSound().play({volume: x}) } // Settings panel callbacks settingsPanel.onColorChange = function () { // Update color variables paddleColor = storage.paddleColor || 0xffbd00; ballColor = storage.ballColor || 0xffffff; // Apply colors applyStoredColors(); }; settingsPanel.onClose = function () { // Hide settings panel with smoother, slower animation tween(settingsPanel, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 700, easing: tween.easeOut, onFinish: function onFinish() { settingsPanel.visible = false; settingsPanelActive = false; } }); }; // Start menu settings callback startMenu.onSettings = function () { // Show settings panel with animation settingsPanel.visible = true; settingsPanel.alpha = 0; settingsPanelActive = true; // Set initial scale for a gentler opening animation settingsPanel.scale.set(0.5); // Make sure we're not adding it multiple times if (!containerContains(game, settingsPanel)) { game.addChild(settingsPanel); } // Make sure settings panel is at the top layer if (containerContains(game, settingsPanel)) { game.removeChild(settingsPanel); game.addChild(settingsPanel); } // Animate both alpha and scale with a smoother, slower animation tween(settingsPanel, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 800, easing: tween.easeOut }); }; // Initialize paddle position paddle.x = 2048 / 2; paddle.y = 2732 - 300; // Initialize ball position ball.reset(); // Initialize lava position to be at the bottom of the screen lava.x = 2048 / 2; lava.y = 2732 + lava.height / 2 - 100; // Position lava so only top part is visible lava.baseY = lava.y; // Store the initial Y position for wave animation lava.minY = lava.height / 2; // Minimum Y position to prevent running off top edge lava.maxY = 2732 - lava.height / 2; // Maximum Y position to prevent running off bottom edge // Create confetti system for level celebrations var confetti = new Confetti(); // Position start menu in center of screen startMenu.x = 2048 / 2; startMenu.y = 2732 / 2; startMenu.updateHighScore(highScore); // Start menu callback startMenu.onStart = function () { startGame(); }; // Create settings button var settingsButton = new Container(); var settingsText = new Text2('SETTINGS', { size: 60, fill: 0xFFFFFF }); settingsText.anchor.set(0.5, 0.5); settingsButton.addChild(settingsText); settingsButton.interactive = true; settingsButton.y = 300; settingsButton.x = 0; startMenu.addChild(settingsButton); // Handle settings button press settingsButton.down = function () { var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('click').play({ volume: masterVolume * sfxVolume }); tween(settingsButton, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; // Handle settings button release settingsButton.up = function () { tween(settingsButton, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { // Show settings panel if (startMenu.onSettings) { startMenu.onSettings(); } } }); }; // Create reset high score button to start menu var resetButton = new Container(); var resetText = new Text2('RESET HIGH SCORE', { size: 40, fill: 0xFFFFFF }); resetText.anchor.set(0.5, 0.5); resetButton.addChild(resetText); resetButton.interactive = true; resetButton.y = 370; resetButton.x = 0; startMenu.addChild(resetButton); // Handle reset button press resetButton.down = function () { var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('click').play({ volume: masterVolume * sfxVolume }); tween(resetButton, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; // Handle reset button release resetButton.up = function () { tween(resetButton, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { // Show confirm dialog showConfirmDialog(); } }); }; // Add start menu to game initially game.addChild(startMenu); // Add settings panel to game (initially hidden) game.addChild(settingsPanel); // Add back button to game game.addChild(backButton); // Setup back button event handlers backButton.down = function () { var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('click').play({ volume: masterVolume * sfxVolume }); tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; backButton.up = function () { var btn = this; tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { returnToMenu(); } }); }; // Initialize confirm dialog confirmDialog.x = 2048 / 2; confirmDialog.y = 2732 / 2; confirmDialog.visible = false; confirmDialog.alpha = 0; // Setup confirm dialog callbacks confirmDialog.onConfirm = function () { // Reset high score to 0 highScore = 0; storage.highScore = 0; startMenu.updateHighScore(0); highScoreTxt.setText('High Score: 0'); // Hide dialog hideConfirmDialog(); }; confirmDialog.onCancel = function () { // Just hide dialog hideConfirmDialog(); }; // Function to show confirm dialog function showConfirmDialog() { confirmDialog.visible = true; confirmDialogActive = true; // Set initial state for a gentler animation confirmDialog.scale.set(0.5); confirmDialog.alpha = 0; // Make sure it's not already added multiple times if (containerContains(game, confirmDialog)) { game.removeChild(confirmDialog); } game.addChild(confirmDialog); // Make sure confirmDialog is at the top layer if (containerContains(game, confirmDialog)) { game.removeChild(confirmDialog); game.addChild(confirmDialog); } // Add smoother, less aggressive animation when dialog opens tween(confirmDialog, { alpha: 1, scaleX: 1.05, scaleY: 1.05 }, { duration: 700, easing: tween.easeOut, onFinish: function onFinish() { // Scale back to normal size with a gentler effect tween(confirmDialog, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); } }); } // Function to hide confirm dialog function hideConfirmDialog() { tween(confirmDialog, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { confirmDialog.visible = false; confirmDialogActive = false; } }); } // Function to start the game function startGame() { // Remove start menu game.removeChild(startMenu); // Add game objects game.addChild(paddle); game.addChild(ball); game.addChild(lava); game.addChild(particles); game.addChild(confetti); game.addChild(mouseParticles); game.addChild(wallParticles); // Setup wall hit callback for ball ball.onWallHit = function (x, y, side) { wallParticles.emitWallHit(x, y, side, 10); }; // Add back button backButton.visible = true; // Apply color settings applyStoredColors(); // Reset game state score = 0; level = 1; difficulty = 1; ball.reset(); // Play background music with fade in and proper combined volume var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var musicVolume = storage.musicVolume !== undefined ? storage.musicVolume : 1.0; var combinedVolume = masterVolume * musicVolume; LK.playMusic('bgmusic', { fade: { start: 0, end: combinedVolume, duration: 1000 } }); // Start difficulty timer if (difficultyIncreaseTimer) { LK.clearInterval(difficultyIncreaseTimer); } difficultyIncreaseTimer = LK.setInterval(increaseDifficulty, 10000); // Reset lava to its proper starting position at the bottom of the screen lava.y = 2732 + lava.height / 2 - 100; // Position lava so only top part is visible lava.baseY = lava.y; // Store the initial Y position for wave animation // Stop any existing animations tween.stop(lava, { y: true }); // Start the rise animation lava.startRiseFall(); // Set game as active gameActive = true; // Make UI elements visible scoreTxt.visible = true; levelTxt.visible = true; progressTxt.visible = true; highScoreTxt.visible = true; // Show high score when game starts // Update UI updateScore(); levelTxt.setText('Level: ' + level); progressTxt.setText('0/' + levelRequirement); } // Function to return to menu function returnToMenu() { // Remove game objects game.removeChild(paddle); game.removeChild(ball); game.removeChild(lava); game.removeChild(particles); game.removeChild(confetti); game.removeChild(wallParticles); // Hide back button backButton.visible = false; // Update high score on menu startMenu.updateHighScore(highScore); // Add menu back if (!containerContains(game, startMenu)) { game.addChild(startMenu); } // Make sure settings button and reset button events are properly attached if (settingsButton && !containerContains(startMenu, settingsButton)) { startMenu.addChild(settingsButton); } if (resetButton && !containerContains(startMenu, resetButton)) { startMenu.addChild(resetButton); } // Set game as inactive gameActive = false; // Hide UI elements scoreTxt.visible = false; levelTxt.visible = false; progressTxt.visible = false; // Fade out and stop music var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var musicVolume = storage.musicVolume !== undefined ? storage.musicVolume : 1.0; var combinedVolume = masterVolume * musicVolume; LK.playMusic('bgmusic', { fade: { start: combinedVolume, end: 0, duration: 800 } }); LK.setTimeout(function () { LK.stopMusic(); }, 800); } // Create score display var scoreTxt = new Text2('Score: 0', { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0.5); scoreTxt.y = 50; scoreTxt.visible = false; // Hide score initially // Create level display var levelTxt = new Text2('Level: 1', { size: 80, fill: 0x00FFFF }); levelTxt.anchor.set(0.5, 0); levelTxt.y = 150; levelTxt.visible = false; // Hide level initially // Create level progress display var progressTxt = new Text2('', { size: 50, fill: 0xFFFFFF }); progressTxt.anchor.set(0.5, 0); progressTxt.y = 30; progressTxt.visible = false; // Hide progress initially // Create high score display var highScoreTxt = new Text2('High Score: 0', { size: 80, fill: 0xFFBD00 }); highScoreTxt.anchor.set(1, 0); highScoreTxt.setText('High Score: ' + highScore); highScoreTxt.y = 70; highScoreTxt.visible = false; // Hide high score initially // Description text removed LK.gui.topRight.addChild(highScoreTxt); LK.gui.center.addChild(scoreTxt); LK.gui.top.addChild(levelTxt); LK.gui.top.addChild(progressTxt); // Touch/drag handling for paddle movement var isDragging = false; game.down = function (x, y, obj) { // Play click sound with combined volume settings var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('click').play({ volume: masterVolume * sfxVolume }); isDragging = true; paddle.x = x; // Emit more particles at mouse/touch position for improved visibility mouseParticles.emitAt(x, y, 35); }; game.move = function (x, y, obj) { if (isDragging) { paddle.x = x; // Clamp paddle position to keep it within screen bounds if (paddle.x < paddle.width / 2) { paddle.x = paddle.width / 2; } else if (paddle.x > 2048 - paddle.width / 2) { paddle.x = 2048 - paddle.width / 2; } } }; game.up = function (x, y, obj) { isDragging = false; }; // Function to update score display function updateScore() { scoreTxt.setText(score.toString()); // Update progress to next level var progress = score % levelRequirement; var remaining = levelRequirement - progress; progressTxt.setText(progress + '/' + levelRequirement); // Check for level completion if (progress === 0 && score > 0) { // Level up! level = Math.floor(score / levelRequirement) + 1; levelTxt.setText('Level: ' + level); // Celebrate level completion celebrateNewLevel(); // Increase difficulty with each level difficulty = 1 + (level - 1) * 0.3; if (difficulty > 3) { difficulty = 3; } // Update game properties based on new difficulty ball.gravity = 0.2 * difficulty; ball.maxSpeed = 15 * difficulty; } // Update high score if current score is higher if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('High Score: ' + highScore); } } // Function to celebrate new level function celebrateNewLevel() { // Play level up sound with combined volume settings var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('levelup').play({ volume: masterVolume * sfxVolume }); // Show celebratory text var celebrationText = new Text2('LEVEL ' + level + '!', { size: 150, fill: 0xFFFF00 }); celebrationText.anchor.set(0.5, 0.5); LK.gui.center.addChild(celebrationText); // Make text appear with scale animation celebrationText.scale.set(0.1); tween(celebrationText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.elasticOut, onFinish: function onFinish() { tween(celebrationText, { scaleX: 1, scaleY: 1 }, { duration: 200, onFinish: function onFinish() { // Fade out after a moment LK.setTimeout(function () { tween(celebrationText, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { LK.gui.center.removeChild(celebrationText); } }); }, 1000); } }); } }); // Create confetti bursts for (var i = 0; i < 5; i++) { LK.setTimeout(function () { // Create multiple confetti bursts across screen confetti.emit(Math.random() * 2048, 500, 30); }, i * 200); } // Flash screen for celebration LK.effects.flashScreen(0x00FFFF, 300); // Sounds and music removed } // Function to increase difficulty over time function increaseDifficulty() { difficulty += 0.1; // Cap maximum difficulty if (difficulty > 2) { difficulty = 2; LK.clearInterval(difficultyIncreaseTimer); } // Update ball properties based on difficulty ball.gravity = 0.1 * difficulty; ball.maxSpeed = 10 * difficulty; // Music adjustment removed } // Start difficulty increase timer difficultyIncreaseTimer = LK.setInterval(increaseDifficulty, 10000); // Music will be played when game starts // Not playing background music initially to allow menu to be silent // Game update loop game.update = function () { // If game is not active or UI panels are active, don't update game if (!gameActive || settingsPanelActive || confirmDialogActive) { return; } // Update ball position ball.update(); // Update lava waves lava.update(); // Check for collision with paddle if (ball.speedY > 0 && ball.y + ball.height / 2 >= paddle.y - paddle.height / 2 && ball.y - ball.height / 2 <= paddle.y + paddle.height / 2 && ball.x + ball.width / 2 >= paddle.x - paddle.width / 2 && ball.x - ball.width / 2 <= paddle.x + paddle.width / 2) { // Bounce the ball in a random direction when it hits the paddle var angle = Math.random() * Math.PI * 0.7 - Math.PI * 0.35; // Random angle between -35 and +35 degrees var speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY) * 1.5; // Keep overall speed but increase by 1.5x ball.speedY = -Math.cos(angle) * speed; // Vertical component (mostly upward) ball.speedX = Math.sin(angle) * speed; // Horizontal component // Add additional horizontal velocity based on where the ball hit the paddle var hitPosition = (ball.x - paddle.x) / (paddle.width / 2); ball.speedX += hitPosition * 5; // Add some influence from hit position, but less than before // Flash paddle to indicate hit LK.effects.flashObject(paddle, 0xffffff, 200); // Emit particles at collision point particles.emitAt(ball.x, paddle.y - paddle.height / 2, 0xffbd00, 15); // Add scale animation to paddle for bounce feedback tween(paddle, { scaleY: 0.7, y: paddle.y + 10 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(paddle, { scaleY: 1, y: 2732 - 300 }, { duration: 150, easing: tween.elasticOut }); } }); // Play bounce sound when ball hits paddle var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('bounce').play({ volume: masterVolume * sfxVolume }); // Increment score score++; updateScore(); } // Check if ball touches lava if (ball.y + ball.height / 2 >= lava.y - lava.height / 2) { // Play lava sound with combined volume settings var masterVolume = storage.volume !== undefined ? storage.volume : 1.0; var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0; LK.getSound('lava').play({ volume: masterVolume * sfxVolume }); LK.getSound('gameover').play({ volume: masterVolume * sfxVolume }); // Flash screen red LK.effects.flashScreen(0xff0000, 500); // Create lava splash effect particles.emitAt(ball.x, lava.y - lava.height / 2, 0xff3300, 30); // Stop any current lava animations tween.stop(lava, { y: true }); // Make lava "jump" without moving up or down tween(lava, { scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(lava, { scaleY: 1 }, { duration: 300, easing: tween.elasticOut }); } }); // Just reset the game without returning to menu // Reset score and level score = 0; level = 1; levelTxt.setText('Level: ' + level); progressTxt.setText('0/' + levelRequirement); updateScore(); // Reset ball ball.reset(); // Reset difficulty difficulty = 1; ball.gravity = 0.2; ball.maxSpeed = 15; // Restart difficulty increase timer LK.clearInterval(difficultyIncreaseTimer); difficultyIncreaseTimer = LK.setInterval(increaseDifficulty, 10000); // Music removed } // If ball goes too high, change direction if (ball.y < 100) { ball.speedY = Math.abs(ball.speedY) * 0.5; } // Update particles particles.update(); // Update confetti confetti.update(); // Update mouse particles mouseParticles.update(); // Update wall particles wallParticles.update(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
volume: 1,
musicVolume: 1,
sfxVolume: 1
});
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = ballGraphics.width;
self.height = ballGraphics.height;
self.speedY = 3;
self.speedX = 0;
self.gravity = 0.1;
self.maxSpeed = 10;
self.reset = function () {
self.x = 2048 / 2;
self.y = 500;
self.speedY = 3;
self.speedX = Math.random() * 4 - 2;
};
self.update = function () {
// Apply gravity
self.speedY += self.gravity;
// Cap max speed
if (self.speedY > self.maxSpeed) {
self.speedY = self.maxSpeed;
}
// Update position
self.x += self.speedX;
self.y += self.speedY;
// Bounce off walls
if (self.x < self.width / 2) {
self.x = self.width / 2;
self.speedX = -self.speedX;
// Play wall hit sound
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('bounce').play({
volume: masterVolume * sfxVolume * 0.7
});
// Emit wall hit particles
if (self.onWallHit) {
self.onWallHit(self.x, self.y, 'left');
}
} else if (self.x > 2048 - self.width / 2) {
self.x = 2048 - self.width / 2;
self.speedX = -self.speedX;
// Play wall hit sound
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('bounce').play({
volume: masterVolume * sfxVolume * 0.7
});
// Emit wall hit particles
if (self.onWallHit) {
self.onWallHit(self.x, self.y, 'right');
}
}
};
return self;
});
var Confetti = Container.expand(function () {
var self = Container.call(this);
self.particles = [];
self.colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFFFFF];
self.emit = function (x, y, count) {
for (var i = 0; i < count; i++) {
var particle = new Container();
var size = Math.random() * 20 + 10;
var color = self.colors[Math.floor(Math.random() * self.colors.length)];
var particleGraphic = particle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size / 40,
scaleY: size / 40
});
particleGraphic.tint = color;
particle.x = x;
particle.y = y;
particle.speedX = Math.random() * 20 - 10;
particle.speedY = -Math.random() * 15 - 5;
particle.rotationSpeed = (Math.random() - 0.5) * 0.2;
particle.life = 60 + Math.random() * 60;
particle.maxLife = particle.life;
self.addChild(particle);
self.particles.push(particle);
}
};
self.update = function () {
for (var i = self.particles.length - 1; i >= 0; i--) {
var particle = self.particles[i];
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.speedY += 0.2; // Gravity
particle.rotation += particle.rotationSpeed;
particle.life--;
particle.alpha = particle.life / particle.maxLife;
if (particle.life <= 0) {
self.removeChild(particle);
self.particles.splice(i, 1);
}
}
};
return self;
});
var ConfirmDialog = Container.expand(function () {
var self = Container.call(this);
// Create semi-transparent background
var bg = new Container();
var bgAsset = bg.attachAsset('lava', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2
});
bgAsset.alpha = 0.8;
bgAsset.tint = 0x000000;
self.addChild(bg);
// Create message text
var messageText = new Text2('Are you sure you want\nto reset your high score?', {
size: 80,
fill: 0xFFFFFF
});
messageText.anchor.set(0.5, 0.5);
messageText.y = -100;
self.addChild(messageText);
// Create yes button
var yesButton = new Container();
var yesText = new Text2('YES', {
size: 80,
fill: 0xFF0000
});
yesText.anchor.set(0.5, 0.5);
yesButton.addChild(yesText);
yesButton.x = -150;
yesButton.y = 100;
yesButton.interactive = true;
self.addChild(yesButton);
// Create no button
var noButton = new Container();
var noText = new Text2('NO', {
size: 80,
fill: 0xFFFFFF
});
noText.anchor.set(0.5, 0.5);
noButton.addChild(noText);
noButton.x = 150;
noButton.y = 100;
noButton.interactive = true;
self.addChild(noButton);
// Button press handlers
yesButton.down = function () {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
yesButton.up = function () {
var btn = this;
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
if (self.onConfirm) {
self.onConfirm();
}
}
});
};
noButton.down = function () {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
noButton.up = function () {
var btn = this;
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
if (self.onCancel) {
self.onCancel();
}
}
});
};
return self;
});
var Lava = Container.expand(function () {
var self = Container.call(this);
var lavaGraphics = self.attachAsset('lava', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial wave properties
self.waveOffset = Math.random() * Math.PI * 2; // Random starting position for wave
self.baseY = 0; // Will store the initial y position
self.rising = false; // Track if lava is rising or falling
self.moveRange = 300; // How far the lava will move up and down
self.moveSpeed = 5000; // Duration of rise/fall movement in ms
// The paddle reference will be set when the lava is added to the game
// Start the rise and fall cycle
self.startRiseFall = function () {
// Make the lava rise a little bit from its base position
tween(self, {
y: self.baseY - 100
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.baseY
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: self.startRiseFall
});
}
});
};
// Create a pulsing effect for the lava
self.pulse = function () {
tween(lavaGraphics, {
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lavaGraphics, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: self.pulse
});
}
});
};
// Create animated wave effect
self.update = function () {
// Create a wave effect based on time
var waveTime = LK.ticks / 30; // Adjust speed of wave
var waveScale = Math.sin(waveTime) * 0.05 + 1; // Scale between 0.95 and 1.05
// Apply different wave scales to left and right sides
var leftWaveScale = Math.sin(waveTime + 1) * 0.05 + 1;
var rightWaveScale = Math.sin(waveTime + 2) * 0.05 + 1;
// Apply the scale distortion to create wave effect
lavaGraphics.scaleX = waveScale;
// Small vertical oscillation to add to the tween movement
var oscillation = Math.sin(waveTime * 0.5) * 0.5;
// The y position is controlled by the tween, we just add small oscillation
// (Not setting self.y directly here since tweens are handling the major movement)
};
self.pulse();
return self;
});
var MouseParticleSystem = Container.expand(function () {
var self = Container.call(this);
self.particles = [];
self.maxParticles = 100;
self.createParticle = function (x, y) {
var particle = new Container();
var particleGraphic = particle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
// Use bright colors for better visibility
var colors = [0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFF5500, 0x88FF00];
particleGraphic.tint = colors[Math.floor(Math.random() * colors.length)];
particle.x = x;
particle.y = y;
particle.alpha = 1;
particle.speedX = Math.random() * 12 - 6;
particle.speedY = Math.random() * 12 - 6;
particle.life = 30 + Math.random() * 30;
particle.maxLife = particle.life;
self.addChild(particle);
self.particles.push(particle);
return particle;
};
self.update = function () {
for (var i = self.particles.length - 1; i >= 0; i--) {
var particle = self.particles[i];
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.speedX *= 0.96;
particle.speedY *= 0.96;
particle.life--;
particle.alpha = particle.life / particle.maxLife;
// Smoother size transition with tween-like effect
particle.scaleX = particle.life / particle.maxLife * 0.4 + 0.1;
particle.scaleY = particle.life / particle.maxLife * 0.4 + 0.1;
// Add rotation for more visual interest
particle.rotation += 0.05;
if (particle.life <= 0) {
self.removeChild(particle);
self.particles.splice(i, 1);
}
}
};
self.emitAt = function (x, y, count) {
// Create more particles for better visual effect
for (var i = 0; i < count; i++) {
// Slightly offset each particle for a more natural burst effect
var offsetX = x + (Math.random() * 20 - 10);
var offsetY = y + (Math.random() * 20 - 10);
self.createParticle(offsetX, offsetY);
}
};
return self;
});
var Paddle = Container.expand(function () {
var self = Container.call(this);
// Create left rounded edge
var paddleLeftGraphics = self.attachAsset('paddleLeft', {
anchorX: 1.0,
anchorY: 0.5,
x: -170
});
// Create middle section
var paddleMiddleGraphics = self.attachAsset('paddleMiddle', {
anchorX: 0.5,
anchorY: 0.5
});
// Create right rounded edge
var paddleRightGraphics = self.attachAsset('paddleRight', {
anchorX: 0.0,
anchorY: 0.5,
x: 170
});
// Adjust positions to eliminate gaps
// The middle section's width is 340, and each end cap is 30
// We need to ensure they connect perfectly
paddleLeftGraphics.x = -170 + 15; // Move right by half of end cap width
paddleRightGraphics.x = 170 - 15; // Move left by half of end cap width
// Set the overall width based on the combined elements
self.width = 400; // total width (30 + 340 + 30)
self.height = 30;
return self;
});
var ParticleSystem = Container.expand(function () {
var self = Container.call(this);
self.particles = [];
self.maxParticles = 20;
self.createParticle = function (x, y, color) {
var particle = new Container();
var particleGraphic = particle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particleGraphic.tint = color;
particle.x = x;
particle.y = y;
particle.alpha = 1;
particle.speedX = Math.random() * 10 - 5;
particle.speedY = -Math.random() * 5 - 2;
particle.life = 30;
self.addChild(particle);
self.particles.push(particle);
return particle;
};
self.update = function () {
for (var i = self.particles.length - 1; i >= 0; i--) {
var particle = self.particles[i];
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.speedY += 0.1;
particle.life--;
particle.alpha = particle.life / 30;
if (particle.life <= 0) {
self.removeChild(particle);
self.particles.splice(i, 1);
}
}
};
self.emitAt = function (x, y, color, count) {
for (var i = 0; i < count; i++) {
self.createParticle(x, y, color);
}
};
return self;
});
var SettingsPanel = Container.expand(function () {
var self = Container.call(this);
// Create semi-transparent background
var bg = new Container();
var bgAsset = bg.attachAsset('lava', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 5
});
bgAsset.alpha = 0.7;
bgAsset.tint = 0x000000;
self.addChild(bg);
// Create panel title
var titleText = new Text2('SETTINGS', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -500;
self.addChild(titleText);
// Create close button
var closeButton = new Container();
var closeText = new Text2('X', {
size: 80,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = 800;
closeButton.y = -500;
closeButton.interactive = true;
self.addChild(closeButton);
// Paddle color selection section
var paddleLabel = new Text2('PADDLE COLOR', {
size: 80,
fill: 0xFFFFFF
});
paddleLabel.anchor.set(0.5, 0.5);
paddleLabel.y = -300;
self.addChild(paddleLabel);
// Color options for paddle
var paddleColors = [0xffbd00, 0xff0000, 0x00ff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff];
var paddleColorButtons = [];
for (var i = 0; i < paddleColors.length; i++) {
var colorBtn = new Container();
var colorAsset = colorBtn.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
colorAsset.tint = paddleColors[i];
colorBtn.x = (i - 3) * 150;
colorBtn.y = -200;
colorBtn.interactive = true;
colorBtn.colorValue = paddleColors[i];
colorBtn.colorType = 'paddle';
paddleColorButtons.push(colorBtn);
self.addChild(colorBtn);
}
// Sound control section
var soundLabel = new Text2('SOUND SETTINGS', {
size: 80,
fill: 0xFFFFFF
});
soundLabel.anchor.set(0.5, 0.5);
soundLabel.y = 300;
self.addChild(soundLabel);
// Master volume controls
var masterVolumeLabel = new Text2('Master Volume', {
size: 50,
fill: 0xFFFFFF
});
masterVolumeLabel.anchor.set(0.5, 0.5);
masterVolumeLabel.y = 360;
self.addChild(masterVolumeLabel);
// Add master slider background
var masterSliderBg = new Container();
var masterSliderBgAsset = masterSliderBg.attachAsset('paddleMiddle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.5
});
masterSliderBgAsset.tint = 0x555555;
masterSliderBg.x = 0;
masterSliderBg.y = 400;
self.addChild(masterSliderBg);
// Add master slider handle
var masterSliderHandle = new Container();
var masterSliderHandleAsset = masterSliderHandle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
masterSliderHandleAsset.tint = 0xFFFFFF;
masterSliderHandle.interactive = true;
masterSliderHandle.y = 400;
self.addChild(masterSliderHandle);
// Music volume controls
var musicVolumeLabel = new Text2('Music Volume', {
size: 50,
fill: 0xFFFFFF
});
musicVolumeLabel.anchor.set(0.5, 0.5);
musicVolumeLabel.y = 460;
self.addChild(musicVolumeLabel);
// Add music slider background
var musicSliderBg = new Container();
var musicSliderBgAsset = musicSliderBg.attachAsset('paddleMiddle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.5
});
musicSliderBgAsset.tint = 0x555555;
musicSliderBg.x = 0;
musicSliderBg.y = 500;
self.addChild(musicSliderBg);
// Add music slider handle
var musicSliderHandle = new Container();
var musicSliderHandleAsset = musicSliderHandle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
musicSliderHandleAsset.tint = 0x00FFFF;
musicSliderHandle.interactive = true;
musicSliderHandle.y = 500;
self.addChild(musicSliderHandle);
// SFX volume controls
var sfxVolumeLabel = new Text2('SFX Volume', {
size: 50,
fill: 0xFFFFFF
});
sfxVolumeLabel.anchor.set(0.5, 0.5);
sfxVolumeLabel.y = 560;
self.addChild(sfxVolumeLabel);
// Add SFX slider background
var sfxSliderBg = new Container();
var sfxSliderBgAsset = sfxSliderBg.attachAsset('paddleMiddle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.5
});
sfxSliderBgAsset.tint = 0x555555;
sfxSliderBg.x = 0;
sfxSliderBg.y = 600;
self.addChild(sfxSliderBg);
// Add SFX slider handle
var sfxSliderHandle = new Container();
var sfxSliderHandleAsset = sfxSliderHandle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sfxSliderHandleAsset.tint = 0xFF5500;
sfxSliderHandle.interactive = true;
sfxSliderHandle.y = 600;
self.addChild(sfxSliderHandle);
// Set initial slider positions based on stored volumes
var volume = storage.volume !== undefined ? storage.volume : 1.0;
var musicVolume = storage.musicVolume !== undefined ? storage.musicVolume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
masterSliderHandle.x = (volume - 0.5) * 300; // Map volume 0-1 to position -150 to 150
musicSliderHandle.x = (musicVolume - 0.5) * 300;
sfxSliderHandle.x = (sfxVolume - 0.5) * 300;
// Ball color selection section
var ballLabel = new Text2('BALL COLOR', {
size: 80,
fill: 0xFFFFFF
});
ballLabel.anchor.set(0.5, 0.5);
ballLabel.y = 200;
self.addChild(ballLabel);
// Color options for ball
var ballColors = [0xffffff, 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];
var ballColorButtons = [];
for (var j = 0; j < ballColors.length; j++) {
var ballBtn = new Container();
var ballAsset = ballBtn.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
ballAsset.tint = ballColors[j];
ballBtn.x = (j - 3) * 150;
ballBtn.y = 100;
ballBtn.interactive = true;
ballBtn.colorValue = ballColors[j];
ballBtn.colorType = 'ball';
ballColorButtons.push(ballBtn);
self.addChild(ballBtn);
}
// Update UI to show selected colors
self.updateSelections = function () {
var paddleColor = storage.paddleColor || 0xffbd00;
var ballColor = storage.ballColor || 0xffffff;
// Update paddle color buttons
for (var i = 0; i < paddleColorButtons.length; i++) {
var btn = paddleColorButtons[i];
if (btn.colorValue === paddleColor) {
tween(btn, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 200
});
} else {
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
}
// Update ball color buttons
for (var j = 0; j < ballColorButtons.length; j++) {
var bBtn = ballColorButtons[j];
if (bBtn.colorValue === ballColor) {
tween(bBtn, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 200
});
} else {
tween(bBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
}
};
// Register color button events for all buttons
function registerColorButtons() {
var isDragging = false;
// Volume slider handlers
var currentSlider = null;
// Master volume slider handlers
masterSliderHandle.down = function (x, y, obj) {
isDragging = true;
currentSlider = this;
tween(this, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
};
masterSliderHandle.move = function (x, y, obj) {
if (this.parent && isDragging && currentSlider === this) {
var localX = x;
// Clamp to slider bounds (-150 to 150)
if (localX < -150) {
localX = -150;
}
if (localX > 150) {
localX = 150;
}
this.x = localX;
// Convert position to volume (0 to 1)
var newVolume = (localX + 150) / 300;
// Store volume setting in storage for persistence
storage.volume = newVolume;
// Test sound for immediate feedback with updated volume
if (LK.ticks % 15 === 0) {
LK.getSound('bounce').play({
volume: newVolume
});
}
}
};
masterSliderHandle.up = function () {
if (currentSlider === this) {
isDragging = false;
currentSlider = null;
}
tween(this, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
};
// Music volume slider handlers
musicSliderHandle.down = function (x, y, obj) {
isDragging = true;
currentSlider = this;
tween(this, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
};
musicSliderHandle.move = function (x, y, obj) {
if (this.parent && isDragging && currentSlider === this) {
var localX = x;
// Clamp to slider bounds
if (localX < -150) {
localX = -150;
}
if (localX > 150) {
localX = 150;
}
this.x = localX;
// Convert position to volume (0 to 1)
var newMusicVolume = (localX + 150) / 300;
// Store music volume setting
storage.musicVolume = newMusicVolume;
// Give immediate feedback by adjusting current music
if (LK.ticks % 15 === 0) {
// Apply music volume change
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
LK.playMusic('bgmusic', {
volume: masterVolume * newMusicVolume
});
}
}
};
musicSliderHandle.up = function () {
if (currentSlider === this) {
isDragging = false;
currentSlider = null;
}
tween(this, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
};
// SFX volume slider handlers
sfxSliderHandle.down = function (x, y, obj) {
isDragging = true;
currentSlider = this;
tween(this, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
};
sfxSliderHandle.move = function (x, y, obj) {
if (this.parent && isDragging && currentSlider === this) {
var localX = x;
// Clamp to slider bounds
if (localX < -150) {
localX = -150;
}
if (localX > 150) {
localX = 150;
}
this.x = localX;
// Convert position to volume (0 to 1)
var newSfxVolume = (localX + 150) / 300;
// Store sfx volume setting
storage.sfxVolume = newSfxVolume;
// Test sound for immediate feedback with updated volume
if (LK.ticks % 15 === 0) {
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
LK.getSound('click').play({
volume: masterVolume * newSfxVolume
});
}
}
};
sfxSliderHandle.up = function () {
if (currentSlider === this) {
isDragging = false;
currentSlider = null;
}
tween(this, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
};
// Paddle color buttons
for (var i = 0; i < paddleColorButtons.length; i++) {
var pBtn = paddleColorButtons[i];
pBtn.down = function () {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
pBtn.up = function () {
var btn = this;
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
storage.paddleColor = btn.colorValue;
self.updateSelections();
// Update game paddle colors
if (self.onColorChange) {
self.onColorChange();
}
}
});
};
}
// Ball color buttons
for (var j = 0; j < ballColorButtons.length; j++) {
var bBtn = ballColorButtons[j];
bBtn.down = function () {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
bBtn.up = function () {
var btn = this;
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
storage.ballColor = btn.colorValue;
self.updateSelections();
// Update game ball colors
if (self.onColorChange) {
self.onColorChange();
}
}
});
};
}
}
// Close button event
closeButton.down = function () {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
closeButton.up = function () {
tween(this, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Call close callback if exists
if (self.onClose) {
self.onClose();
}
}
});
};
// Initialize
registerColorButtons();
self.updateSelections();
return self;
});
var StartMenu = Container.expand(function () {
var self = Container.call(this);
// Create title text
var titleText = new Text2('Lava Bounce', {
size: 200,
fill: 0xFFBD00
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -300;
self.addChild(titleText);
// Create title animation
function animateTitle() {
tween(titleText, {
y: -300 - 30
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(titleText, {
y: -300 + 30
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
animateTitle();
}
});
}
});
}
// Start the title animation
animateTitle();
// Create start button
var startButton = new Container();
// Remove background asset but keep the text
var buttonText = new Text2('PLAY', {
size: 100,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
startButton.addChild(buttonText);
startButton.interactive = true;
startButton.y = 0;
self.addChild(startButton);
// High score display
var highScoreText = new Text2('HIGH SCORE: 0', {
size: 60,
fill: 0xFFFFFF
});
highScoreText.anchor.set(0.5, 0.5);
highScoreText.y = 200;
self.addChild(highScoreText);
// Update high score display
self.updateHighScore = function (score) {
highScoreText.setText('HIGH SCORE: ' + score);
};
// Handle button press
startButton.down = function () {
// Play click sound with combined volume settings
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('click').play({
volume: masterVolume * sfxVolume
});
// Scale effect
tween(startButton, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
// Handle button release
startButton.up = function () {
// Return to original scale with bounce
tween(startButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Notify game that start was pressed
if (self.onStart) {
self.onStart();
}
}
});
};
return self;
});
var WallParticles = Container.expand(function () {
var self = Container.call(this);
self.particles = [];
self.maxParticles = 15;
self.createParticle = function (x, y, side) {
var particle = new Container();
var particleGraphic = particle.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2 + Math.random() * 0.3,
scaleY: 0.2 + Math.random() * 0.3
});
// Use white or slightly colored particles
var colors = [0xFFFFFF, 0xFFFFAA, 0xAAFFFF];
particleGraphic.tint = colors[Math.floor(Math.random() * colors.length)];
particle.x = x;
particle.y = y;
particle.alpha = 1;
// Particles fly away from the wall
var speed = 2 + Math.random() * 3;
if (side === 'left') {
particle.speedX = speed;
} else {
particle.speedX = -speed;
}
particle.speedY = Math.random() * 6 - 3;
particle.rotationSpeed = (Math.random() - 0.5) * 0.2;
particle.life = 20 + Math.random() * 15;
particle.maxLife = particle.life;
self.addChild(particle);
self.particles.push(particle);
return particle;
};
self.update = function () {
for (var i = self.particles.length - 1; i >= 0; i--) {
var particle = self.particles[i];
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.rotation += particle.rotationSpeed;
particle.speedX *= 0.95;
particle.speedY *= 0.95;
particle.life--;
particle.alpha = particle.life / particle.maxLife;
if (particle.life <= 0) {
self.removeChild(particle);
self.particles.splice(i, 1);
}
}
};
self.emitWallHit = function (x, y, side, count) {
for (var i = 0; i < count; i++) {
self.createParticle(x, y, side);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x220000
});
/****
* Game Code
****/
// Game state
// Sounds removed
var gameActive = false;
var settingsPanelActive = false;
var confirmDialogActive = false;
// Game variables
var score = 0;
var highScore = storage.highScore || 0;
var level = 1;
var hitsToNextLevel = 20;
var difficulty = 1;
var difficultyIncreaseTimer = null;
var levelRequirement = 20; // Hits needed to complete each level
var mouseParticles = new MouseParticleSystem();
// Create back button
var backButton = new Container();
// Remove yellow box background, keeping just the arrow
var backText = new Text2('←', {
size: 60,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backText);
backButton.x = 2048 - 120;
backButton.y = 80;
backButton.interactive = true;
backButton.visible = false;
// Function to check if a display object is a child of this container
function containerContains(container, child) {
if (!container.children) {
return false;
}
for (var i = 0; i < container.children.length; i++) {
if (container.children[i] === child) {
return true;
}
}
return false;
}
;
// Load stored colors or use defaults
var paddleColor = storage.paddleColor || 0xffbd00;
var ballColor = storage.ballColor || 0xffffff;
// Create game objects
var paddle = new Paddle();
var ball = new Ball();
var lava = new Lava();
var particles = new ParticleSystem();
var wallParticles = new WallParticles();
var startMenu = new StartMenu();
var settingsPanel = new SettingsPanel();
var confirmDialog = new ConfirmDialog();
// Initialize settings panel
settingsPanel.x = 2048 / 2;
settingsPanel.y = 2732 / 2;
settingsPanel.visible = false;
settingsPanel.alpha = 0;
// Apply stored colors and volume to game objects
function applyStoredColors() {
// Apply paddle color
paddle.children.forEach(function (child) {
if (child.tint !== undefined) {
child.tint = paddleColor;
}
});
// Apply ball color
if (ball.children && ball.children.length > 0) {
ball.children[0].tint = ballColor;
}
// Apply stored volume settings - note: we don't need to set volume here
// as sounds will use the stored volume when played
var volume = storage.volume !== undefined ? storage.volume : 1.0;
// Don't need to set volume directly on sound objects since we'll use
// the volume parameter when playing sounds via LK.getSound().play({volume: x})
}
// Settings panel callbacks
settingsPanel.onColorChange = function () {
// Update color variables
paddleColor = storage.paddleColor || 0xffbd00;
ballColor = storage.ballColor || 0xffffff;
// Apply colors
applyStoredColors();
};
settingsPanel.onClose = function () {
// Hide settings panel with smoother, slower animation
tween(settingsPanel, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
settingsPanel.visible = false;
settingsPanelActive = false;
}
});
};
// Start menu settings callback
startMenu.onSettings = function () {
// Show settings panel with animation
settingsPanel.visible = true;
settingsPanel.alpha = 0;
settingsPanelActive = true;
// Set initial scale for a gentler opening animation
settingsPanel.scale.set(0.5);
// Make sure we're not adding it multiple times
if (!containerContains(game, settingsPanel)) {
game.addChild(settingsPanel);
}
// Make sure settings panel is at the top layer
if (containerContains(game, settingsPanel)) {
game.removeChild(settingsPanel);
game.addChild(settingsPanel);
}
// Animate both alpha and scale with a smoother, slower animation
tween(settingsPanel, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeOut
});
};
// Initialize paddle position
paddle.x = 2048 / 2;
paddle.y = 2732 - 300;
// Initialize ball position
ball.reset();
// Initialize lava position to be at the bottom of the screen
lava.x = 2048 / 2;
lava.y = 2732 + lava.height / 2 - 100; // Position lava so only top part is visible
lava.baseY = lava.y; // Store the initial Y position for wave animation
lava.minY = lava.height / 2; // Minimum Y position to prevent running off top edge
lava.maxY = 2732 - lava.height / 2; // Maximum Y position to prevent running off bottom edge
// Create confetti system for level celebrations
var confetti = new Confetti();
// Position start menu in center of screen
startMenu.x = 2048 / 2;
startMenu.y = 2732 / 2;
startMenu.updateHighScore(highScore);
// Start menu callback
startMenu.onStart = function () {
startGame();
};
// Create settings button
var settingsButton = new Container();
var settingsText = new Text2('SETTINGS', {
size: 60,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsButton.addChild(settingsText);
settingsButton.interactive = true;
settingsButton.y = 300;
settingsButton.x = 0;
startMenu.addChild(settingsButton);
// Handle settings button press
settingsButton.down = function () {
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('click').play({
volume: masterVolume * sfxVolume
});
tween(settingsButton, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
// Handle settings button release
settingsButton.up = function () {
tween(settingsButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Show settings panel
if (startMenu.onSettings) {
startMenu.onSettings();
}
}
});
};
// Create reset high score button to start menu
var resetButton = new Container();
var resetText = new Text2('RESET HIGH SCORE', {
size: 40,
fill: 0xFFFFFF
});
resetText.anchor.set(0.5, 0.5);
resetButton.addChild(resetText);
resetButton.interactive = true;
resetButton.y = 370;
resetButton.x = 0;
startMenu.addChild(resetButton);
// Handle reset button press
resetButton.down = function () {
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('click').play({
volume: masterVolume * sfxVolume
});
tween(resetButton, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
// Handle reset button release
resetButton.up = function () {
tween(resetButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Show confirm dialog
showConfirmDialog();
}
});
};
// Add start menu to game initially
game.addChild(startMenu);
// Add settings panel to game (initially hidden)
game.addChild(settingsPanel);
// Add back button to game
game.addChild(backButton);
// Setup back button event handlers
backButton.down = function () {
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('click').play({
volume: masterVolume * sfxVolume
});
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
backButton.up = function () {
var btn = this;
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
returnToMenu();
}
});
};
// Initialize confirm dialog
confirmDialog.x = 2048 / 2;
confirmDialog.y = 2732 / 2;
confirmDialog.visible = false;
confirmDialog.alpha = 0;
// Setup confirm dialog callbacks
confirmDialog.onConfirm = function () {
// Reset high score to 0
highScore = 0;
storage.highScore = 0;
startMenu.updateHighScore(0);
highScoreTxt.setText('High Score: 0');
// Hide dialog
hideConfirmDialog();
};
confirmDialog.onCancel = function () {
// Just hide dialog
hideConfirmDialog();
};
// Function to show confirm dialog
function showConfirmDialog() {
confirmDialog.visible = true;
confirmDialogActive = true;
// Set initial state for a gentler animation
confirmDialog.scale.set(0.5);
confirmDialog.alpha = 0;
// Make sure it's not already added multiple times
if (containerContains(game, confirmDialog)) {
game.removeChild(confirmDialog);
}
game.addChild(confirmDialog);
// Make sure confirmDialog is at the top layer
if (containerContains(game, confirmDialog)) {
game.removeChild(confirmDialog);
game.addChild(confirmDialog);
}
// Add smoother, less aggressive animation when dialog opens
tween(confirmDialog, {
alpha: 1,
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
// Scale back to normal size with a gentler effect
tween(confirmDialog, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
});
}
// Function to hide confirm dialog
function hideConfirmDialog() {
tween(confirmDialog, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
confirmDialog.visible = false;
confirmDialogActive = false;
}
});
}
// Function to start the game
function startGame() {
// Remove start menu
game.removeChild(startMenu);
// Add game objects
game.addChild(paddle);
game.addChild(ball);
game.addChild(lava);
game.addChild(particles);
game.addChild(confetti);
game.addChild(mouseParticles);
game.addChild(wallParticles);
// Setup wall hit callback for ball
ball.onWallHit = function (x, y, side) {
wallParticles.emitWallHit(x, y, side, 10);
};
// Add back button
backButton.visible = true;
// Apply color settings
applyStoredColors();
// Reset game state
score = 0;
level = 1;
difficulty = 1;
ball.reset();
// Play background music with fade in and proper combined volume
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var musicVolume = storage.musicVolume !== undefined ? storage.musicVolume : 1.0;
var combinedVolume = masterVolume * musicVolume;
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: combinedVolume,
duration: 1000
}
});
// Start difficulty timer
if (difficultyIncreaseTimer) {
LK.clearInterval(difficultyIncreaseTimer);
}
difficultyIncreaseTimer = LK.setInterval(increaseDifficulty, 10000);
// Reset lava to its proper starting position at the bottom of the screen
lava.y = 2732 + lava.height / 2 - 100; // Position lava so only top part is visible
lava.baseY = lava.y; // Store the initial Y position for wave animation
// Stop any existing animations
tween.stop(lava, {
y: true
});
// Start the rise animation
lava.startRiseFall();
// Set game as active
gameActive = true;
// Make UI elements visible
scoreTxt.visible = true;
levelTxt.visible = true;
progressTxt.visible = true;
highScoreTxt.visible = true; // Show high score when game starts
// Update UI
updateScore();
levelTxt.setText('Level: ' + level);
progressTxt.setText('0/' + levelRequirement);
}
// Function to return to menu
function returnToMenu() {
// Remove game objects
game.removeChild(paddle);
game.removeChild(ball);
game.removeChild(lava);
game.removeChild(particles);
game.removeChild(confetti);
game.removeChild(wallParticles);
// Hide back button
backButton.visible = false;
// Update high score on menu
startMenu.updateHighScore(highScore);
// Add menu back
if (!containerContains(game, startMenu)) {
game.addChild(startMenu);
}
// Make sure settings button and reset button events are properly attached
if (settingsButton && !containerContains(startMenu, settingsButton)) {
startMenu.addChild(settingsButton);
}
if (resetButton && !containerContains(startMenu, resetButton)) {
startMenu.addChild(resetButton);
}
// Set game as inactive
gameActive = false;
// Hide UI elements
scoreTxt.visible = false;
levelTxt.visible = false;
progressTxt.visible = false;
// Fade out and stop music
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var musicVolume = storage.musicVolume !== undefined ? storage.musicVolume : 1.0;
var combinedVolume = masterVolume * musicVolume;
LK.playMusic('bgmusic', {
fade: {
start: combinedVolume,
end: 0,
duration: 800
}
});
LK.setTimeout(function () {
LK.stopMusic();
}, 800);
}
// Create score display
var scoreTxt = new Text2('Score: 0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0.5);
scoreTxt.y = 50;
scoreTxt.visible = false; // Hide score initially
// Create level display
var levelTxt = new Text2('Level: 1', {
size: 80,
fill: 0x00FFFF
});
levelTxt.anchor.set(0.5, 0);
levelTxt.y = 150;
levelTxt.visible = false; // Hide level initially
// Create level progress display
var progressTxt = new Text2('', {
size: 50,
fill: 0xFFFFFF
});
progressTxt.anchor.set(0.5, 0);
progressTxt.y = 30;
progressTxt.visible = false; // Hide progress initially
// Create high score display
var highScoreTxt = new Text2('High Score: 0', {
size: 80,
fill: 0xFFBD00
});
highScoreTxt.anchor.set(1, 0);
highScoreTxt.setText('High Score: ' + highScore);
highScoreTxt.y = 70;
highScoreTxt.visible = false; // Hide high score initially
// Description text removed
LK.gui.topRight.addChild(highScoreTxt);
LK.gui.center.addChild(scoreTxt);
LK.gui.top.addChild(levelTxt);
LK.gui.top.addChild(progressTxt);
// Touch/drag handling for paddle movement
var isDragging = false;
game.down = function (x, y, obj) {
// Play click sound with combined volume settings
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('click').play({
volume: masterVolume * sfxVolume
});
isDragging = true;
paddle.x = x;
// Emit more particles at mouse/touch position for improved visibility
mouseParticles.emitAt(x, y, 35);
};
game.move = function (x, y, obj) {
if (isDragging) {
paddle.x = x;
// Clamp paddle position to keep it within screen bounds
if (paddle.x < paddle.width / 2) {
paddle.x = paddle.width / 2;
} else if (paddle.x > 2048 - paddle.width / 2) {
paddle.x = 2048 - paddle.width / 2;
}
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Function to update score display
function updateScore() {
scoreTxt.setText(score.toString());
// Update progress to next level
var progress = score % levelRequirement;
var remaining = levelRequirement - progress;
progressTxt.setText(progress + '/' + levelRequirement);
// Check for level completion
if (progress === 0 && score > 0) {
// Level up!
level = Math.floor(score / levelRequirement) + 1;
levelTxt.setText('Level: ' + level);
// Celebrate level completion
celebrateNewLevel();
// Increase difficulty with each level
difficulty = 1 + (level - 1) * 0.3;
if (difficulty > 3) {
difficulty = 3;
}
// Update game properties based on new difficulty
ball.gravity = 0.2 * difficulty;
ball.maxSpeed = 15 * difficulty;
}
// Update high score if current score is higher
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
}
// Function to celebrate new level
function celebrateNewLevel() {
// Play level up sound with combined volume settings
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('levelup').play({
volume: masterVolume * sfxVolume
});
// Show celebratory text
var celebrationText = new Text2('LEVEL ' + level + '!', {
size: 150,
fill: 0xFFFF00
});
celebrationText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(celebrationText);
// Make text appear with scale animation
celebrationText.scale.set(0.1);
tween(celebrationText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(celebrationText, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
onFinish: function onFinish() {
// Fade out after a moment
LK.setTimeout(function () {
tween(celebrationText, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.gui.center.removeChild(celebrationText);
}
});
}, 1000);
}
});
}
});
// Create confetti bursts
for (var i = 0; i < 5; i++) {
LK.setTimeout(function () {
// Create multiple confetti bursts across screen
confetti.emit(Math.random() * 2048, 500, 30);
}, i * 200);
}
// Flash screen for celebration
LK.effects.flashScreen(0x00FFFF, 300);
// Sounds and music removed
}
// Function to increase difficulty over time
function increaseDifficulty() {
difficulty += 0.1;
// Cap maximum difficulty
if (difficulty > 2) {
difficulty = 2;
LK.clearInterval(difficultyIncreaseTimer);
}
// Update ball properties based on difficulty
ball.gravity = 0.1 * difficulty;
ball.maxSpeed = 10 * difficulty;
// Music adjustment removed
}
// Start difficulty increase timer
difficultyIncreaseTimer = LK.setInterval(increaseDifficulty, 10000);
// Music will be played when game starts
// Not playing background music initially to allow menu to be silent
// Game update loop
game.update = function () {
// If game is not active or UI panels are active, don't update game
if (!gameActive || settingsPanelActive || confirmDialogActive) {
return;
}
// Update ball position
ball.update();
// Update lava waves
lava.update();
// Check for collision with paddle
if (ball.speedY > 0 && ball.y + ball.height / 2 >= paddle.y - paddle.height / 2 && ball.y - ball.height / 2 <= paddle.y + paddle.height / 2 && ball.x + ball.width / 2 >= paddle.x - paddle.width / 2 && ball.x - ball.width / 2 <= paddle.x + paddle.width / 2) {
// Bounce the ball in a random direction when it hits the paddle
var angle = Math.random() * Math.PI * 0.7 - Math.PI * 0.35; // Random angle between -35 and +35 degrees
var speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY) * 1.5; // Keep overall speed but increase by 1.5x
ball.speedY = -Math.cos(angle) * speed; // Vertical component (mostly upward)
ball.speedX = Math.sin(angle) * speed; // Horizontal component
// Add additional horizontal velocity based on where the ball hit the paddle
var hitPosition = (ball.x - paddle.x) / (paddle.width / 2);
ball.speedX += hitPosition * 5; // Add some influence from hit position, but less than before
// Flash paddle to indicate hit
LK.effects.flashObject(paddle, 0xffffff, 200);
// Emit particles at collision point
particles.emitAt(ball.x, paddle.y - paddle.height / 2, 0xffbd00, 15);
// Add scale animation to paddle for bounce feedback
tween(paddle, {
scaleY: 0.7,
y: paddle.y + 10
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(paddle, {
scaleY: 1,
y: 2732 - 300
}, {
duration: 150,
easing: tween.elasticOut
});
}
});
// Play bounce sound when ball hits paddle
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('bounce').play({
volume: masterVolume * sfxVolume
});
// Increment score
score++;
updateScore();
}
// Check if ball touches lava
if (ball.y + ball.height / 2 >= lava.y - lava.height / 2) {
// Play lava sound with combined volume settings
var masterVolume = storage.volume !== undefined ? storage.volume : 1.0;
var sfxVolume = storage.sfxVolume !== undefined ? storage.sfxVolume : 1.0;
LK.getSound('lava').play({
volume: masterVolume * sfxVolume
});
LK.getSound('gameover').play({
volume: masterVolume * sfxVolume
});
// Flash screen red
LK.effects.flashScreen(0xff0000, 500);
// Create lava splash effect
particles.emitAt(ball.x, lava.y - lava.height / 2, 0xff3300, 30);
// Stop any current lava animations
tween.stop(lava, {
y: true
});
// Make lava "jump" without moving up or down
tween(lava, {
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lava, {
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
// Just reset the game without returning to menu
// Reset score and level
score = 0;
level = 1;
levelTxt.setText('Level: ' + level);
progressTxt.setText('0/' + levelRequirement);
updateScore();
// Reset ball
ball.reset();
// Reset difficulty
difficulty = 1;
ball.gravity = 0.2;
ball.maxSpeed = 15;
// Restart difficulty increase timer
LK.clearInterval(difficultyIncreaseTimer);
difficultyIncreaseTimer = LK.setInterval(increaseDifficulty, 10000);
// Music removed
}
// If ball goes too high, change direction
if (ball.y < 100) {
ball.speedY = Math.abs(ball.speedY) * 0.5;
}
// Update particles
particles.update();
// Update confetti
confetti.update();
// Update mouse particles
mouseParticles.update();
// Update wall particles
wallParticles.update();
};