/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var Ball = Container.expand(function () { var self = Container.call(this); var ballGraphic = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.speedX = 0; self.speedY = 0; self.active = true; self.trailCounter = 0; self.gravity = 0.18; // Further reduced gravity for slower falling self.airResistance = 0.995; // Slightly increased air resistance for slower movement self.bounciness = 0.92; // Lower bounciness for less energetic bounces self.spin = 0; // Ball spin (affects horizontal movement) self.lastHitPos = 0; // Last hit position on paddle (for spin calculation) self.reset = function (speedMultiplier) { // Set position to center of screen self.x = 2048 / 2; self.y = 2732 / 2; // Generate a random angle between 0 and 2π (full 360 degrees) var randomAngle = Math.random() * Math.PI * 2; // Set initial speed magnitude var speedMagnitude = 15 * speedMultiplier; // Calculate velocity components based on random angle self.speedX = Math.cos(randomAngle) * speedMagnitude; self.speedY = Math.sin(randomAngle) * speedMagnitude; self.active = true; self.spin = 0; // Reset spin self.lastHitPos = 0; // Reset last hit position }; self.update = function () { if (!self.active) { return; } // Store last positions for collision detection var lastX = self.x; var lastY = self.y; // Apply gravity - increases vertical speed over time self.speedY += self.gravity; // Apply air resistance self.speedX *= self.airResistance; self.speedY *= self.airResistance; // Apply spin effect to horizontal movement self.speedX += self.spin * 0.1; // Gradually reduce spin over time self.spin *= 0.98; // Apply velocity self.x += self.speedX; self.y += self.speedY; // Add trail effect based on speed self.trailCounter++; // Adjust trail frequency based on speed - faster speed = more frequent trails var trailFrequency = Math.max(1, 5 - Math.floor((Math.abs(self.speedX) + Math.abs(self.speedY)) / 10)); // Create more trails at higher speeds if (self.trailCounter > trailFrequency) { self.trailCounter = 0; var trail = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: lastX, y: lastY }); // Adjust trail size based on ball speed var speedFactor = Math.min(1, (Math.abs(self.speedX) + Math.abs(self.speedY)) / 40); trail.scale.set(self.scale.x * (0.7 - speedFactor * 0.2), self.scale.y * (0.7 - speedFactor * 0.2)); trail.tint = gameColors.ball; // Higher alpha for faster speeds trail.alpha = 0.5 + speedFactor * 0.3; game.addChildAt(trail, game.getChildIndex(self)); // Fade out and remove trail - faster trails disappear quicker var trailDuration = 300 - speedFactor * 150; tween(trail, { alpha: 0, scaleX: trail.scale.x * 0.5, scaleY: trail.scale.y * 0.5 }, { duration: trailDuration, onFinish: function onFinish() { trail.destroy(); } }); } // Rotate the ball based on horizontal speed to show rolling effect ballGraphic.rotation += self.speedX * 0.05; // Bounce off sides with more random physics if (self.x < 20 || self.x > 2028) { // Create particles at wall collision point createCollisionParticles(self.x, self.y); // Flip the horizontal direction self.speedX = -self.speedX * self.bounciness * 1.2; // Add significant random variation to both speed components self.speedX += Math.random() * 6 - 3; // Major randomness on x-axis self.speedY += Math.random() * 4 - 2; // Add randomness to y-axis on wall bounce too // Chance for a very wild bounce (20% chance) if (Math.random() < 0.2) { self.speedX *= 0.5 + Math.random(); self.speedY *= 0.5 + Math.random(); } // Keep the ball within the game boundaries self.x = Math.max(20, Math.min(2028, self.x)); // Play bounce sound if soundEnabled is true if (soundEnabled) { LK.getSound('bounce').play(); } } // Check if ball hits the top of the screen with more random bounces if (self.y < 20) { // Create particles at ceiling collision point createCollisionParticles(self.x, self.y); // Flip the vertical direction self.speedY = -self.speedY * self.bounciness * 1.25; // Add significant random variation to both speed components self.speedX += Math.random() * 5 - 2.5; // Add randomness to x-axis on ceiling bounce self.speedY += Math.random() * 5 - 2.5; // Major randomness on y-axis // Chance for a very wild bounce (20% chance) if (Math.random() < 0.2) { // Completely random direction after ceiling hit var randomAngle = Math.random() * Math.PI + Math.PI; // Angle in bottom half var currentSpeed = Math.sqrt(self.speedX * self.speedX + self.speedY * self.speedY); self.speedX = Math.cos(randomAngle) * currentSpeed; self.speedY = Math.sin(randomAngle) * currentSpeed; } self.y = 20; // Play bounce sound if soundEnabled is true if (soundEnabled) { LK.getSound('bounce').play(); } } }; return self; }); var DiagonalStripe = Container.expand(function () { var self = Container.call(this); // Create a shape for the diagonal stripe var stripeGraphic = self.attachAsset('background', { anchorX: 0, anchorY: 0 }); // Configure the stripe appearance stripeGraphic.width = 3000; // Increased width to extend past screen edges stripeGraphic.height = 100; stripeGraphic.tint = 0xffffff; // White // Initial position and rotation stripeGraphic.rotation = Math.PI / 4; // 45 degrees in radians // Position it to extend past screen edges stripeGraphic.x = -500; // Start before the left edge // Empty update method (stripe will be still) self.update = function () { // No animation - stripe remains still }; return self; }); var Paddle = Container.expand(function () { var self = Container.call(this); // Create the main paddle base - middle rectangle section var paddleGraphic = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); paddleGraphic.tint = 0xFFB612; // Set paddle color to #FFB612 // Create the left rounded end (circle shape) var leftEnd = LK.getAsset('ball', { // Using the ball asset as it's a circle anchorX: 0.5, anchorY: 0.5, width: paddleGraphic.height, height: paddleGraphic.height, tint: 0xFFB612 }); leftEnd.x = -paddleGraphic.width / 2 + leftEnd.width / 2; self.addChild(leftEnd); // Create the right rounded end (circle shape) var rightEnd = LK.getAsset('ball', { // Using the ball asset as it's a circle anchorX: 0.5, anchorY: 0.5, width: paddleGraphic.height, height: paddleGraphic.height, tint: 0xFFB612 }); rightEnd.x = paddleGraphic.width / 2 - rightEnd.width / 2; // Make sure the right end is the correct color rightEnd.tint = 0xFFB612; self.addChild(rightEnd); // Trim the main paddle to accommodate rounded ends paddleGraphic.width = paddleGraphic.width - paddleGraphic.height; self.width = paddleGraphic.width + paddleGraphic.height; // Total width includes the circles self.height = paddleGraphic.height; // Add paddle tracking flag and double click detection self.ballFollowing = false; self.lastClickTime = 0; self.down = function (x, y, obj) { var currentTime = Date.now(); // Check for double click (within 300ms) if (currentTime - self.lastClickTime < 300) { // Toggle ball following state self.ballFollowing = !self.ballFollowing; // Create visual feedback var effect = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: self.ballFollowing ? 0x00ff00 : 0xff0000 }); effect.scale.set(2, 2); effect.alpha = 0.5; game.addChild(effect); // Animate and remove the effect tween(effect, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 300, onFinish: function onFinish() { effect.destroy(); } }); // Play click sound for feedback if (soundEnabled) { LK.getSound('click').play(); } } self.lastClickTime = currentTime; }; self.update = function () { // Keep paddle within screen bounds self.x = Math.max(self.width / 2, Math.min(2048 - self.width / 2, self.x)); }; return self; }); var Particle = Container.expand(function () { var self = Container.call(this); var particleGraphic = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); // Initial size scale self.scale.set(0.5, 0.5); // Start with smaller particles // Particle colors var colors = [0xFFB612, 0xC60C30, 0x003087, 0xFFFFFF]; particleGraphic.tint = colors[Math.floor(Math.random() * colors.length)]; // Random speed and direction var angle = Math.random() * Math.PI * 2; var speed = 2 + Math.random() * 8; self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; // Random rotation self.rotationSpeed = (Math.random() - 0.5) * 0.2; // Particle lifespan tracking self.lifetime = 0; self.maxLifetime = 30 + Math.random() * 30; // Update function self.update = function () { // Update position with reduced movement (smaller radius) self.x += self.vx * 0.5; // Reduce horizontal movement by 50% self.vy += 0.1; // Gravity effect self.y += self.vy * 0.5; // Reduce vertical movement by 50% // Rotate particle particleGraphic.rotation += self.rotationSpeed; // Update lifetime self.lifetime++; // Fade out based on lifetime if (self.lifetime > self.maxLifetime * 0.7) { self.alpha = 1 - (self.lifetime - self.maxLifetime * 0.7) / (self.maxLifetime * 0.3); } // Remove when lifetime is over if (self.lifetime >= self.maxLifetime) { self.active = false; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xffffff // Light sky blue for a calmer atmosphere }); /**** * Game Code ****/ // Game state management var GAME_STATE = { MENU: 0, PLAYING: 1, SETTINGS: 2 }; var currentState = GAME_STATE.MENU; // Default colors for game elements - softer, more calming colors var gameColors = { paddle: 0xffb612, // Changed to requested color #ffb612 ball: 0xFFB612, // Changed to requested color #FFB612 lava: 0xC60C30 // Changed to requested color #C60C30 }; // Color saving/loading disabled // Make game accessible to other functions var gameInstance = game; // Game variables var background; var paddle; var lava; var balls = []; var particles = []; // Array to track active particles var score = 0; var highScore = storage.highScore || 0; var level = 1; var combo = 0; var lastBallHit = 0; var gameActive = false; var speedMultiplier = 1.0; var maxBalls = 1; var ballsInPlay = 0; var spawnInterval; var hitsToNextLevel = 25; var currentHits = 0; var hitCounterText; // UI elements var scoreTxt; var levelTxt; var comboTxt; var highScoreTxt; var speedTxt; // Default sound settings var soundEnabled = true; var musicEnabled = true; // Load stored sound settings var soundSetting = storage.soundEnabled; if (soundSetting !== undefined) { soundEnabled = soundSetting; } // Initialize game elements (called when starting game) function initializeGameElements() { if (!background) { // Create background background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(background); // Initialize lava lava = LK.getAsset('lava', { anchorX: 0.5, anchorY: 0, x: 2048 / 2, y: 2732 - 200 }); lava.tint = gameColors.lava; lava.alpha = 0.8; // Add transparency to lava lava.lavaEffects = null; // Will be populated in update method game.addChild(lava); // Initialize paddle paddle = new Paddle(); paddle.x = 2048 / 2; paddle.y = 2732 - 250; paddle.getChildAt(0).tint = 0xffb612; game.addChild(paddle); // Diagonal stripe removed from here and placed in menu // Create hit counter text scoreTxt = new Text2('0', { size: 180, fill: 0x101820 }); scoreTxt.anchor.set(0.5, 0.5); // Position score text precisely in the center of the screen scoreTxt.x = 2048 / 2; // True center horizontally scoreTxt.y = 2732 / 2; // True center vertically game.addChild(scoreTxt); // Add Glaud text in the upper right corner var glaudText = new Text2('Glaud', { size: 60, fill: 0xFF8C00 // Orange color }); glaudText.anchor.set(1, 0); // Anchor to top right glaudText.x = 2000; // Position near right edge glaudText.y = 20; // Position near top game.addChild(glaudText); // Create level text levelTxt = new Text2('Level: 1', { size: 70, fill: 0xFFFFFF }); levelTxt.anchor.set(1, 0); levelTxt.x = 2000; levelTxt.y = 50; LK.gui.addChild(levelTxt); // Create combo text comboTxt = new Text2('', { size: 60, fill: 0xFFFFFF }); comboTxt.anchor.set(0.5, 0); comboTxt.x = 1024; comboTxt.y = 50; comboTxt.alpha = 0; LK.gui.addChild(comboTxt); // Create high score text highScoreTxt = new Text2('High Score: ' + highScore, { size: 60, fill: 0xffb612 }); highScoreTxt.anchor.set(1, 0); highScoreTxt.x = 2000; highScoreTxt.y = 130; // Position below hit counter game.addChild(highScoreTxt); // Add directly to game to ensure visibility // Create speed indicator text speedTxt = new Text2('Speed: x' + speedMultiplier.toFixed(1), { size: 60, fill: 0xffb612 }); speedTxt.anchor.set(0, 0); speedTxt.x = 48; speedTxt.y = 50; game.addChild(speedTxt); // Create hit counter text hitCounterText = new Text2(currentHits + '/25', { size: 70, fill: 0x003087 }); hitCounterText.anchor.set(0.5, 0); hitCounterText.x = 1024; hitCounterText.y = 150; // More visible position at top of screen game.addChild(hitCounterText); // Add directly to game to ensure visibility } // Show game elements background.visible = true; lava.visible = true; paddle.visible = true; scoreTxt.visible = true; levelTxt.visible = true; comboTxt.visible = true; highScoreTxt.visible = true; hitCounterText.visible = true; } // Create menu elements var titleText; var startButton; var settingsButton; var settingsPanel; var menuBackground; // Initialize menu initializeMenu(); function initializeMenu() { // Play menu music if enabled if (musicEnabled) { LK.playMusic('menuMusic', { fade: { start: 0, end: 0.6, duration: 1500 } }); } // Create menu background menuBackground = new Container(); var menuBg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: 0xFFFFFF // White color for menu background }); // Create a border by adding a slightly larger background behind it var menuBorder = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: 0xA5ACAF // Border color set to #A5ACAF }); menuBorder.width = 2048 + 10 * 2; // Adding 10px on each side menuBorder.height = 2732 + 10 * 2; // Adding 10px on each side menuBorder.x = -10; // Position it 10px to the left menuBorder.y = -10; // Position it 10px to the top menuBackground.addChild(menuBorder); menuBackground.addChild(menuBg); game.addChild(menuBackground); // Diagonal stripe removed from menu // Create game title titleText = new Text2('Lava Bounce', { size: 150, fill: 0x101820 }); titleText.anchor.set(0.5, 0); titleText.x = 1024; titleText.y = 200; game.addChild(titleText); // Animate the title to rotate back and forth function animateTitleRotation() { // Rotate to one side tween(titleText, { rotation: 0.1 // Slight rotation to the right (in radians) }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { // Rotate to the other side tween(titleText, { rotation: -0.1 // Slight rotation to the left (in radians) }, { duration: 1000, easing: tween.easeInOut, onFinish: animateTitleRotation // Loop the animation }); } }); } // Start the title rotation animation animateTitleRotation(); // Create the "2" as a separate, larger text element var titleNumber = new Text2('2', { size: 200, // Bigger than the main title text fill: 0xFFB612 // Gold color }); titleNumber.anchor.set(0.5, 0); titleNumber.x = 1024; // Centered horizontally titleNumber.y = 350; // Positioned below the main title game.addChild(titleNumber); // Animate the "2" with a continuous bounce effect function animateTitle2() { // Bounce up animation tween(titleNumber, { y: 320, // Move up slightly scaleX: 1.1, scaleY: 1.1 }, { duration: 700, onFinish: function onFinish() { // Bounce down animation tween(titleNumber, { y: 350, // Back to original position scaleX: 1, scaleY: 1 }, { duration: 700, onFinish: animateTitle2 // Loop the animation }); } }); } // Start the animation animateTitle2(); // Create start button startButton = new Text2('Start Game', { size: 90, fill: 0xFFB612 }); startButton.anchor.set(0.5, 0.5); startButton.x = 1024; startButton.y = 1200; startButton.interactive = true; startButton.isHovered = false; // Track hover state startButton.move = function (x, y, obj) { // Check if cursor is over the button if (x >= startButton.x - startButton.width / 2 && x <= startButton.x + startButton.width / 2 && y >= startButton.y - startButton.height / 2 && y <= startButton.y + startButton.height / 2) { // Start animation only if not already hovering if (!startButton.isHovered) { startButton.isHovered = true; // Apply a slight scale increase when hovered tween(startButton, { scaleX: 1.05, scaleY: 1.05 }, { duration: 200, easing: tween.easeOut }); animateStartButton(); } } else { // Stop animation when cursor moves away if (startButton.isHovered) { startButton.isHovered = false; tween.stop(startButton, { rotation: true }); tween(startButton, { rotation: 0, scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } } }; // Function to animate the start button function animateStartButton() { if (!startButton.isHovered) { return; } // Rotate to one side tween(startButton, { rotation: 0.08 // Slight rotation (in radians) }, { duration: 50, // Very quick rotation for more responsive feel easing: tween.easeInOut, onFinish: function onFinish() { if (!startButton.isHovered) { startButton.rotation = 0; return; } // Rotate to the other side tween(startButton, { rotation: -0.08 // Slight rotation in opposite direction }, { duration: 50, // Very quick rotation for more responsive feel easing: tween.easeInOut, onFinish: animateStartButton // Continue the animation }); } }); } game.addChild(startButton); // Create settings button settingsButton = new Text2('Settings', { size: 90, fill: 0x101820 }); settingsButton.anchor.set(0.5, 0.5); settingsButton.x = 1024; settingsButton.y = 1400; settingsButton.interactive = true; game.addChild(settingsButton); // Set up event handlers for menu startButton.down = function () { if (soundEnabled) { LK.getSound('click').play(); } // Stop menu music with fade out if (musicEnabled) { LK.playMusic('menuMusic', { fade: { start: 0.6, end: 0, duration: 500 } }); } hideMenu(); initializeGameElements(); startGame(); }; settingsButton.down = function () { if (soundEnabled) { LK.getSound('click').play(); } hideMenu(); showSettings(); }; } function hideMenu() { if (menuBackground) { menuBackground.visible = false; } if (titleText) { titleText.visible = false; } if (startButton) { startButton.visible = false; } if (settingsButton) { settingsButton.visible = false; } // Make sure high score is visible in game if (highScoreTxt) { highScoreTxt.visible = true; } } function showMenu() { currentState = GAME_STATE.MENU; if (menuBackground) { menuBackground.visible = true; } if (titleText) { titleText.visible = true; } if (startButton) { startButton.visible = true; } if (settingsButton) { settingsButton.visible = true; } if (settingsPanel) { settingsPanel.visible = false; } // Play menu music when returning to menu from game over if (musicEnabled && currentState === GAME_STATE.MENU) { LK.playMusic('menuMusic', { fade: { start: 0, end: 0.6, duration: 1000 } }); } } function showSettings() { currentState = GAME_STATE.SETTINGS; // Create settings panel if it doesn't exist if (!settingsPanel) { settingsPanel = new Container(); game.addChild(settingsPanel); // Settings panel background without border var panelContainer = new Container(); panelContainer.x = 1024; panelContainer.y = 1366; // Set initial scale to zero for animation panelContainer.scale.x = 0; panelContainer.scale.y = 0; // Inner panel background (without blue border) var panelBg = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); panelBg.width = 1600; panelBg.height = 1000; panelBg.tint = 0x101820; // Dark blue/black color for settings panel panelContainer.addChild(panelBg); settingsPanel.addChild(panelContainer); // Settings title var settingsTitle = new Text2('Settings', { size: 80, fill: 0xFFFFFF }); settingsTitle.anchor.set(0.5, 0); settingsTitle.x = 1024; settingsTitle.y = 500; settingsPanel.addChild(settingsTitle); // Color selectors have been removed // Game description text var gameDescription = new Text2('Lava Bounce 2: A frantic paddle game where you\ncontrol a golden paddle to bounce balls away from\ndeadly red lava. Score points with each bounce,\nlevel up for increased speed and challenge.\nCan you set a new high score record?', { size: 50, fill: 0xFFB612 }); gameDescription.anchor.set(0.5, 0.5); gameDescription.x = 1024; // True center horizontally gameDescription.y = 700; settingsPanel.addChild(gameDescription); // High score display in settings var highScoreDisplay = new Text2('High Score: ' + highScore, { size: 60, fill: 0xFFFFFF }); highScoreDisplay.anchor.set(0, 0.5); highScoreDisplay.x = 400; highScoreDisplay.y = 970; // Moved down a bit settingsPanel.addChild(highScoreDisplay); // Reset high score button var resetButton = new Text2('Reset High Score', { size: 60, fill: 0xFF6B6B }); resetButton.anchor.set(0.5, 0.5); resetButton.x = 1024; // True center horizontally resetButton.y = 1366; // True center vertically resetButton.interactive = true; resetButton.down = function () { if (soundEnabled) { LK.getSound('click').play(); } showResetConfirmation(); }; settingsPanel.addChild(resetButton); // Back to menu button var backButton = new Text2('Back to Menu', { size: 60, fill: 0xFFFFFF }); backButton.anchor.set(0.5, 0.5); backButton.x = 1024; backButton.y = 1100; backButton.interactive = true; backButton.down = function () { if (soundEnabled) { LK.getSound('click').play(); } // Animate settings panel closing tween(settingsPanel.getChildAt(0), { scaleX: 0, scaleY: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { settingsPanel.visible = false; showMenu(); // Play menu music when returning to menu if (musicEnabled) { LK.playMusic('menuMusic', { fade: { start: 0, end: 0.6, duration: 1000 } }); } } }); }; settingsPanel.addChild(backButton); } else { settingsPanel.visible = true; // Reset scale for animation if panel already exists settingsPanel.getChildAt(0).scale.x = 0; settingsPanel.getChildAt(0).scale.y = 0; } // Animate the settings panel opening with bounce effect tween(settingsPanel.getChildAt(0), { scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.elasticOut }); // Color buttons function has been removed } // Function to create particles at click location function createClickParticles(x, y) { // Create 15-20 particles for a visually impressive effect var particleCount = 15 + Math.floor(Math.random() * 6); for (var i = 0; i < particleCount; i++) { var particle = new Particle(); particle.x = x; particle.y = y; particle.active = true; // Add to particles array and game display particles.push(particle); game.addChild(particle); } } // Function to create particles at collision location function createCollisionParticles(x, y) { // Create 8-12 particles for collision effect - fewer than click particles var particleCount = 8 + Math.floor(Math.random() * 5); for (var i = 0; i < particleCount; i++) { var particle = new Particle(); particle.x = x; particle.y = y; particle.active = true; particle.scale.set(0.3, 0.3); // Smaller particles for collisions // Add to particles array and game display particles.push(particle); game.addChild(particle); } } // Initialize balls array function createBall() { if (ballsInPlay >= maxBalls || !gameActive) { return; } var ball = new Ball(); ball.reset(speedMultiplier); ball.getChildAt(0).tint = gameColors.ball; // Visual indicator of speed - make ball slightly smaller as it gets faster var scale = Math.max(0.6, 1 - (speedMultiplier - 1) * 0.15); ball.scale.set(scale, scale); balls.push(ball); game.addChild(ball); ballsInPlay++; } // Handle input events based on current state game.down = function (x, y, obj) { // Play click sound whenever cursor is clicked if (soundEnabled) { LK.getSound('click').play(); } // Create particles at click location createClickParticles(x, y); if (currentState === GAME_STATE.PLAYING) { // Check if click is within paddle bounds if (x >= paddle.x - paddle.width / 2 && x <= paddle.x + paddle.width / 2 && y >= paddle.y - paddle.height / 2 && y <= paddle.y + paddle.height / 2) { // Call paddle's down handler for double-click detection paddle.down(x, y, obj); } else if (!paddle.ballFollowing) { // Only move paddle if not in ball following mode paddle.x = x; } } // Check if we hit any interactive elements if (obj && obj.event && obj.event.target && obj.event.target.down) { obj.event.target.down(x, y, obj); } }; game.move = function (x, y, obj) { if (currentState === GAME_STATE.PLAYING) { // Only move paddle with mouse if not in ball following mode if (!paddle.ballFollowing) { paddle.x = x; } } }; // Update function game.update = function () { // Update any diagonal stripes in the game (always update even when not playing) for (var i = 0; i < game.children.length; i++) { if (game.children[i] instanceof DiagonalStripe) { game.children[i].update(); } } // Update all particles for (var i = particles.length - 1; i >= 0; i--) { var particle = particles[i]; if (particle.active === false) { particle.destroy(); particles.splice(i, 1); continue; } particle.update(); } // Check if in playing state if (currentState !== GAME_STATE.PLAYING) { return; } // Game play state if (!gameActive) { return; } // Animate lava flow with flowing colors if (lava) { // Create flowing lava effect using color transitions if (!lava.lavaEffects) { lava.lavaEffects = []; // Create multiple flowing wave effects for (var i = 0; i < 5; i++) { var wave = LK.getAsset('lava', { anchorX: 0.5, anchorY: 0, x: 2048 / 2, y: lava.y + i * 5 - 10, tint: i % 2 === 0 ? 0xff8800 : 0xc60c30 // Alternate yellow and red }); wave.alpha = 0.5; wave.height = 60; lava.lavaEffects.push(wave); game.addChildAt(wave, game.getChildIndex(lava)); // Start flow animation for each wave animateLavaFlow(wave, i); } } // Create occasional particles on lava surface for extra effect if (LK.ticks % 10 == 0) { var particleX = Math.random() * 2048; var particleY = lava.y + Math.random() * 10; var lavaParticle = new Particle(); lavaParticle.x = particleX; lavaParticle.y = particleY; lavaParticle.scale.set(0.2, 0.2); // Small particles lavaParticle.active = true; // Make particles match lava color scheme lavaParticle.getChildAt(0).tint = Math.random() > 0.5 ? 0xff8800 : 0xc60c30; // Add to particles array and game display particles.push(lavaParticle); game.addChild(lavaParticle); } } // Update speed indicator if it exists if (speedTxt) { speedTxt.setText('Speed: x' + speedMultiplier.toFixed(1)); } // Only create a ball if none exists if (ballsInPlay === 0) { createBall(); } // Update paddle and track position for physics calculations game.lastPaddleX = paddle.x; // Store current position for next frame paddle.update(); // Update all balls for (var i = balls.length - 1; i >= 0; i--) { var ball = balls[i]; if (!ball.active) { continue; } // Make ball follow paddle quickly if ball following is enabled if (paddle.ballFollowing) { // Calculate direction vector to paddle var dirX = paddle.x - ball.x; var dirY = paddle.y - 50 - ball.y; // Position ball slightly above paddle // Normalize and scale by desired speed (much faster than normal) var length = Math.sqrt(dirX * dirX + dirY * dirY); if (length > 5) { // Only move if not already very close ball.speedX = dirX / length * 30; // Fast following speed ball.speedY = dirY / length * 30; } else { // If close enough, hover above paddle ball.speedX *= 0.8; ball.speedY *= 0.8; } } ball.update(); // Check if ball hits paddle with improved collision detection if (ball.speedY > 0 && ball.y + 20 >= paddle.y - paddle.height / 2 && ball.y - 20 <= paddle.y + paddle.height / 2 && ball.x + 20 >= paddle.x - paddle.width / 2 && ball.x - 20 <= paddle.x + paddle.width / 2) { // Create particles at collision point createCollisionParticles(ball.x, ball.y); // Calculate hit position from -1 (left edge) to 1 (right edge) var hitPos = (ball.x - paddle.x) / (paddle.width / 2); // Calculate spin based on the difference between current and last hit position // This simulates the effect of a moving paddle hitting the ball var paddleMovementEffect = 0; if (game.lastPaddleX !== undefined) { paddleMovementEffect = (paddle.x - game.lastPaddleX) * 0.1; } // Apply reduced spin based on hit position and paddle movement ball.spin = hitPos * 0.3 + paddleMovementEffect * 0.6; // Reduced spin effect ball.lastHitPos = hitPos; // Calculate angle based on where the ball hits the paddle with more randomness // Wider angle range - full 180 degrees (0 to 180) instead of 120 degrees var angle = Math.PI * Math.random() + Math.PI / 2; // Random angle in upper half (90 to 270 degrees) // Add influence from hit position angle = angle * 0.7 + (Math.PI / 3 * hitPos + Math.PI / 2) * 0.3; // Calculate current speed with an adjustment var currentSpeed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY); var speed = Math.max(currentSpeed * 1.25, 12 * speedMultiplier); // Adjust ball direction with more random bounce physics ball.speedX = Math.cos(angle) * speed + paddleMovementEffect * 1.5; ball.speedY = -Math.sin(angle) * speed * 1.4; // Increased vertical multiplier for higher bounce // Add larger random variations for more unpredictable bouncing ball.speedX += (Math.random() * 4 - 2) * speedMultiplier; // Increased randomness ball.speedY += (Math.random() * 4 - 2) * speedMultiplier; // Increased randomness // Create a bounce effect with the paddle - make it visually respond to the hit tween(paddle, { scaleY: 0.85, y: paddle.y + 5 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(paddle, { scaleY: 1.0, y: paddle.y - 5 }, { duration: 100, easing: tween.elasticOut }); } }); // Move ball above paddle to prevent multiple collisions ball.y = paddle.y - paddle.height / 2 - 20; // Add a visual impact effect var impactEffect = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: ball.x, y: ball.y + 10, tint: 0xFFFFFF }); impactEffect.scale.set(1.5, 0.5); impactEffect.alpha = 0.7; game.addChild(impactEffect); // Animate and remove the impact effect tween(impactEffect, { alpha: 0, scaleX: 2.5, scaleY: 0.2 }, { duration: 200, onFinish: function onFinish() { impactEffect.destroy(); } }); // Simple scoring - always add 1 point score += 1; // Hide combo text comboTxt.alpha = 0; // Update hit counter scoreTxt.setText('' + score); // Center the score text scoreTxt.x = 2048 / 2; // True center horizontally scoreTxt.y = 2732 / 2; // True center vertically // Make it fade and gently grow when updated tween(scoreTxt, { scaleX: 1.1, // Smaller scale change scaleY: 1.1, // Smaller scale change alpha: 0.8 // Slight fade out }, { duration: 300, // Longer duration for smoother animation easing: tween.easeInOut, // Smoother easing onFinish: function onFinish() { tween(scoreTxt, { scaleX: 1, scaleY: 1, alpha: 1 // Back to full opacity }, { duration: 300, // Longer duration easing: tween.easeInOut // Smoother easing }); } }); LK.setScore(score); // Play bounce sound if enabled if (soundEnabled) { LK.getSound('bounce').play(); } // Update hit counter for level system currentHits++; hitCounterText.setText(currentHits + '/' + hitsToNextLevel); // Level up based on hits if (currentHits >= hitsToNextLevel) { currentHits = 0; levelUp(); hitCounterText.setText('Hits to Next Level: ' + hitsToNextLevel); // Animate hit counter text tween(hitCounterText, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(hitCounterText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300, easing: tween.easeInOut }); } }); } } // Check if ball falls into lava if (ball.y > lava.y) { // Play lava sound if enabled if (soundEnabled) { LK.getSound('lava').play(); } // Flash the lava tween(lava, { tint: 0xffffff }, { duration: 200, onFinish: function onFinish() { tween(lava, { tint: 0xe74c3c }, { duration: 200 }); } }); // Remove ball ball.active = false; ball.destroy(); balls.splice(i, 1); ballsInPlay--; // Check game over if (balls.length === 0 && ballsInPlay === 0) { gameOver(); } } } }; // Function to animate lava flow waves function animateLavaFlow(wave, index) { // Starting position slightly off screen to the left wave.x = -200; // Set color based on index to ensure even distribution of yellow and red wave.tint = index % 2 === 0 ? 0xff8800 : 0xc60c30; // Animate wave flowing from left to right tween(wave, { x: 2048 + 200 // Move to right side of screen and beyond }, { duration: 6000 + index * 1000, // Different speeds for different waves onFinish: function onFinish() { // Maintain the same color based on index for consistent distribution // (no toggling colors anymore to keep distribution even) // Reset position and restart animation wave.x = -200; animateLavaFlow(wave, index); } }); } // Reset confirmation popup function showResetConfirmation() { // Create popup container var popup = new Container(); game.addChild(popup); // Popup background var popupBg = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); popupBg.width = 1200; popupBg.height = 700; popupBg.tint = 0x101820; popup.addChild(popupBg); // Border for popup var popupBorder = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); popupBorder.width = 1210; popupBorder.height = 710; popupBorder.tint = 0xFFB612; popup.addChildAt(popupBorder, 0); // Confirmation text var confirmText = new Text2('Reset High Score?', { size: 70, fill: 0xFFFFFF }); confirmText.anchor.set(0.5, 0); confirmText.x = 1024; confirmText.y = 1200; popup.addChild(confirmText); // Yes button var yesButton = new Text2('Yes', { size: 60, fill: 0xFF6B6B }); yesButton.anchor.set(0.5, 0.5); yesButton.x = 824; yesButton.y = 1450; yesButton.interactive = true; yesButton.down = function () { if (soundEnabled) { LK.getSound('click').play(); } // Reset high score highScore = 0; storage.highScore = 0; // Update high score display if (highScoreTxt) { highScoreTxt.setText('High Score: 0'); } // Update settings display settingsPanel.children.forEach(function (child) { if (child instanceof Text2 && child.text && child.text.startsWith('High Score:')) { child.setText('High Score: 0'); } }); // Remove popup with animation tween(popup, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, onFinish: function onFinish() { popup.destroy(); } }); }; popup.addChild(yesButton); // No button var noButton = new Text2('No', { size: 60, fill: 0xFFFFFF }); noButton.anchor.set(0.5, 0.5); noButton.x = 1224; noButton.y = 1450; noButton.interactive = true; noButton.down = function () { if (soundEnabled) { LK.getSound('click').play(); } // Remove popup with animation tween(popup, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, onFinish: function onFinish() { popup.destroy(); } }); }; popup.addChild(noButton); // Animate popup appearing popup.scale.set(0.8, 0.8); popup.alpha = 0; tween(popup, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); } function levelUp() { level++; levelTxt.setText('Level: ' + level); // Show level up message with speed information var levelUpTxt = new Text2('LEVEL UP!\nSpeed x' + speedMultiplier.toFixed(1), { size: 80, fill: 0xFFFFFF }); levelUpTxt.anchor.set(0.5, 0.5); levelUpTxt.x = 1024; levelUpTxt.y = 1366; LK.gui.addChild(levelUpTxt); // Animate level up message tween(levelUpTxt, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 1000, onFinish: function onFinish() { levelUpTxt.destroy(); } }); // Change music to heavy metal if level is 4 or above if (level === 4 && musicEnabled) { // Transition to heavy metal music with fade effects LK.playMusic('heavyMetalMusic', { fade: { start: 0, end: 0.7, duration: 1500 } }); } // Increase difficulty with a moderate speed boost for slightly slower progression speedMultiplier += 0.4 + level * 0.08; // Reduced speed boost for slightly slower gameplay maxBalls = 1; // Keep maxBalls at 1 // Reset hit counter for next level currentHits = 0; // Increase hits required for next level for increasing challenge hitsToNextLevel = 25 + (level - 1) * 5; hitCounterText.setText('0/' + hitsToNextLevel); } function gameOver() { gameActive = false; // Check if we have a new high score if (score > highScore) { highScore = score; storage.highScore = highScore; // Show new high score message var newHighScoreTxt = new Text2('NEW HIGH SCORE!', { size: 80, fill: 0xFFB612 }); newHighScoreTxt.anchor.set(0.5, 0.5); newHighScoreTxt.x = 1024; newHighScoreTxt.y = 1000; LK.gui.addChild(newHighScoreTxt); // Animate high score message tween(newHighScoreTxt, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 1500, onFinish: function onFinish() { newHighScoreTxt.destroy(); } }); // Update high score display if (highScoreTxt) { highScoreTxt.setText('High Score: ' + highScore); } } // Flash the screen red to indicate death LK.effects.flashScreen(0xff0000, 500); // If music enabled and player was at level 4 or above, prepare to reset music var shouldResetMusic = level >= 4 && musicEnabled; // Restart the game after a short delay instead of showing menu LK.setTimeout(function () { // Keep the game elements visible, just restart game logic // Clear any remaining balls for (var i = 0; i < balls.length; i++) { balls[i].destroy(); } balls = []; ballsInPlay = 0; // Restart the game immediately startGame(); // Ensure we're in playing state currentState = GAME_STATE.PLAYING; }, 500); } // Start the game function startGame() { // Reset variables score = 0; level = 1; combo = 0; lastBallHit = 0; gameActive = true; speedMultiplier = 1.5; // Further reduced initial speed multiplier for slower initial gameplay maxBalls = 3; // Maintained same number of maximum balls ballsInPlay = 0; currentHits = 0; hitsToNextLevel = 25; // Update UI scoreTxt.setText('0'); scoreTxt.scale.set(1, 1); // Reset any scaling from animations scoreTxt.x = 2048 / 2; // True center horizontally scoreTxt.y = 2732 / 2; // True center vertically levelTxt.setText('Level: 1'); comboTxt.setText(''); comboTxt.alpha = 0; hitCounterText.setText('0/' + hitsToNextLevel); // Update high score display if (highScoreTxt) { highScoreTxt.setText('High Score: ' + highScore); } // Clear any existing balls for (var i = 0; i < balls.length; i++) { balls[i].destroy(); } balls = []; // Start with one ball createBall(); // Play relaxing game music at start (level 1-3) if (musicEnabled) { LK.playMusic('gameMusic', { fade: { start: 0, end: 0.4, // Lower volume duration: 2000 // Longer fade in } }); } // Ensure the current state is set to playing currentState = GAME_STATE.PLAYING; } // Game will start when player presses the Play button in the menu
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphic = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.active = true;
self.trailCounter = 0;
self.gravity = 0.18; // Further reduced gravity for slower falling
self.airResistance = 0.995; // Slightly increased air resistance for slower movement
self.bounciness = 0.92; // Lower bounciness for less energetic bounces
self.spin = 0; // Ball spin (affects horizontal movement)
self.lastHitPos = 0; // Last hit position on paddle (for spin calculation)
self.reset = function (speedMultiplier) {
// Set position to center of screen
self.x = 2048 / 2;
self.y = 2732 / 2;
// Generate a random angle between 0 and 2π (full 360 degrees)
var randomAngle = Math.random() * Math.PI * 2;
// Set initial speed magnitude
var speedMagnitude = 15 * speedMultiplier;
// Calculate velocity components based on random angle
self.speedX = Math.cos(randomAngle) * speedMagnitude;
self.speedY = Math.sin(randomAngle) * speedMagnitude;
self.active = true;
self.spin = 0; // Reset spin
self.lastHitPos = 0; // Reset last hit position
};
self.update = function () {
if (!self.active) {
return;
}
// Store last positions for collision detection
var lastX = self.x;
var lastY = self.y;
// Apply gravity - increases vertical speed over time
self.speedY += self.gravity;
// Apply air resistance
self.speedX *= self.airResistance;
self.speedY *= self.airResistance;
// Apply spin effect to horizontal movement
self.speedX += self.spin * 0.1;
// Gradually reduce spin over time
self.spin *= 0.98;
// Apply velocity
self.x += self.speedX;
self.y += self.speedY;
// Add trail effect based on speed
self.trailCounter++;
// Adjust trail frequency based on speed - faster speed = more frequent trails
var trailFrequency = Math.max(1, 5 - Math.floor((Math.abs(self.speedX) + Math.abs(self.speedY)) / 10));
// Create more trails at higher speeds
if (self.trailCounter > trailFrequency) {
self.trailCounter = 0;
var trail = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: lastX,
y: lastY
});
// Adjust trail size based on ball speed
var speedFactor = Math.min(1, (Math.abs(self.speedX) + Math.abs(self.speedY)) / 40);
trail.scale.set(self.scale.x * (0.7 - speedFactor * 0.2), self.scale.y * (0.7 - speedFactor * 0.2));
trail.tint = gameColors.ball;
// Higher alpha for faster speeds
trail.alpha = 0.5 + speedFactor * 0.3;
game.addChildAt(trail, game.getChildIndex(self));
// Fade out and remove trail - faster trails disappear quicker
var trailDuration = 300 - speedFactor * 150;
tween(trail, {
alpha: 0,
scaleX: trail.scale.x * 0.5,
scaleY: trail.scale.y * 0.5
}, {
duration: trailDuration,
onFinish: function onFinish() {
trail.destroy();
}
});
}
// Rotate the ball based on horizontal speed to show rolling effect
ballGraphic.rotation += self.speedX * 0.05;
// Bounce off sides with more random physics
if (self.x < 20 || self.x > 2028) {
// Create particles at wall collision point
createCollisionParticles(self.x, self.y);
// Flip the horizontal direction
self.speedX = -self.speedX * self.bounciness * 1.2;
// Add significant random variation to both speed components
self.speedX += Math.random() * 6 - 3; // Major randomness on x-axis
self.speedY += Math.random() * 4 - 2; // Add randomness to y-axis on wall bounce too
// Chance for a very wild bounce (20% chance)
if (Math.random() < 0.2) {
self.speedX *= 0.5 + Math.random();
self.speedY *= 0.5 + Math.random();
}
// Keep the ball within the game boundaries
self.x = Math.max(20, Math.min(2028, self.x));
// Play bounce sound if soundEnabled is true
if (soundEnabled) {
LK.getSound('bounce').play();
}
}
// Check if ball hits the top of the screen with more random bounces
if (self.y < 20) {
// Create particles at ceiling collision point
createCollisionParticles(self.x, self.y);
// Flip the vertical direction
self.speedY = -self.speedY * self.bounciness * 1.25;
// Add significant random variation to both speed components
self.speedX += Math.random() * 5 - 2.5; // Add randomness to x-axis on ceiling bounce
self.speedY += Math.random() * 5 - 2.5; // Major randomness on y-axis
// Chance for a very wild bounce (20% chance)
if (Math.random() < 0.2) {
// Completely random direction after ceiling hit
var randomAngle = Math.random() * Math.PI + Math.PI; // Angle in bottom half
var currentSpeed = Math.sqrt(self.speedX * self.speedX + self.speedY * self.speedY);
self.speedX = Math.cos(randomAngle) * currentSpeed;
self.speedY = Math.sin(randomAngle) * currentSpeed;
}
self.y = 20;
// Play bounce sound if soundEnabled is true
if (soundEnabled) {
LK.getSound('bounce').play();
}
}
};
return self;
});
var DiagonalStripe = Container.expand(function () {
var self = Container.call(this);
// Create a shape for the diagonal stripe
var stripeGraphic = self.attachAsset('background', {
anchorX: 0,
anchorY: 0
});
// Configure the stripe appearance
stripeGraphic.width = 3000; // Increased width to extend past screen edges
stripeGraphic.height = 100;
stripeGraphic.tint = 0xffffff; // White
// Initial position and rotation
stripeGraphic.rotation = Math.PI / 4; // 45 degrees in radians
// Position it to extend past screen edges
stripeGraphic.x = -500; // Start before the left edge
// Empty update method (stripe will be still)
self.update = function () {
// No animation - stripe remains still
};
return self;
});
var Paddle = Container.expand(function () {
var self = Container.call(this);
// Create the main paddle base - middle rectangle section
var paddleGraphic = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
paddleGraphic.tint = 0xFFB612; // Set paddle color to #FFB612
// Create the left rounded end (circle shape)
var leftEnd = LK.getAsset('ball', {
// Using the ball asset as it's a circle
anchorX: 0.5,
anchorY: 0.5,
width: paddleGraphic.height,
height: paddleGraphic.height,
tint: 0xFFB612
});
leftEnd.x = -paddleGraphic.width / 2 + leftEnd.width / 2;
self.addChild(leftEnd);
// Create the right rounded end (circle shape)
var rightEnd = LK.getAsset('ball', {
// Using the ball asset as it's a circle
anchorX: 0.5,
anchorY: 0.5,
width: paddleGraphic.height,
height: paddleGraphic.height,
tint: 0xFFB612
});
rightEnd.x = paddleGraphic.width / 2 - rightEnd.width / 2;
// Make sure the right end is the correct color
rightEnd.tint = 0xFFB612;
self.addChild(rightEnd);
// Trim the main paddle to accommodate rounded ends
paddleGraphic.width = paddleGraphic.width - paddleGraphic.height;
self.width = paddleGraphic.width + paddleGraphic.height; // Total width includes the circles
self.height = paddleGraphic.height;
// Add paddle tracking flag and double click detection
self.ballFollowing = false;
self.lastClickTime = 0;
self.down = function (x, y, obj) {
var currentTime = Date.now();
// Check for double click (within 300ms)
if (currentTime - self.lastClickTime < 300) {
// Toggle ball following state
self.ballFollowing = !self.ballFollowing;
// Create visual feedback
var effect = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: self.ballFollowing ? 0x00ff00 : 0xff0000
});
effect.scale.set(2, 2);
effect.alpha = 0.5;
game.addChild(effect);
// Animate and remove the effect
tween(effect, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 300,
onFinish: function onFinish() {
effect.destroy();
}
});
// Play click sound for feedback
if (soundEnabled) {
LK.getSound('click').play();
}
}
self.lastClickTime = currentTime;
};
self.update = function () {
// Keep paddle within screen bounds
self.x = Math.max(self.width / 2, Math.min(2048 - self.width / 2, self.x));
};
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
var particleGraphic = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Initial size scale
self.scale.set(0.5, 0.5); // Start with smaller particles
// Particle colors
var colors = [0xFFB612, 0xC60C30, 0x003087, 0xFFFFFF];
particleGraphic.tint = colors[Math.floor(Math.random() * colors.length)];
// Random speed and direction
var angle = Math.random() * Math.PI * 2;
var speed = 2 + Math.random() * 8;
self.vx = Math.cos(angle) * speed;
self.vy = Math.sin(angle) * speed;
// Random rotation
self.rotationSpeed = (Math.random() - 0.5) * 0.2;
// Particle lifespan tracking
self.lifetime = 0;
self.maxLifetime = 30 + Math.random() * 30;
// Update function
self.update = function () {
// Update position with reduced movement (smaller radius)
self.x += self.vx * 0.5; // Reduce horizontal movement by 50%
self.vy += 0.1; // Gravity effect
self.y += self.vy * 0.5; // Reduce vertical movement by 50%
// Rotate particle
particleGraphic.rotation += self.rotationSpeed;
// Update lifetime
self.lifetime++;
// Fade out based on lifetime
if (self.lifetime > self.maxLifetime * 0.7) {
self.alpha = 1 - (self.lifetime - self.maxLifetime * 0.7) / (self.maxLifetime * 0.3);
}
// Remove when lifetime is over
if (self.lifetime >= self.maxLifetime) {
self.active = false;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffffff // Light sky blue for a calmer atmosphere
});
/****
* Game Code
****/
// Game state management
var GAME_STATE = {
MENU: 0,
PLAYING: 1,
SETTINGS: 2
};
var currentState = GAME_STATE.MENU;
// Default colors for game elements - softer, more calming colors
var gameColors = {
paddle: 0xffb612,
// Changed to requested color #ffb612
ball: 0xFFB612,
// Changed to requested color #FFB612
lava: 0xC60C30 // Changed to requested color #C60C30
};
// Color saving/loading disabled
// Make game accessible to other functions
var gameInstance = game;
// Game variables
var background;
var paddle;
var lava;
var balls = [];
var particles = []; // Array to track active particles
var score = 0;
var highScore = storage.highScore || 0;
var level = 1;
var combo = 0;
var lastBallHit = 0;
var gameActive = false;
var speedMultiplier = 1.0;
var maxBalls = 1;
var ballsInPlay = 0;
var spawnInterval;
var hitsToNextLevel = 25;
var currentHits = 0;
var hitCounterText;
// UI elements
var scoreTxt;
var levelTxt;
var comboTxt;
var highScoreTxt;
var speedTxt;
// Default sound settings
var soundEnabled = true;
var musicEnabled = true;
// Load stored sound settings
var soundSetting = storage.soundEnabled;
if (soundSetting !== undefined) {
soundEnabled = soundSetting;
}
// Initialize game elements (called when starting game)
function initializeGameElements() {
if (!background) {
// Create background
background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
// Initialize lava
lava = LK.getAsset('lava', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 2732 - 200
});
lava.tint = gameColors.lava;
lava.alpha = 0.8; // Add transparency to lava
lava.lavaEffects = null; // Will be populated in update method
game.addChild(lava);
// Initialize paddle
paddle = new Paddle();
paddle.x = 2048 / 2;
paddle.y = 2732 - 250;
paddle.getChildAt(0).tint = 0xffb612;
game.addChild(paddle);
// Diagonal stripe removed from here and placed in menu
// Create hit counter text
scoreTxt = new Text2('0', {
size: 180,
fill: 0x101820
});
scoreTxt.anchor.set(0.5, 0.5);
// Position score text precisely in the center of the screen
scoreTxt.x = 2048 / 2; // True center horizontally
scoreTxt.y = 2732 / 2; // True center vertically
game.addChild(scoreTxt);
// Add Glaud text in the upper right corner
var glaudText = new Text2('Glaud', {
size: 60,
fill: 0xFF8C00 // Orange color
});
glaudText.anchor.set(1, 0); // Anchor to top right
glaudText.x = 2000; // Position near right edge
glaudText.y = 20; // Position near top
game.addChild(glaudText);
// Create level text
levelTxt = new Text2('Level: 1', {
size: 70,
fill: 0xFFFFFF
});
levelTxt.anchor.set(1, 0);
levelTxt.x = 2000;
levelTxt.y = 50;
LK.gui.addChild(levelTxt);
// Create combo text
comboTxt = new Text2('', {
size: 60,
fill: 0xFFFFFF
});
comboTxt.anchor.set(0.5, 0);
comboTxt.x = 1024;
comboTxt.y = 50;
comboTxt.alpha = 0;
LK.gui.addChild(comboTxt);
// Create high score text
highScoreTxt = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xffb612
});
highScoreTxt.anchor.set(1, 0);
highScoreTxt.x = 2000;
highScoreTxt.y = 130; // Position below hit counter
game.addChild(highScoreTxt); // Add directly to game to ensure visibility
// Create speed indicator text
speedTxt = new Text2('Speed: x' + speedMultiplier.toFixed(1), {
size: 60,
fill: 0xffb612
});
speedTxt.anchor.set(0, 0);
speedTxt.x = 48;
speedTxt.y = 50;
game.addChild(speedTxt);
// Create hit counter text
hitCounterText = new Text2(currentHits + '/25', {
size: 70,
fill: 0x003087
});
hitCounterText.anchor.set(0.5, 0);
hitCounterText.x = 1024;
hitCounterText.y = 150; // More visible position at top of screen
game.addChild(hitCounterText); // Add directly to game to ensure visibility
}
// Show game elements
background.visible = true;
lava.visible = true;
paddle.visible = true;
scoreTxt.visible = true;
levelTxt.visible = true;
comboTxt.visible = true;
highScoreTxt.visible = true;
hitCounterText.visible = true;
}
// Create menu elements
var titleText;
var startButton;
var settingsButton;
var settingsPanel;
var menuBackground;
// Initialize menu
initializeMenu();
function initializeMenu() {
// Play menu music if enabled
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1500
}
});
}
// Create menu background
menuBackground = new Container();
var menuBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
tint: 0xFFFFFF // White color for menu background
});
// Create a border by adding a slightly larger background behind it
var menuBorder = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
tint: 0xA5ACAF // Border color set to #A5ACAF
});
menuBorder.width = 2048 + 10 * 2; // Adding 10px on each side
menuBorder.height = 2732 + 10 * 2; // Adding 10px on each side
menuBorder.x = -10; // Position it 10px to the left
menuBorder.y = -10; // Position it 10px to the top
menuBackground.addChild(menuBorder);
menuBackground.addChild(menuBg);
game.addChild(menuBackground);
// Diagonal stripe removed from menu
// Create game title
titleText = new Text2('Lava Bounce', {
size: 150,
fill: 0x101820
});
titleText.anchor.set(0.5, 0);
titleText.x = 1024;
titleText.y = 200;
game.addChild(titleText);
// Animate the title to rotate back and forth
function animateTitleRotation() {
// Rotate to one side
tween(titleText, {
rotation: 0.1 // Slight rotation to the right (in radians)
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Rotate to the other side
tween(titleText, {
rotation: -0.1 // Slight rotation to the left (in radians)
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: animateTitleRotation // Loop the animation
});
}
});
}
// Start the title rotation animation
animateTitleRotation();
// Create the "2" as a separate, larger text element
var titleNumber = new Text2('2', {
size: 200,
// Bigger than the main title text
fill: 0xFFB612 // Gold color
});
titleNumber.anchor.set(0.5, 0);
titleNumber.x = 1024; // Centered horizontally
titleNumber.y = 350; // Positioned below the main title
game.addChild(titleNumber);
// Animate the "2" with a continuous bounce effect
function animateTitle2() {
// Bounce up animation
tween(titleNumber, {
y: 320,
// Move up slightly
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 700,
onFinish: function onFinish() {
// Bounce down animation
tween(titleNumber, {
y: 350,
// Back to original position
scaleX: 1,
scaleY: 1
}, {
duration: 700,
onFinish: animateTitle2 // Loop the animation
});
}
});
}
// Start the animation
animateTitle2();
// Create start button
startButton = new Text2('Start Game', {
size: 90,
fill: 0xFFB612
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 1024;
startButton.y = 1200;
startButton.interactive = true;
startButton.isHovered = false; // Track hover state
startButton.move = function (x, y, obj) {
// Check if cursor is over the button
if (x >= startButton.x - startButton.width / 2 && x <= startButton.x + startButton.width / 2 && y >= startButton.y - startButton.height / 2 && y <= startButton.y + startButton.height / 2) {
// Start animation only if not already hovering
if (!startButton.isHovered) {
startButton.isHovered = true;
// Apply a slight scale increase when hovered
tween(startButton, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 200,
easing: tween.easeOut
});
animateStartButton();
}
} else {
// Stop animation when cursor moves away
if (startButton.isHovered) {
startButton.isHovered = false;
tween.stop(startButton, {
rotation: true
});
tween(startButton, {
rotation: 0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
}
};
// Function to animate the start button
function animateStartButton() {
if (!startButton.isHovered) {
return;
}
// Rotate to one side
tween(startButton, {
rotation: 0.08 // Slight rotation (in radians)
}, {
duration: 50,
// Very quick rotation for more responsive feel
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!startButton.isHovered) {
startButton.rotation = 0;
return;
}
// Rotate to the other side
tween(startButton, {
rotation: -0.08 // Slight rotation in opposite direction
}, {
duration: 50,
// Very quick rotation for more responsive feel
easing: tween.easeInOut,
onFinish: animateStartButton // Continue the animation
});
}
});
}
game.addChild(startButton);
// Create settings button
settingsButton = new Text2('Settings', {
size: 90,
fill: 0x101820
});
settingsButton.anchor.set(0.5, 0.5);
settingsButton.x = 1024;
settingsButton.y = 1400;
settingsButton.interactive = true;
game.addChild(settingsButton);
// Set up event handlers for menu
startButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
// Stop menu music with fade out
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0.6,
end: 0,
duration: 500
}
});
}
hideMenu();
initializeGameElements();
startGame();
};
settingsButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
hideMenu();
showSettings();
};
}
function hideMenu() {
if (menuBackground) {
menuBackground.visible = false;
}
if (titleText) {
titleText.visible = false;
}
if (startButton) {
startButton.visible = false;
}
if (settingsButton) {
settingsButton.visible = false;
}
// Make sure high score is visible in game
if (highScoreTxt) {
highScoreTxt.visible = true;
}
}
function showMenu() {
currentState = GAME_STATE.MENU;
if (menuBackground) {
menuBackground.visible = true;
}
if (titleText) {
titleText.visible = true;
}
if (startButton) {
startButton.visible = true;
}
if (settingsButton) {
settingsButton.visible = true;
}
if (settingsPanel) {
settingsPanel.visible = false;
}
// Play menu music when returning to menu from game over
if (musicEnabled && currentState === GAME_STATE.MENU) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
}
}
function showSettings() {
currentState = GAME_STATE.SETTINGS;
// Create settings panel if it doesn't exist
if (!settingsPanel) {
settingsPanel = new Container();
game.addChild(settingsPanel);
// Settings panel background without border
var panelContainer = new Container();
panelContainer.x = 1024;
panelContainer.y = 1366;
// Set initial scale to zero for animation
panelContainer.scale.x = 0;
panelContainer.scale.y = 0;
// Inner panel background (without blue border)
var panelBg = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
panelBg.width = 1600;
panelBg.height = 1000;
panelBg.tint = 0x101820; // Dark blue/black color for settings panel
panelContainer.addChild(panelBg);
settingsPanel.addChild(panelContainer);
// Settings title
var settingsTitle = new Text2('Settings', {
size: 80,
fill: 0xFFFFFF
});
settingsTitle.anchor.set(0.5, 0);
settingsTitle.x = 1024;
settingsTitle.y = 500;
settingsPanel.addChild(settingsTitle);
// Color selectors have been removed
// Game description text
var gameDescription = new Text2('Lava Bounce 2: A frantic paddle game where you\ncontrol a golden paddle to bounce balls away from\ndeadly red lava. Score points with each bounce,\nlevel up for increased speed and challenge.\nCan you set a new high score record?', {
size: 50,
fill: 0xFFB612
});
gameDescription.anchor.set(0.5, 0.5);
gameDescription.x = 1024; // True center horizontally
gameDescription.y = 700;
settingsPanel.addChild(gameDescription);
// High score display in settings
var highScoreDisplay = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xFFFFFF
});
highScoreDisplay.anchor.set(0, 0.5);
highScoreDisplay.x = 400;
highScoreDisplay.y = 970; // Moved down a bit
settingsPanel.addChild(highScoreDisplay);
// Reset high score button
var resetButton = new Text2('Reset High Score', {
size: 60,
fill: 0xFF6B6B
});
resetButton.anchor.set(0.5, 0.5);
resetButton.x = 1024; // True center horizontally
resetButton.y = 1366; // True center vertically
resetButton.interactive = true;
resetButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
showResetConfirmation();
};
settingsPanel.addChild(resetButton);
// Back to menu button
var backButton = new Text2('Back to Menu', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = 1100;
backButton.interactive = true;
backButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
// Animate settings panel closing
tween(settingsPanel.getChildAt(0), {
scaleX: 0,
scaleY: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
settingsPanel.visible = false;
showMenu();
// Play menu music when returning to menu
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
}
}
});
};
settingsPanel.addChild(backButton);
} else {
settingsPanel.visible = true;
// Reset scale for animation if panel already exists
settingsPanel.getChildAt(0).scale.x = 0;
settingsPanel.getChildAt(0).scale.y = 0;
}
// Animate the settings panel opening with bounce effect
tween(settingsPanel.getChildAt(0), {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.elasticOut
});
// Color buttons function has been removed
}
// Function to create particles at click location
function createClickParticles(x, y) {
// Create 15-20 particles for a visually impressive effect
var particleCount = 15 + Math.floor(Math.random() * 6);
for (var i = 0; i < particleCount; i++) {
var particle = new Particle();
particle.x = x;
particle.y = y;
particle.active = true;
// Add to particles array and game display
particles.push(particle);
game.addChild(particle);
}
}
// Function to create particles at collision location
function createCollisionParticles(x, y) {
// Create 8-12 particles for collision effect - fewer than click particles
var particleCount = 8 + Math.floor(Math.random() * 5);
for (var i = 0; i < particleCount; i++) {
var particle = new Particle();
particle.x = x;
particle.y = y;
particle.active = true;
particle.scale.set(0.3, 0.3); // Smaller particles for collisions
// Add to particles array and game display
particles.push(particle);
game.addChild(particle);
}
}
// Initialize balls array
function createBall() {
if (ballsInPlay >= maxBalls || !gameActive) {
return;
}
var ball = new Ball();
ball.reset(speedMultiplier);
ball.getChildAt(0).tint = gameColors.ball;
// Visual indicator of speed - make ball slightly smaller as it gets faster
var scale = Math.max(0.6, 1 - (speedMultiplier - 1) * 0.15);
ball.scale.set(scale, scale);
balls.push(ball);
game.addChild(ball);
ballsInPlay++;
}
// Handle input events based on current state
game.down = function (x, y, obj) {
// Play click sound whenever cursor is clicked
if (soundEnabled) {
LK.getSound('click').play();
}
// Create particles at click location
createClickParticles(x, y);
if (currentState === GAME_STATE.PLAYING) {
// Check if click is within paddle bounds
if (x >= paddle.x - paddle.width / 2 && x <= paddle.x + paddle.width / 2 && y >= paddle.y - paddle.height / 2 && y <= paddle.y + paddle.height / 2) {
// Call paddle's down handler for double-click detection
paddle.down(x, y, obj);
} else if (!paddle.ballFollowing) {
// Only move paddle if not in ball following mode
paddle.x = x;
}
}
// Check if we hit any interactive elements
if (obj && obj.event && obj.event.target && obj.event.target.down) {
obj.event.target.down(x, y, obj);
}
};
game.move = function (x, y, obj) {
if (currentState === GAME_STATE.PLAYING) {
// Only move paddle with mouse if not in ball following mode
if (!paddle.ballFollowing) {
paddle.x = x;
}
}
};
// Update function
game.update = function () {
// Update any diagonal stripes in the game (always update even when not playing)
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] instanceof DiagonalStripe) {
game.children[i].update();
}
}
// Update all particles
for (var i = particles.length - 1; i >= 0; i--) {
var particle = particles[i];
if (particle.active === false) {
particle.destroy();
particles.splice(i, 1);
continue;
}
particle.update();
}
// Check if in playing state
if (currentState !== GAME_STATE.PLAYING) {
return;
}
// Game play state
if (!gameActive) {
return;
}
// Animate lava flow with flowing colors
if (lava) {
// Create flowing lava effect using color transitions
if (!lava.lavaEffects) {
lava.lavaEffects = [];
// Create multiple flowing wave effects
for (var i = 0; i < 5; i++) {
var wave = LK.getAsset('lava', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: lava.y + i * 5 - 10,
tint: i % 2 === 0 ? 0xff8800 : 0xc60c30 // Alternate yellow and red
});
wave.alpha = 0.5;
wave.height = 60;
lava.lavaEffects.push(wave);
game.addChildAt(wave, game.getChildIndex(lava));
// Start flow animation for each wave
animateLavaFlow(wave, i);
}
}
// Create occasional particles on lava surface for extra effect
if (LK.ticks % 10 == 0) {
var particleX = Math.random() * 2048;
var particleY = lava.y + Math.random() * 10;
var lavaParticle = new Particle();
lavaParticle.x = particleX;
lavaParticle.y = particleY;
lavaParticle.scale.set(0.2, 0.2); // Small particles
lavaParticle.active = true;
// Make particles match lava color scheme
lavaParticle.getChildAt(0).tint = Math.random() > 0.5 ? 0xff8800 : 0xc60c30;
// Add to particles array and game display
particles.push(lavaParticle);
game.addChild(lavaParticle);
}
}
// Update speed indicator if it exists
if (speedTxt) {
speedTxt.setText('Speed: x' + speedMultiplier.toFixed(1));
}
// Only create a ball if none exists
if (ballsInPlay === 0) {
createBall();
}
// Update paddle and track position for physics calculations
game.lastPaddleX = paddle.x; // Store current position for next frame
paddle.update();
// Update all balls
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
if (!ball.active) {
continue;
}
// Make ball follow paddle quickly if ball following is enabled
if (paddle.ballFollowing) {
// Calculate direction vector to paddle
var dirX = paddle.x - ball.x;
var dirY = paddle.y - 50 - ball.y; // Position ball slightly above paddle
// Normalize and scale by desired speed (much faster than normal)
var length = Math.sqrt(dirX * dirX + dirY * dirY);
if (length > 5) {
// Only move if not already very close
ball.speedX = dirX / length * 30; // Fast following speed
ball.speedY = dirY / length * 30;
} else {
// If close enough, hover above paddle
ball.speedX *= 0.8;
ball.speedY *= 0.8;
}
}
ball.update();
// Check if ball hits paddle with improved collision detection
if (ball.speedY > 0 && ball.y + 20 >= paddle.y - paddle.height / 2 && ball.y - 20 <= paddle.y + paddle.height / 2 && ball.x + 20 >= paddle.x - paddle.width / 2 && ball.x - 20 <= paddle.x + paddle.width / 2) {
// Create particles at collision point
createCollisionParticles(ball.x, ball.y);
// Calculate hit position from -1 (left edge) to 1 (right edge)
var hitPos = (ball.x - paddle.x) / (paddle.width / 2);
// Calculate spin based on the difference between current and last hit position
// This simulates the effect of a moving paddle hitting the ball
var paddleMovementEffect = 0;
if (game.lastPaddleX !== undefined) {
paddleMovementEffect = (paddle.x - game.lastPaddleX) * 0.1;
}
// Apply reduced spin based on hit position and paddle movement
ball.spin = hitPos * 0.3 + paddleMovementEffect * 0.6; // Reduced spin effect
ball.lastHitPos = hitPos;
// Calculate angle based on where the ball hits the paddle with more randomness
// Wider angle range - full 180 degrees (0 to 180) instead of 120 degrees
var angle = Math.PI * Math.random() + Math.PI / 2; // Random angle in upper half (90 to 270 degrees)
// Add influence from hit position
angle = angle * 0.7 + (Math.PI / 3 * hitPos + Math.PI / 2) * 0.3;
// Calculate current speed with an adjustment
var currentSpeed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY);
var speed = Math.max(currentSpeed * 1.25, 12 * speedMultiplier);
// Adjust ball direction with more random bounce physics
ball.speedX = Math.cos(angle) * speed + paddleMovementEffect * 1.5;
ball.speedY = -Math.sin(angle) * speed * 1.4; // Increased vertical multiplier for higher bounce
// Add larger random variations for more unpredictable bouncing
ball.speedX += (Math.random() * 4 - 2) * speedMultiplier; // Increased randomness
ball.speedY += (Math.random() * 4 - 2) * speedMultiplier; // Increased randomness
// Create a bounce effect with the paddle - make it visually respond to the hit
tween(paddle, {
scaleY: 0.85,
y: paddle.y + 5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(paddle, {
scaleY: 1.0,
y: paddle.y - 5
}, {
duration: 100,
easing: tween.elasticOut
});
}
});
// Move ball above paddle to prevent multiple collisions
ball.y = paddle.y - paddle.height / 2 - 20;
// Add a visual impact effect
var impactEffect = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: ball.x,
y: ball.y + 10,
tint: 0xFFFFFF
});
impactEffect.scale.set(1.5, 0.5);
impactEffect.alpha = 0.7;
game.addChild(impactEffect);
// Animate and remove the impact effect
tween(impactEffect, {
alpha: 0,
scaleX: 2.5,
scaleY: 0.2
}, {
duration: 200,
onFinish: function onFinish() {
impactEffect.destroy();
}
});
// Simple scoring - always add 1 point
score += 1;
// Hide combo text
comboTxt.alpha = 0;
// Update hit counter
scoreTxt.setText('' + score);
// Center the score text
scoreTxt.x = 2048 / 2; // True center horizontally
scoreTxt.y = 2732 / 2; // True center vertically
// Make it fade and gently grow when updated
tween(scoreTxt, {
scaleX: 1.1,
// Smaller scale change
scaleY: 1.1,
// Smaller scale change
alpha: 0.8 // Slight fade out
}, {
duration: 300,
// Longer duration for smoother animation
easing: tween.easeInOut,
// Smoother easing
onFinish: function onFinish() {
tween(scoreTxt, {
scaleX: 1,
scaleY: 1,
alpha: 1 // Back to full opacity
}, {
duration: 300,
// Longer duration
easing: tween.easeInOut // Smoother easing
});
}
});
LK.setScore(score);
// Play bounce sound if enabled
if (soundEnabled) {
LK.getSound('bounce').play();
}
// Update hit counter for level system
currentHits++;
hitCounterText.setText(currentHits + '/' + hitsToNextLevel);
// Level up based on hits
if (currentHits >= hitsToNextLevel) {
currentHits = 0;
levelUp();
hitCounterText.setText('Hits to Next Level: ' + hitsToNextLevel);
// Animate hit counter text
tween(hitCounterText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(hitCounterText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
}
// Check if ball falls into lava
if (ball.y > lava.y) {
// Play lava sound if enabled
if (soundEnabled) {
LK.getSound('lava').play();
}
// Flash the lava
tween(lava, {
tint: 0xffffff
}, {
duration: 200,
onFinish: function onFinish() {
tween(lava, {
tint: 0xe74c3c
}, {
duration: 200
});
}
});
// Remove ball
ball.active = false;
ball.destroy();
balls.splice(i, 1);
ballsInPlay--;
// Check game over
if (balls.length === 0 && ballsInPlay === 0) {
gameOver();
}
}
}
};
// Function to animate lava flow waves
function animateLavaFlow(wave, index) {
// Starting position slightly off screen to the left
wave.x = -200;
// Set color based on index to ensure even distribution of yellow and red
wave.tint = index % 2 === 0 ? 0xff8800 : 0xc60c30;
// Animate wave flowing from left to right
tween(wave, {
x: 2048 + 200 // Move to right side of screen and beyond
}, {
duration: 6000 + index * 1000,
// Different speeds for different waves
onFinish: function onFinish() {
// Maintain the same color based on index for consistent distribution
// (no toggling colors anymore to keep distribution even)
// Reset position and restart animation
wave.x = -200;
animateLavaFlow(wave, index);
}
});
}
// Reset confirmation popup
function showResetConfirmation() {
// Create popup container
var popup = new Container();
game.addChild(popup);
// Popup background
var popupBg = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
popupBg.width = 1200;
popupBg.height = 700;
popupBg.tint = 0x101820;
popup.addChild(popupBg);
// Border for popup
var popupBorder = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
popupBorder.width = 1210;
popupBorder.height = 710;
popupBorder.tint = 0xFFB612;
popup.addChildAt(popupBorder, 0);
// Confirmation text
var confirmText = new Text2('Reset High Score?', {
size: 70,
fill: 0xFFFFFF
});
confirmText.anchor.set(0.5, 0);
confirmText.x = 1024;
confirmText.y = 1200;
popup.addChild(confirmText);
// Yes button
var yesButton = new Text2('Yes', {
size: 60,
fill: 0xFF6B6B
});
yesButton.anchor.set(0.5, 0.5);
yesButton.x = 824;
yesButton.y = 1450;
yesButton.interactive = true;
yesButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
// Reset high score
highScore = 0;
storage.highScore = 0;
// Update high score display
if (highScoreTxt) {
highScoreTxt.setText('High Score: 0');
}
// Update settings display
settingsPanel.children.forEach(function (child) {
if (child instanceof Text2 && child.text && child.text.startsWith('High Score:')) {
child.setText('High Score: 0');
}
});
// Remove popup with animation
tween(popup, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
onFinish: function onFinish() {
popup.destroy();
}
});
};
popup.addChild(yesButton);
// No button
var noButton = new Text2('No', {
size: 60,
fill: 0xFFFFFF
});
noButton.anchor.set(0.5, 0.5);
noButton.x = 1224;
noButton.y = 1450;
noButton.interactive = true;
noButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
// Remove popup with animation
tween(popup, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
onFinish: function onFinish() {
popup.destroy();
}
});
};
popup.addChild(noButton);
// Animate popup appearing
popup.scale.set(0.8, 0.8);
popup.alpha = 0;
tween(popup, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
function levelUp() {
level++;
levelTxt.setText('Level: ' + level);
// Show level up message with speed information
var levelUpTxt = new Text2('LEVEL UP!\nSpeed x' + speedMultiplier.toFixed(1), {
size: 80,
fill: 0xFFFFFF
});
levelUpTxt.anchor.set(0.5, 0.5);
levelUpTxt.x = 1024;
levelUpTxt.y = 1366;
LK.gui.addChild(levelUpTxt);
// Animate level up message
tween(levelUpTxt, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 1000,
onFinish: function onFinish() {
levelUpTxt.destroy();
}
});
// Change music to heavy metal if level is 4 or above
if (level === 4 && musicEnabled) {
// Transition to heavy metal music with fade effects
LK.playMusic('heavyMetalMusic', {
fade: {
start: 0,
end: 0.7,
duration: 1500
}
});
}
// Increase difficulty with a moderate speed boost for slightly slower progression
speedMultiplier += 0.4 + level * 0.08; // Reduced speed boost for slightly slower gameplay
maxBalls = 1; // Keep maxBalls at 1
// Reset hit counter for next level
currentHits = 0;
// Increase hits required for next level for increasing challenge
hitsToNextLevel = 25 + (level - 1) * 5;
hitCounterText.setText('0/' + hitsToNextLevel);
}
function gameOver() {
gameActive = false;
// Check if we have a new high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
// Show new high score message
var newHighScoreTxt = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0xFFB612
});
newHighScoreTxt.anchor.set(0.5, 0.5);
newHighScoreTxt.x = 1024;
newHighScoreTxt.y = 1000;
LK.gui.addChild(newHighScoreTxt);
// Animate high score message
tween(newHighScoreTxt, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 1500,
onFinish: function onFinish() {
newHighScoreTxt.destroy();
}
});
// Update high score display
if (highScoreTxt) {
highScoreTxt.setText('High Score: ' + highScore);
}
}
// Flash the screen red to indicate death
LK.effects.flashScreen(0xff0000, 500);
// If music enabled and player was at level 4 or above, prepare to reset music
var shouldResetMusic = level >= 4 && musicEnabled;
// Restart the game after a short delay instead of showing menu
LK.setTimeout(function () {
// Keep the game elements visible, just restart game logic
// Clear any remaining balls
for (var i = 0; i < balls.length; i++) {
balls[i].destroy();
}
balls = [];
ballsInPlay = 0;
// Restart the game immediately
startGame();
// Ensure we're in playing state
currentState = GAME_STATE.PLAYING;
}, 500);
}
// Start the game
function startGame() {
// Reset variables
score = 0;
level = 1;
combo = 0;
lastBallHit = 0;
gameActive = true;
speedMultiplier = 1.5; // Further reduced initial speed multiplier for slower initial gameplay
maxBalls = 3; // Maintained same number of maximum balls
ballsInPlay = 0;
currentHits = 0;
hitsToNextLevel = 25;
// Update UI
scoreTxt.setText('0');
scoreTxt.scale.set(1, 1); // Reset any scaling from animations
scoreTxt.x = 2048 / 2; // True center horizontally
scoreTxt.y = 2732 / 2; // True center vertically
levelTxt.setText('Level: 1');
comboTxt.setText('');
comboTxt.alpha = 0;
hitCounterText.setText('0/' + hitsToNextLevel);
// Update high score display
if (highScoreTxt) {
highScoreTxt.setText('High Score: ' + highScore);
}
// Clear any existing balls
for (var i = 0; i < balls.length; i++) {
balls[i].destroy();
}
balls = [];
// Start with one ball
createBall();
// Play relaxing game music at start (level 1-3)
if (musicEnabled) {
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 0.4,
// Lower volume
duration: 2000 // Longer fade in
}
});
}
// Ensure the current state is set to playing
currentState = GAME_STATE.PLAYING;
}
// Game will start when player presses the Play button in the menu