User prompt
put a score counter in the top center
User prompt
make it so when you hit the ball your score only goes up by one and when you die your score is reset and put the score counter in the top center
User prompt
only one ball
Code edit (1 edits merged)
Please save this source code
User prompt
Lava Paddle Panic
Initial prompt
paddle game where you hit a falling ball to get points and avoid the ball falling into the lava
/**** * 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; 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; 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); // 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) { 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) { 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; } // 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; } 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(); } } } }; // 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;
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;
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);
// 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) {
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) {
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;
}
// 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;
}
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();
}
}
}
};
// 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