User prompt
move the score counter to the center
User prompt
remove the start menu
User prompt
fix that
User prompt
well fix that
User prompt
when i click play the game does not start
User prompt
make the buttons clickable
User prompt
Please fix the bug: 'storage.getValue is not a function' in or related to this line: 'var soundSetting = storage.getValue('soundEnabled');' Line Number: 288 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
make a start menu where there is settings and a play button
User prompt
move the score counter to the top center of the screen\
User prompt
move the score counter to the center
User prompt
when the ball falls into the lava make it respawn in the top center
User prompt
when you die the screen flashes red ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when you die start again right away, no delay
User prompt
make the ball go faster
User prompt
when the ball hits the paddle make the ball go in a random direction
User prompt
when the ball hits the paddle your score only goes up by one
User prompt
make the ball spawn in the center
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
});
// Only create trail if trails are enabled
if (!visualSettings.trailsEnabled) {
self.trailCounter = 0;
return;
}
// Adjust trail size based on ball speed and settings
var speedFactor = Math.min(1, (Math.abs(self.speedX) + Math.abs(self.speedY)) / 40);
var trailSize = (0.7 - speedFactor * 0.2) * visualSettings.ballSize * visualSettings.trailIntensity;
trail.scale.set(self.scale.x * trailSize, self.scale.y * trailSize);
trail.tint = visualSettings.ballColor;
// Higher alpha for faster speeds, adjusted by trail intensity
trail.alpha = (0.5 + speedFactor * 0.3) * visualSettings.trailIntensity;
game.addChildAt(trail, game.getChildIndex(self));
// Fade out and remove trail - faster trails disappear quicker, adjusted by animation speed
var trailDuration = (300 - speedFactor * 150) / visualSettings.animationSpeed;
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 based on settings
var particleScale = 0.5 * visualSettings.particleSize * visualSettings.particleIntensity;
self.scale.set(particleScale, particleScale);
// Particle colors from settings
particleGraphic.tint = visualSettings.particleColors[Math.floor(Math.random() * visualSettings.particleColors.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 enhanced movement patterns
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%
// Add swirling motion for more dynamic particles
var swirl = Math.sin(self.lifetime * 0.1) * 0.5;
self.x += swirl;
// Rotate particle with varying speeds
particleGraphic.rotation += self.rotationSpeed;
// Scale animation during lifetime
var scaleMultiplier = 1 + Math.sin(self.lifetime * 0.15) * 0.1;
self.scale.set(particleScale * scaleMultiplier, particleScale * scaleMultiplier);
// Update lifetime
self.lifetime++;
// Enhanced fade out with pulsing effect
if (self.lifetime > self.maxLifetime * 0.7) {
var fadeProgress = (self.lifetime - self.maxLifetime * 0.7) / (self.maxLifetime * 0.3);
var pulse = 1 + Math.sin(self.lifetime * 0.3) * 0.1;
self.alpha = (1 - fadeProgress) * pulse;
}
// 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
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
var GAME_STATE = {
MENU: 0,
PLAYING: 1,
SETTINGS: 2,
MODE_SELECT: 3
};
var currentState = GAME_STATE.MENU;
// Game modes
var GAME_MODES = {
CLASSIC: 0,
SPEED_DEMON: 1,
MULTI_BALL: 2,
SURVIVAL: 3
};
var currentGameMode = GAME_MODES.CLASSIC;
// Game mode configurations
var gameModeConfigs = _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, GAME_MODES.CLASSIC, {
name: "Classic Mode",
description: "The original Lava Bounce experience.\nBounce balls and level up gradually.",
initialSpeed: 1.5,
speedIncrease: 0.4,
maxBalls: 1,
hitsPerLevel: 25
}), GAME_MODES.SPEED_DEMON, {
name: "Speed Demon",
description: "Fast-paced action from the start!\nHigh speed, quick reflexes required.",
initialSpeed: 3.0,
speedIncrease: 0.8,
maxBalls: 1,
hitsPerLevel: 15
}), GAME_MODES.MULTI_BALL, {
name: "Multi-Ball Madness",
description: "Multiple balls create chaos!\nManage several balls at once.",
initialSpeed: 1.2,
speedIncrease: 0.3,
maxBalls: 3,
hitsPerLevel: 35
}), GAME_MODES.SURVIVAL, {
name: "Survival Mode",
description: "How long can you last?\nIncreasing difficulty, one life only.",
initialSpeed: 1.0,
speedIncrease: 0.6,
maxBalls: 1,
hitsPerLevel: 20
});
// Visual customization settings with defaults
var visualSettings = {
// Colors
paddleColor: storage.paddleColor || 0xFFB612,
ballColor: storage.ballColor || 0xFFB612,
lavaColor: storage.lavaColor || 0xC60C30,
backgroundColor: storage.backgroundColor || 0xFFFFFF,
textColor: storage.textColor || 0x101820,
uiColor: storage.uiColor || 0xFFFFFF,
particleColors: storage.particleColors || [0xFFB612, 0xC60C30, 0x003087, 0xFFFFFF],
// Effects
trailsEnabled: storage.trailsEnabled !== undefined ? storage.trailsEnabled : true,
particlesEnabled: storage.particlesEnabled !== undefined ? storage.particlesEnabled : true,
screenShakeEnabled: storage.screenShakeEnabled !== undefined ? storage.screenShakeEnabled : true,
lavaAnimationEnabled: storage.lavaAnimationEnabled !== undefined ? storage.lavaAnimationEnabled : true,
// Sizes
ballSize: storage.ballSize || 1.0,
paddleSize: storage.paddleSize || 1.0,
textSize: storage.textSize || 1.0,
particleSize: storage.particleSize || 1.0,
// Visual intensity
trailIntensity: storage.trailIntensity || 1.0,
particleIntensity: storage.particleIntensity || 1.0,
animationSpeed: storage.animationSpeed || 1.0
};
// Default colors for game elements - using customizable settings
var gameColors = {
paddle: visualSettings.paddleColor,
ball: visualSettings.ballColor,
lava: visualSettings.lavaColor
};
// Function to save visual settings
function saveVisualSettings() {
storage.paddleColor = visualSettings.paddleColor;
storage.ballColor = visualSettings.ballColor;
storage.lavaColor = visualSettings.lavaColor;
storage.backgroundColor = visualSettings.backgroundColor;
storage.textColor = visualSettings.textColor;
storage.uiColor = visualSettings.uiColor;
storage.particleColors = visualSettings.particleColors;
storage.trailsEnabled = visualSettings.trailsEnabled;
storage.particlesEnabled = visualSettings.particlesEnabled;
storage.screenShakeEnabled = visualSettings.screenShakeEnabled;
storage.lavaAnimationEnabled = visualSettings.lavaAnimationEnabled;
storage.ballSize = visualSettings.ballSize;
storage.paddleSize = visualSettings.paddleSize;
storage.textSize = visualSettings.textSize;
storage.particleSize = visualSettings.particleSize;
storage.trailIntensity = visualSettings.trailIntensity;
storage.particleIntensity = visualSettings.particleIntensity;
storage.animationSpeed = visualSettings.animationSpeed;
}
// Function to apply visual settings to game elements
function applyVisualSettings() {
// Update game colors
gameColors.paddle = visualSettings.paddleColor;
gameColors.ball = visualSettings.ballColor;
gameColors.lava = visualSettings.lavaColor;
// Update background color
if (game) {
game.setBackgroundColor(visualSettings.backgroundColor);
}
if (background) {
background.tint = visualSettings.backgroundColor;
}
if (menuBackground) {
menuBackground.getChildAt(1).tint = visualSettings.backgroundColor;
}
}
// 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;
var backButton;
// Default sound settings
var soundEnabled = storage.soundEnabled !== undefined ? storage.soundEnabled : true;
var musicEnabled = storage.musicEnabled !== undefined ? storage.musicEnabled : true;
// 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 = visualSettings.lavaColor;
game.addChild(lava);
// Start lava animation
animateLava();
// Initialize paddle
paddle = new Paddle();
paddle.x = 2048 / 2;
paddle.y = 2732 - 250;
paddle.getChildAt(0).tint = visualSettings.paddleColor;
// Apply paddle size scaling - set absolute scale to prevent growth
paddle.scale.set(visualSettings.paddleSize, visualSettings.paddleSize);
// Ensure paddle tint is also reset
paddle.getChildAt(0).tint = visualSettings.paddleColor;
paddle.getChildAt(1).tint = visualSettings.paddleColor;
paddle.getChildAt(2).tint = visualSettings.paddleColor;
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 game mode indicator
var modeIndicator = new Text2(gameModeConfigs[currentGameMode].name, {
size: 50,
fill: 0x003087
});
modeIndicator.anchor.set(0, 0);
modeIndicator.x = 48;
modeIndicator.y = 130;
game.addChild(modeIndicator);
// 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
// Create back button
backButton = new Text2('← Menu', {
size: 50,
fill: 0xFFB612
});
backButton.anchor.set(0, 0);
backButton.x = 120; // Position to avoid platform menu icon
backButton.y = 120;
backButton.interactive = true;
backButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
// Stop current game
gameActive = false;
// Clear balls
for (var i = 0; i < balls.length; i++) {
balls[i].destroy();
}
balls = [];
ballsInPlay = 0;
// Hide game elements
hideGameElements();
// Show menu
showMenu();
};
game.addChild(backButton);
}
// 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;
backButton.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 and floating motion
function animateTitle2() {
// Bounce up animation
tween(titleNumber, {
y: 320,
// Move up slightly
scaleX: 1.1,
scaleY: 1.1,
rotation: 0.05
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Bounce down animation
tween(titleNumber, {
y: 350,
// Back to original position
scaleX: 1,
scaleY: 1,
rotation: -0.05
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: animateTitle2 // Loop the animation
});
}
});
}
// Start the animation
animateTitle2();
// Add floating animation for the "2"
function floatTitle2() {
tween(titleNumber, {
x: 1024 + Math.random() * 20 - 10
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: floatTitle2
});
}
floatTitle2();
// 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) {
// Add pulsing glow effect
var _pulseStartButton = function pulseStartButton() {
if (!startButton.isHovered) return;
tween(startButton, {
alpha: 1.0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!startButton.isHovered) return;
tween(startButton, {
alpha: 0.85
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: _pulseStartButton
});
}
});
};
startButton.isHovered = true;
// Apply sophisticated hover animation with glow effect
tween(startButton, {
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.9,
y: startButton.y - 5
}, {
duration: 150,
easing: tween.easeOut
});
_pulseStartButton();
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
});
}
});
}
// Initial entrance animation for start button
startButton.alpha = 0;
startButton.y = 1300;
tween(startButton, {
alpha: 1,
y: 1200
}, {
duration: 500,
easing: tween.bounceOut
});
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;
// Add hover animation for settings button
settingsButton.isHovered = false;
settingsButton.move = function (x, y, obj) {
// Check if cursor is over the button
if (x >= settingsButton.x - settingsButton.width / 2 && x <= settingsButton.x + settingsButton.width / 2 && y >= settingsButton.y - settingsButton.height / 2 && y <= settingsButton.y + settingsButton.height / 2) {
if (!settingsButton.isHovered) {
settingsButton.isHovered = true;
tween(settingsButton, {
scaleX: 1.05,
scaleY: 1.05,
rotation: 0.02
}, {
duration: 200,
easing: tween.easeOut
});
}
} else {
if (settingsButton.isHovered) {
settingsButton.isHovered = false;
tween(settingsButton, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
}
};
// Initial entrance animation
settingsButton.alpha = 0;
settingsButton.y = 1500;
tween(settingsButton, {
alpha: 1,
y: 1400
}, {
duration: 600,
easing: tween.bounceOut
});
game.addChild(settingsButton);
// Set up event handlers for menu
startButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
hideMenu();
showModeSelect();
};
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 hideGameElements() {
if (background) {
background.visible = false;
}
if (lava) {
lava.visible = false;
}
if (paddle) {
paddle.visible = false;
}
if (scoreTxt) {
scoreTxt.visible = false;
}
if (levelTxt) {
levelTxt.visible = false;
}
if (comboTxt) {
comboTxt.visible = false;
}
if (highScoreTxt) {
highScoreTxt.visible = false;
}
if (hitCounterText) {
hitCounterText.visible = false;
}
if (speedTxt) {
speedTxt.visible = false;
}
if (backButton) {
backButton.visible = false;
}
}
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
}
});
}
}
// Mode selection screen
var modeSelectPanel;
var modeButtons = [];
function showModeSelect() {
currentState = GAME_STATE.MODE_SELECT;
// Create mode select panel if it doesn't exist
if (!modeSelectPanel) {
modeSelectPanel = new Container();
game.addChild(modeSelectPanel);
// Mode select background
var modeBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
tint: 0xFFFFFF
});
modeSelectPanel.addChild(modeBg);
// Mode select title
var modeTitle = new Text2('Select Game Mode', {
size: 120,
fill: 0x101820
});
modeTitle.anchor.set(0.5, 0);
modeTitle.x = 1024;
modeTitle.y = 100;
modeSelectPanel.addChild(modeTitle);
// Create mode buttons
var modeKeys = Object.keys(GAME_MODES);
for (var i = 0; i < modeKeys.length; i++) {
var modeKey = modeKeys[i];
var modeId = GAME_MODES[modeKey];
var config = gameModeConfigs[modeId];
var yPos = 400 + i * 320;
// Mode button container
var modeContainer = new Container();
modeContainer.x = 1024;
modeContainer.y = yPos;
modeSelectPanel.addChild(modeContainer);
// Mode button background with rounded corners
var buttonBg = new Container();
// Main rectangle body
var mainRect = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
mainRect.width = 1700; // Slightly smaller for rounded ends
mainRect.height = 280;
mainRect.tint = 0x101820;
buttonBg.addChild(mainRect);
// Left rounded corner (circle)
var leftCorner = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 280,
height: 280,
tint: 0x101820
});
leftCorner.x = -850; // Position at left edge
leftCorner.y = 0;
buttonBg.addChild(leftCorner);
// Right rounded corner (circle)
var rightCorner = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 280,
height: 280,
tint: 0x101820
});
rightCorner.x = 850; // Position at right edge
rightCorner.y = 0;
buttonBg.addChild(rightCorner);
modeContainer.addChild(buttonBg);
// Mode name
var modeName = new Text2(config.name, {
size: 80,
fill: 0xFFB612
});
modeName.anchor.set(0.5, 0);
modeName.x = 0;
modeName.y = -80;
modeContainer.addChild(modeName);
// Mode description
var modeDesc = new Text2(config.description, {
size: 50,
fill: 0xFFFFFF
});
modeDesc.anchor.set(0.5, 0);
modeDesc.x = 0;
modeDesc.y = -20;
modeContainer.addChild(modeDesc);
// Make button interactive with hover effects
modeContainer.interactive = true;
modeContainer.modeId = modeId;
modeContainer.isHovered = false;
// Add entrance animation with staggered delay
modeContainer.alpha = 0;
modeContainer.x = 1024 + 300;
tween(modeContainer, {
alpha: 1,
x: 1024
}, {
duration: 400 + i * 100,
easing: tween.bounceOut
});
// Hover animation
modeContainer.move = function (x, y, obj) {
if (x >= this.x - 900 && x <= this.x + 900 && y >= this.y - 140 && y <= this.y + 140) {
if (!this.isHovered) {
this.isHovered = true;
tween(this, {
scaleX: 1.02,
scaleY: 1.02,
y: this.y - 5
}, {
duration: 200,
easing: tween.easeOut
});
}
} else {
if (this.isHovered) {
this.isHovered = false;
tween(this, {
scaleX: 1.0,
scaleY: 1.0,
y: yPos
}, {
duration: 200,
easing: tween.easeOut
});
}
}
};
modeContainer.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
currentGameMode = this.modeId;
hideModeSelect();
// Stop menu music with fade out
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0.6,
end: 0,
duration: 500
}
});
}
initializeGameElements();
startGame();
};
modeButtons.push(modeContainer);
}
// Back to menu button
var backToMenuBtn = new Text2('← Back to Menu', {
size: 70,
fill: 0x101820
});
backToMenuBtn.anchor.set(0.5, 0.5);
backToMenuBtn.x = 1024;
backToMenuBtn.y = 2500;
backToMenuBtn.interactive = true;
backToMenuBtn.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
hideModeSelect();
showMenu();
};
modeSelectPanel.addChild(backToMenuBtn);
} else {
modeSelectPanel.visible = true;
}
}
function hideModeSelect() {
if (modeSelectPanel) {
modeSelectPanel.visible = false;
}
}
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
var panelBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
panelBg.width = 2048;
panelBg.height = 2732;
panelBg.tint = 0x101820;
settingsPanel.addChild(panelBg);
// Settings title
var settingsTitle = new Text2('Visual Settings', {
size: 100 * visualSettings.textSize,
fill: visualSettings.uiColor
});
settingsTitle.anchor.set(0.5, 0);
settingsTitle.x = 1024;
settingsTitle.y = 100;
settingsPanel.addChild(settingsTitle);
// Create scrollable settings content
var settingsContent = new Container();
settingsContent.y = 0;
settingsPanel.addChild(settingsContent);
var currentY = 250;
var spacing = 120;
// Color settings section
var colorTitle = new Text2('Colors', {
size: 80 * visualSettings.textSize,
fill: 0xFFB612
});
colorTitle.anchor.set(0, 0);
colorTitle.x = 100;
colorTitle.y = currentY;
settingsContent.addChild(colorTitle);
currentY += spacing;
// Color presets
var colorPresets = [{
name: 'Classic Gold',
paddle: 0xFFB612,
ball: 0xFFB612,
lava: 0xC60C30,
bg: 0xFFFFFF,
text: 0x101820,
ui: 0xFFFFFF
}, {
name: 'Ocean Blue',
paddle: 0x3498DB,
ball: 0x2980B9,
lava: 0xE74C3C,
bg: 0xECF0F1,
text: 0x2C3E50,
ui: 0xFFFFFF
}, {
name: 'Forest Green',
paddle: 0x27AE60,
ball: 0x2ECC71,
lava: 0xE67E22,
bg: 0xF8F9FA,
text: 0x1E3A8A,
ui: 0xFFFFFF
}, {
name: 'Purple Power',
paddle: 0x8E44AD,
ball: 0x9B59B6,
lava: 0xE74C3C,
bg: 0xF4F3FF,
text: 0x4C1D95,
ui: 0xFFFFFF
}, {
name: 'Sunset',
paddle: 0xFF6B35,
ball: 0xFF8C42,
lava: 0xC70025,
bg: 0xFFF8DC,
text: 0x8B0000,
ui: 0xFFFFFF
}];
for (var i = 0; i < colorPresets.length; i++) {
var preset = colorPresets[i];
var presetButton = new Text2(preset.name, {
size: 60 * visualSettings.textSize,
fill: preset.paddle
});
presetButton.anchor.set(0, 0);
presetButton.x = 150;
presetButton.y = currentY;
presetButton.interactive = true;
presetButton.preset = preset;
presetButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Apply preset colors
visualSettings.paddleColor = this.preset.paddle;
visualSettings.ballColor = this.preset.ball;
visualSettings.lavaColor = this.preset.lava;
visualSettings.backgroundColor = this.preset.bg;
visualSettings.textColor = this.preset.text;
visualSettings.uiColor = this.preset.ui;
saveVisualSettings();
applyVisualSettings();
// Refresh settings panel
settingsPanel.destroy();
settingsPanel = null;
showSettings();
};
settingsContent.addChild(presetButton);
currentY += 80;
}
currentY += 40;
// Effects section
var effectsTitle = new Text2('Effects', {
size: 80 * visualSettings.textSize,
fill: 0xFFB612
});
effectsTitle.anchor.set(0, 0);
effectsTitle.x = 100;
effectsTitle.y = currentY;
settingsContent.addChild(effectsTitle);
currentY += spacing;
// Effects toggles
var effectsSettings = [{
key: 'trailsEnabled',
name: 'Ball Trails'
}, {
key: 'particlesEnabled',
name: 'Particles'
}, {
key: 'screenShakeEnabled',
name: 'Screen Shake'
}, {
key: 'lavaAnimationEnabled',
name: 'Lava Animation'
}];
for (var i = 0; i < effectsSettings.length; i++) {
var setting = effectsSettings[i];
var toggleText = new Text2(setting.name + ': ' + (visualSettings[setting.key] ? 'ON' : 'OFF'), {
size: 60 * visualSettings.textSize,
fill: visualSettings[setting.key] ? 0x00FF00 : 0xFF0000
});
toggleText.anchor.set(0, 0);
toggleText.x = 150;
toggleText.y = currentY;
toggleText.interactive = true;
toggleText.settingKey = setting.key;
toggleText.settingName = setting.name;
toggleText.down = function () {
if (soundEnabled) LK.getSound('click').play();
visualSettings[this.settingKey] = !visualSettings[this.settingKey];
saveVisualSettings();
this.setText(this.settingName + ': ' + (visualSettings[this.settingKey] ? 'ON' : 'OFF'));
this.fill = visualSettings[this.settingKey] ? 0x00FF00 : 0xFF0000;
};
settingsContent.addChild(toggleText);
currentY += 80;
}
currentY += 40;
// Size settings section
var sizeTitle = new Text2('Sizes', {
size: 80 * visualSettings.textSize,
fill: 0xFFB612
});
sizeTitle.anchor.set(0, 0);
sizeTitle.x = 100;
sizeTitle.y = currentY;
settingsContent.addChild(sizeTitle);
currentY += spacing;
// Size presets
var sizePresets = [{
name: 'Small',
ball: 0.8,
paddle: 0.8,
text: 0.8,
particle: 0.8
}, {
name: 'Normal',
ball: 1.0,
paddle: 1.0,
text: 1.0,
particle: 1.0
}, {
name: 'Large',
ball: 1.3,
paddle: 1.2,
text: 1.2,
particle: 1.2
}, {
name: 'Extra Large',
ball: 1.6,
paddle: 1.4,
text: 1.4,
particle: 1.4
}];
for (var i = 0; i < sizePresets.length; i++) {
var preset = sizePresets[i];
var sizeButton = new Text2(preset.name, {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
sizeButton.anchor.set(0, 0);
sizeButton.x = 150;
sizeButton.y = currentY;
sizeButton.interactive = true;
sizeButton.preset = preset;
sizeButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Apply size preset
visualSettings.ballSize = this.preset.ball;
visualSettings.paddleSize = this.preset.paddle;
visualSettings.textSize = this.preset.text;
visualSettings.particleSize = this.preset.particle;
saveVisualSettings();
// Refresh settings panel
settingsPanel.destroy();
settingsPanel = null;
showSettings();
};
settingsContent.addChild(sizeButton);
currentY += 80;
}
currentY += 40;
// Reset all settings button
var resetAllButton = new Text2('Reset All Settings', {
size: 70 * visualSettings.textSize,
fill: 0xFF6B6B
});
resetAllButton.anchor.set(0.5, 0.5);
resetAllButton.x = 1024;
resetAllButton.y = currentY;
resetAllButton.interactive = true;
resetAllButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Reset to defaults
visualSettings.paddleColor = 0xFFB612;
visualSettings.ballColor = 0xFFB612;
visualSettings.lavaColor = 0xC60C30;
visualSettings.backgroundColor = 0xFFFFFF;
visualSettings.textColor = 0x101820;
visualSettings.uiColor = 0xFFFFFF;
visualSettings.trailsEnabled = true;
visualSettings.particlesEnabled = true;
visualSettings.screenShakeEnabled = true;
visualSettings.lavaAnimationEnabled = true;
visualSettings.ballSize = 1.0;
visualSettings.paddleSize = 1.0;
visualSettings.textSize = 1.0;
visualSettings.particleSize = 1.0;
visualSettings.trailIntensity = 1.0;
visualSettings.particleIntensity = 1.0;
visualSettings.animationSpeed = 1.0;
saveVisualSettings();
applyVisualSettings();
// Refresh settings panel
settingsPanel.destroy();
settingsPanel = null;
showSettings();
};
settingsContent.addChild(resetAllButton);
currentY += 120;
// High score display
var highScoreDisplay = new Text2('High Score: ' + highScore, {
size: 60 * visualSettings.textSize,
fill: visualSettings.uiColor
});
highScoreDisplay.anchor.set(0.5, 0.5);
highScoreDisplay.x = 1024;
highScoreDisplay.y = currentY;
settingsContent.addChild(highScoreDisplay);
currentY += 100;
// Reset high score button
var resetScoreButton = new Text2('Reset High Score', {
size: 60 * visualSettings.textSize,
fill: 0xFF6B6B
});
resetScoreButton.anchor.set(0.5, 0.5);
resetScoreButton.x = 1024;
resetScoreButton.y = currentY;
resetScoreButton.interactive = true;
resetScoreButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
showResetConfirmation();
};
settingsContent.addChild(resetScoreButton);
currentY += 100;
// Down arrow for sound settings
var soundSettingsArrow = new Text2('Sound Settings ↓', {
size: 70 * visualSettings.textSize,
fill: 0xFFB612
});
soundSettingsArrow.anchor.set(0.5, 0.5);
soundSettingsArrow.x = 1024;
soundSettingsArrow.y = currentY;
soundSettingsArrow.interactive = true;
soundSettingsArrow.down = function () {
if (soundEnabled) LK.getSound('click').play();
showSoundSettings();
};
settingsContent.addChild(soundSettingsArrow);
currentY += 100;
// Back to menu button
var backButton = new Text2('← Back to Menu', {
size: 70 * visualSettings.textSize,
fill: visualSettings.uiColor
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = currentY;
backButton.interactive = true;
backButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Animate settings panel exit
tween(settingsPanel, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
settingsPanel.visible = false;
showMenu();
}
});
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
}
};
settingsContent.addChild(backButton);
// Make settings panel scrollable
settingsPanel.interactive = true;
settingsPanel.scrollY = 0;
settingsPanel.move = function (x, y, obj) {
// Simple scroll implementation
if (obj && obj.event && obj.event.movementY) {
settingsContent.y += obj.event.movementY * 2;
settingsContent.y = Math.max(Math.min(0, settingsContent.y), -(currentY - 2732 + 200));
}
};
} else {
settingsPanel.visible = true;
}
// Settings panel enter animation
settingsPanel.alpha = 0;
settingsPanel.scale.set(0.8, 0.8);
tween(settingsPanel, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeOut
});
}
// Function to create particles at click location
function createClickParticles(x, y) {
// Only create particles if enabled
if (!visualSettings.particlesEnabled) {
return;
}
// Create 15-20 particles for a visually impressive effect, adjusted by intensity
var baseCount = 15 + Math.floor(Math.random() * 6);
var particleCount = Math.floor(baseCount * visualSettings.particleIntensity);
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) {
// Only create particles if enabled
if (!visualSettings.particlesEnabled) {
return;
}
// Create 8-12 particles for collision effect - fewer than click particles, adjusted by intensity
var baseCount = 8 + Math.floor(Math.random() * 5);
var particleCount = Math.floor(baseCount * visualSettings.particleIntensity);
for (var i = 0; i < particleCount; i++) {
var particle = new Particle();
particle.x = x;
particle.y = y;
particle.active = true;
var collisionScale = 0.3 * visualSettings.particleSize * visualSettings.particleIntensity;
particle.scale.set(collisionScale, collisionScale); // 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 = visualSettings.ballColor;
// Visual indicator of speed - make ball slightly smaller as it gets faster, adjusted by ball size setting
var scale = Math.max(0.6, 1 - (speedMultiplier - 1) * 0.15) * visualSettings.ballSize;
// Enhanced multi-stage animated entrance effect
ball.alpha = 0;
ball.scale.set(0, 0);
ball.rotation = Math.PI * 2;
// Stage 1: Dramatic entrance with spin
tween(ball, {
alpha: 0.7,
scaleX: scale * 1.3,
scaleY: scale * 1.3,
rotation: 0
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Stage 2: Settle to final size with bounce
tween(ball, {
alpha: 1,
scaleX: scale,
scaleY: scale
}, {
duration: 300,
easing: tween.bounceOut
});
}
});
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.up = function (x, y, obj) {
// Handle volume slider up events
if (game.sliderUpHandler) {
game.sliderUpHandler(x, y, obj);
}
};
game.move = function (x, y, obj) {
if (currentState === GAME_STATE.PLAYING) {
paddle.x = x;
}
// Handle volume slider movements
if (game.sliderMoveHandler) {
game.sliderMoveHandler(x, y, obj);
}
};
// 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));
}
// Create balls based on game mode
if (currentGameMode === GAME_MODES.MULTI_BALL) {
// Multi-ball mode: maintain multiple balls
while (ballsInPlay < maxBalls) {
createBall();
}
} else {
// Other modes: 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
// Enhanced paddle hit response with sophisticated multi-stage animation
var originalPaddleY = paddle.y;
var baseScale = visualSettings.paddleSize; // Use base scale from settings
// Stage 1: Impact compression with rotation
tween(paddle, {
scaleY: 0.7 * baseScale,
scaleX: 1.2 * baseScale,
y: originalPaddleY + 12,
rotation: ball.spin * 0.15,
alpha: 0.9
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
// Stage 2: Elastic rebound with overshoot
tween(paddle, {
scaleY: 1.1 * baseScale,
scaleX: 0.95 * baseScale,
y: originalPaddleY - 5,
rotation: ball.spin * 0.05,
alpha: 1.0
}, {
duration: 140,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Stage 3: Final settle with subtle bounce
tween(paddle, {
scaleX: baseScale,
scaleY: baseScale,
y: originalPaddleY,
rotation: 0
}, {
duration: 120,
easing: tween.bounceOut
});
}
});
}
});
// 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
// Enhanced score animation with multi-stage effects and particle burst
var originalFill = scoreTxt.fill;
var scoreColors = [0xFFB612, 0xFF6B35, 0xE74C3C, 0x8E44AD, 0x3498DB];
var colorIndex = 0;
// Stage 1: Dramatic scale up with rotation and glow effect
tween(scoreTxt, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.85,
rotation: 0.1
}, {
duration: 120,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Stage 2: Color cycling effect
function cycleColors() {
if (colorIndex < scoreColors.length) {
scoreTxt.fill = scoreColors[colorIndex];
colorIndex++;
LK.setTimeout(cycleColors, 50);
} else {
// Stage 3: Final settle with bounce
tween(scoreTxt, {
scaleX: 1,
scaleY: 1,
alpha: 1,
rotation: 0
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Reset fill color after animation
scoreTxt.fill = originalFill !== undefined ? originalFill : 0x101820;
}
});
}
}
cycleColors();
// Create score particle burst
for (var p = 0; p < 8; p++) {
var scoreParticle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: scoreTxt.x,
y: scoreTxt.y,
tint: scoreColors[Math.floor(Math.random() * scoreColors.length)]
});
scoreParticle.scale.set(0.3, 0.3);
var angle = Math.PI * 2 * p / 8;
var distance = 100 + Math.random() * 50;
game.addChild(scoreParticle);
tween(scoreParticle, {
x: scoreTxt.x + Math.cos(angle) * distance,
y: scoreTxt.y + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
scoreParticle.destroy();
}
});
}
}
});
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();
}
// Enhanced lava death animation with multiple effects
// Screen shake effect if enabled
if (visualSettings.screenShakeEnabled) {
var originalGameY = game.y;
for (var shake = 0; shake < 5; shake++) {
LK.setTimeout(function () {
game.y = originalGameY + (Math.random() - 0.5) * 20;
LK.setTimeout(function () {
game.y = originalGameY;
}, 50);
}, shake * 100);
}
}
// Enhanced lava flash with ripple effect
tween(lava, {
tint: 0xffffff,
scaleY: 1.3,
scaleX: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Ripple effect
tween(lava, {
scaleY: 0.8,
scaleX: 0.95
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lava, {
tint: visualSettings.lavaColor,
scaleY: 1.0,
scaleX: 1.0
}, {
duration: 250,
easing: tween.elasticOut
});
}
});
}
});
// 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);
// Enhanced level up animation with multiple effects
levelUpTxt.alpha = 0;
levelUpTxt.scale.set(0.1, 0.1);
levelUpTxt.rotation = Math.PI * 2;
// Multi-stage animation
tween(levelUpTxt, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
rotation: 0
}, {
duration: 400,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Pulsing effect
function pulse() {
tween(levelUpTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(levelUpTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: pulse
});
}
});
}
pulse();
// Final fade out after displaying
LK.setTimeout(function () {
tween(levelUpTxt, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: -Math.PI,
y: levelUpTxt.y - 100
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
levelUpTxt.destroy();
}
});
}, 2000);
}
});
// 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
}
});
}
// Get current mode configuration
var modeConfig = gameModeConfigs[currentGameMode];
// Increase difficulty based on game mode
speedMultiplier += modeConfig.speedIncrease + level * 0.08;
maxBalls = modeConfig.maxBalls;
// Reset hit counter for next level
currentHits = 0;
// Set hits required for next level based on mode
hitsToNextLevel = modeConfig.hitsPerLevel + (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 with dramatic entrance
var newHighScoreTxt = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0xFFB612
});
newHighScoreTxt.anchor.set(0.5, 0.5);
newHighScoreTxt.x = 2048 / 2; // True center horizontally
newHighScoreTxt.y = 1000;
// Dramatic entrance animation
newHighScoreTxt.alpha = 0;
newHighScoreTxt.scale.set(0.1, 0.1);
newHighScoreTxt.rotation = Math.PI * 4;
LK.gui.addChild(newHighScoreTxt);
// Multi-stage dramatic animation
tween(newHighScoreTxt, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5,
rotation: 0
}, {
duration: 500,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Pulsing rainbow effect simulation
var colors = [0xFFB612, 0xFF6B35, 0xE74C3C, 0x8E44AD, 0x3498DB, 0x27AE60];
var colorIndex = 0;
function colorCycle() {
newHighScoreTxt.fill = colors[colorIndex];
colorIndex = (colorIndex + 1) % colors.length;
if (newHighScoreTxt.parent) {
LK.setTimeout(colorCycle, 200);
}
}
colorCycle();
// Final dramatic exit
LK.setTimeout(function () {
tween(newHighScoreTxt, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.PI * 2,
y: 600
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
newHighScoreTxt.destroy();
}
});
}, 1000);
}
});
// Update high score display
if (highScoreTxt) {
highScoreTxt.setText('High Score: ' + highScore);
}
}
// Enhanced game over effects with particle explosion
LK.effects.flashScreen(0xff0000, 500);
// Create dramatic particle explosion from center
for (var explosion = 0; explosion < 25; explosion++) {
var explodeParticle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
tint: [0xFF6B35, 0xE74C3C, 0xFFB612, 0x8E44AD][Math.floor(Math.random() * 4)]
});
explodeParticle.scale.set(0.8 + Math.random() * 0.4, 0.8 + Math.random() * 0.4);
game.addChild(explodeParticle);
var explodeAngle = Math.random() * Math.PI * 2;
var explodeDistance = 200 + Math.random() * 300;
var explodeDuration = 800 + Math.random() * 400;
tween(explodeParticle, {
x: 1024 + Math.cos(explodeAngle) * explodeDistance,
y: 1366 + Math.sin(explodeAngle) * explodeDistance,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.random() * Math.PI * 4
}, {
duration: explodeDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
explodeParticle.destroy();
}
});
}
// 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() {
// Get current mode configuration
var modeConfig = gameModeConfigs[currentGameMode];
// Reset variables
score = 0;
level = 1;
combo = 0;
lastBallHit = 0;
gameActive = true;
speedMultiplier = modeConfig.initialSpeed;
maxBalls = modeConfig.maxBalls;
ballsInPlay = 0;
currentHits = 0;
hitsToNextLevel = modeConfig.hitsPerLevel;
// Update UI with smooth animations
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
// Animate score text entrance
scoreTxt.alpha = 0;
scoreTxt.scale.set(0.5, 0.5);
tween(scoreTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.bounceOut
});
levelTxt.setText('Level: 1');
// Animate level text
levelTxt.alpha = 0;
tween(levelTxt, {
alpha: 1
}, {
duration: 300
});
comboTxt.setText('');
comboTxt.alpha = 0;
hitCounterText.setText('0/' + hitsToNextLevel);
// Animate hit counter
hitCounterText.alpha = 0;
hitCounterText.y = 100;
tween(hitCounterText, {
alpha: 1,
y: 150
}, {
duration: 500,
easing: tween.elasticOut
});
// 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;
}
// Apply visual settings on initialization
applyVisualSettings();
// Sound settings panel
var soundSettingsPanel;
function showSoundSettings() {
// Create sound settings panel if it doesn't exist
if (!soundSettingsPanel) {
soundSettingsPanel = new Container();
game.addChild(soundSettingsPanel);
// Sound settings panel background
var soundPanelBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
soundPanelBg.width = 2048;
soundPanelBg.height = 2732;
soundPanelBg.tint = 0x2C3E50;
soundSettingsPanel.addChild(soundPanelBg);
// Sound settings title
var soundTitle = new Text2('Sound Settings', {
size: 100 * visualSettings.textSize,
fill: 0xFFB612
});
soundTitle.anchor.set(0.5, 0);
soundTitle.x = 1024;
soundTitle.y = 200;
soundSettingsPanel.addChild(soundTitle);
var soundCurrentY = 400;
var soundSpacing = 150;
// Sound Effects Toggle
var soundEffectsText = new Text2('Sound Effects: ' + (soundEnabled ? 'ON' : 'OFF'), {
size: 80 * visualSettings.textSize,
fill: soundEnabled ? 0x00FF00 : 0xFF0000
});
soundEffectsText.anchor.set(0.5, 0.5);
soundEffectsText.x = 1024;
soundEffectsText.y = soundCurrentY;
soundEffectsText.interactive = true;
soundEffectsText.down = function () {
if (soundEnabled) LK.getSound('click').play();
soundEnabled = !soundEnabled;
storage.soundEnabled = soundEnabled;
this.setText('Sound Effects: ' + (soundEnabled ? 'ON' : 'OFF'));
this.fill = soundEnabled ? 0x00FF00 : 0xFF0000;
};
soundSettingsPanel.addChild(soundEffectsText);
soundCurrentY += soundSpacing;
// Music Toggle
var musicText = new Text2('Music: ' + (musicEnabled ? 'ON' : 'OFF'), {
size: 80 * visualSettings.textSize,
fill: musicEnabled ? 0x00FF00 : 0xFF0000
});
musicText.anchor.set(0.5, 0.5);
musicText.x = 1024;
musicText.y = soundCurrentY;
musicText.interactive = true;
musicText.down = function () {
if (soundEnabled) LK.getSound('click').play();
musicEnabled = !musicEnabled;
storage.musicEnabled = musicEnabled;
this.setText('Music: ' + (musicEnabled ? 'ON' : 'OFF'));
this.fill = musicEnabled ? 0x00FF00 : 0xFF0000;
if (!musicEnabled) {
LK.stopMusic();
} else if (currentState === GAME_STATE.MENU) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
} else if (currentState === GAME_STATE.PLAYING && level >= 4) {
LK.playMusic('heavyMetalMusic', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
} else if (currentState === GAME_STATE.PLAYING) {
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
};
soundSettingsPanel.addChild(musicText);
soundCurrentY += soundSpacing;
// Volume controls section
var volumeTitle = new Text2('Volume Controls', {
size: 70 * visualSettings.textSize,
fill: 0xFFB612
});
volumeTitle.anchor.set(0.5, 0);
volumeTitle.x = 1024;
volumeTitle.y = soundCurrentY;
soundSettingsPanel.addChild(volumeTitle);
soundCurrentY += 100;
// Initialize volume settings if not present
if (storage.masterVolume === undefined) storage.masterVolume = 0.7;
if (storage.soundVolume === undefined) storage.soundVolume = 1.0;
if (storage.musicVolume === undefined) storage.musicVolume = 0.6;
// Master Volume Slider
var masterVolumeLabel = new Text2('Master Volume: ' + Math.round(storage.masterVolume * 100) + '%', {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
masterVolumeLabel.anchor.set(0.5, 0.5);
masterVolumeLabel.x = 1024;
masterVolumeLabel.y = soundCurrentY;
soundSettingsPanel.addChild(masterVolumeLabel);
soundCurrentY += 80;
// Master volume slider track
var masterTrack = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: soundCurrentY
});
masterTrack.width = 800;
masterTrack.height = 20;
masterTrack.tint = 0x333333;
soundSettingsPanel.addChild(masterTrack);
// Master volume slider handle
var masterHandle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50,
tint: 0xFFB612
});
masterHandle.x = 624 + storage.masterVolume * 800; // Position based on volume
masterHandle.y = soundCurrentY;
masterHandle.interactive = true;
masterHandle.isDragging = false;
masterHandle.volumeType = 'master';
masterHandle.label = masterVolumeLabel;
masterHandle.down = function () {
this.isDragging = true;
if (soundEnabled) LK.getSound('click').play();
};
masterHandle.move = function (x, y, obj) {
if (this.isDragging) {
var trackLeft = 624;
var trackRight = 1424;
this.x = Math.max(trackLeft, Math.min(trackRight, x));
var volume = (this.x - trackLeft) / 800;
storage.masterVolume = volume;
this.label.setText('Master Volume: ' + Math.round(volume * 100) + '%');
}
};
masterHandle.up = function () {
this.isDragging = false;
};
soundSettingsPanel.addChild(masterHandle);
soundCurrentY += 100;
// Sound Effects Volume Slider
var soundVolumeLabel = new Text2('Sound Effects: ' + Math.round(storage.soundVolume * 100) + '%', {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
soundVolumeLabel.anchor.set(0.5, 0.5);
soundVolumeLabel.x = 1024;
soundVolumeLabel.y = soundCurrentY;
soundSettingsPanel.addChild(soundVolumeLabel);
soundCurrentY += 80;
// Sound effects volume slider track
var soundTrack = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: soundCurrentY
});
soundTrack.width = 800;
soundTrack.height = 20;
soundTrack.tint = 0x333333;
soundSettingsPanel.addChild(soundTrack);
// Sound effects volume slider handle
var soundHandle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50,
tint: 0x3498DB
});
soundHandle.x = 624 + storage.soundVolume * 800;
soundHandle.y = soundCurrentY;
soundHandle.interactive = true;
soundHandle.isDragging = false;
soundHandle.volumeType = 'sound';
soundHandle.label = soundVolumeLabel;
soundHandle.down = function () {
this.isDragging = true;
if (soundEnabled) LK.getSound('click').play();
};
soundHandle.move = function (x, y, obj) {
if (this.isDragging) {
var trackLeft = 624;
var trackRight = 1424;
this.x = Math.max(trackLeft, Math.min(trackRight, x));
var volume = (this.x - trackLeft) / 800;
storage.soundVolume = volume;
this.label.setText('Sound Effects: ' + Math.round(volume * 100) + '%');
}
};
soundHandle.up = function () {
this.isDragging = false;
};
soundSettingsPanel.addChild(soundHandle);
soundCurrentY += 100;
// Music Volume Slider
var musicVolumeLabel = new Text2('Music Volume: ' + Math.round(storage.musicVolume * 100) + '%', {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
musicVolumeLabel.anchor.set(0.5, 0.5);
musicVolumeLabel.x = 1024;
musicVolumeLabel.y = soundCurrentY;
soundSettingsPanel.addChild(musicVolumeLabel);
soundCurrentY += 80;
// Music volume slider track
var musicTrack = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: soundCurrentY
});
musicTrack.width = 800;
musicTrack.height = 20;
musicTrack.tint = 0x333333;
soundSettingsPanel.addChild(musicTrack);
// Music volume slider handle
var musicHandle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50,
tint: 0xE74C3C
});
musicHandle.x = 624 + storage.musicVolume * 800;
musicHandle.y = soundCurrentY;
musicHandle.interactive = true;
musicHandle.isDragging = false;
musicHandle.volumeType = 'music';
musicHandle.label = musicVolumeLabel;
musicHandle.down = function () {
this.isDragging = true;
if (soundEnabled) LK.getSound('click').play();
};
musicHandle.move = function (x, y, obj) {
if (this.isDragging) {
var trackLeft = 624;
var trackRight = 1424;
this.x = Math.max(trackLeft, Math.min(trackRight, x));
var volume = (this.x - trackLeft) / 800;
storage.musicVolume = volume;
this.label.setText('Music Volume: ' + Math.round(volume * 100) + '%');
// Apply volume change immediately to current music
if (musicEnabled && currentState === GAME_STATE.MENU) {
LK.playMusic('menuMusic', {
fade: {
start: volume,
end: volume,
duration: 100
}
});
} else if (musicEnabled && currentState === GAME_STATE.PLAYING && level >= 4) {
LK.playMusic('heavyMetalMusic', {
fade: {
start: volume,
end: volume,
duration: 100
}
});
} else if (musicEnabled && currentState === GAME_STATE.PLAYING) {
LK.playMusic('gameMusic', {
fade: {
start: volume,
end: volume,
duration: 100
}
});
}
}
};
musicHandle.up = function () {
this.isDragging = false;
};
soundSettingsPanel.addChild(musicHandle);
soundCurrentY += 120;
// Add global move handler to catch slider movements
game.sliderMoveHandler = function (x, y, obj) {
// Find all slider handles and update them
var children = soundSettingsPanel.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.isDragging && child.move) {
child.move(x, y, obj);
}
}
};
// Add global up handler to stop dragging
game.sliderUpHandler = function (x, y, obj) {
var children = soundSettingsPanel.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.isDragging && child.up) {
child.up(x, y, obj);
}
}
};
// Back to visual settings button
var backToVisualBtn = new Text2('← Back to Visual Settings', {
size: 70 * visualSettings.textSize,
fill: 0xFFB612
});
backToVisualBtn.anchor.set(0.5, 0.5);
backToVisualBtn.x = 1024;
backToVisualBtn.y = soundCurrentY + 100;
backToVisualBtn.interactive = true;
backToVisualBtn.down = function () {
if (soundEnabled) LK.getSound('click').play();
hideSoundSettings();
};
soundSettingsPanel.addChild(backToVisualBtn);
} else {
soundSettingsPanel.visible = true;
}
// Page flip animation - slide from right to left
soundSettingsPanel.x = 2048;
soundSettingsPanel.alpha = 1;
tween(soundSettingsPanel, {
x: 0
}, {
duration: 500,
easing: tween.easeInOut
});
// Hide visual settings with slide animation
tween(settingsPanel, {
x: -2048
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
settingsPanel.visible = false;
settingsPanel.x = 0; // Reset position for next time
}
});
}
function hideSoundSettings() {
if (soundSettingsPanel) {
// Page flip animation back - slide from left to right
tween(soundSettingsPanel, {
x: 2048
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
soundSettingsPanel.visible = false;
soundSettingsPanel.x = 0; // Reset position
}
});
// Show visual settings with slide animation
settingsPanel.visible = true;
settingsPanel.x = -2048;
tween(settingsPanel, {
x: 0
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
// Game will start when player presses the Play button in the menu
// Function to animate the lava with multiple effects
function animateLava() {
if (!visualSettings.lavaAnimationEnabled) {
// Just apply the static lava color if animations are disabled
if (lava) {
lava.tint = visualSettings.lavaColor;
}
return;
}
// Pulsing scale animation
function pulseLava() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
scaleY: 1.05,
scaleX: 1.02
}, {
duration: (800 + Math.random() * 400) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
scaleY: 0.98,
scaleX: 1.01
}, {
duration: (600 + Math.random() * 400) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: pulseLava
});
}
});
}
// Color shifting animation - based on the selected lava color
function shiftLavaColor() {
if (!visualSettings.lavaAnimationEnabled) return;
// Create variations of the selected lava color
var baseLava = visualSettings.lavaColor;
var colors = [baseLava, baseLava + 0x111111, baseLava - 0x111111, baseLava + 0x220000, baseLava - 0x110011];
var randomColor = colors[Math.floor(Math.random() * colors.length)];
tween(lava, {
tint: randomColor
}, {
duration: (1500 + Math.random() * 1000) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: shiftLavaColor
});
}
// Subtle vertical movement to simulate bubbling
function bubbleLava() {
if (!visualSettings.lavaAnimationEnabled) return;
var originalY = lava.y;
tween(lava, {
y: originalY - 5 - Math.random() * 8
}, {
duration: (400 + Math.random() * 300) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
y: originalY + 3 + Math.random() * 4
}, {
duration: (500 + Math.random() * 200) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
y: originalY
}, {
duration: (300 + Math.random() * 200) / visualSettings.animationSpeed,
easing: tween.easeOut,
onFinish: function onFinish() {
// Wait a bit before next bubble cycle
LK.setTimeout(bubbleLava, (500 + Math.random() * 1500) / visualSettings.animationSpeed);
}
});
}
});
}
});
}
// Start all animations
pulseLava();
shiftLavaColor();
LK.setTimeout(bubbleLava, Math.random() * 1000 / visualSettings.animationSpeed); // Start bubbling with random delay
} /****
* 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
});
// Only create trail if trails are enabled
if (!visualSettings.trailsEnabled) {
self.trailCounter = 0;
return;
}
// Adjust trail size based on ball speed and settings
var speedFactor = Math.min(1, (Math.abs(self.speedX) + Math.abs(self.speedY)) / 40);
var trailSize = (0.7 - speedFactor * 0.2) * visualSettings.ballSize * visualSettings.trailIntensity;
trail.scale.set(self.scale.x * trailSize, self.scale.y * trailSize);
trail.tint = visualSettings.ballColor;
// Higher alpha for faster speeds, adjusted by trail intensity
trail.alpha = (0.5 + speedFactor * 0.3) * visualSettings.trailIntensity;
game.addChildAt(trail, game.getChildIndex(self));
// Fade out and remove trail - faster trails disappear quicker, adjusted by animation speed
var trailDuration = (300 - speedFactor * 150) / visualSettings.animationSpeed;
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 based on settings
var particleScale = 0.5 * visualSettings.particleSize * visualSettings.particleIntensity;
self.scale.set(particleScale, particleScale);
// Particle colors from settings
particleGraphic.tint = visualSettings.particleColors[Math.floor(Math.random() * visualSettings.particleColors.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 enhanced movement patterns
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%
// Add swirling motion for more dynamic particles
var swirl = Math.sin(self.lifetime * 0.1) * 0.5;
self.x += swirl;
// Rotate particle with varying speeds
particleGraphic.rotation += self.rotationSpeed;
// Scale animation during lifetime
var scaleMultiplier = 1 + Math.sin(self.lifetime * 0.15) * 0.1;
self.scale.set(particleScale * scaleMultiplier, particleScale * scaleMultiplier);
// Update lifetime
self.lifetime++;
// Enhanced fade out with pulsing effect
if (self.lifetime > self.maxLifetime * 0.7) {
var fadeProgress = (self.lifetime - self.maxLifetime * 0.7) / (self.maxLifetime * 0.3);
var pulse = 1 + Math.sin(self.lifetime * 0.3) * 0.1;
self.alpha = (1 - fadeProgress) * pulse;
}
// 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
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
var GAME_STATE = {
MENU: 0,
PLAYING: 1,
SETTINGS: 2,
MODE_SELECT: 3
};
var currentState = GAME_STATE.MENU;
// Game modes
var GAME_MODES = {
CLASSIC: 0,
SPEED_DEMON: 1,
MULTI_BALL: 2,
SURVIVAL: 3
};
var currentGameMode = GAME_MODES.CLASSIC;
// Game mode configurations
var gameModeConfigs = _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, GAME_MODES.CLASSIC, {
name: "Classic Mode",
description: "The original Lava Bounce experience.\nBounce balls and level up gradually.",
initialSpeed: 1.5,
speedIncrease: 0.4,
maxBalls: 1,
hitsPerLevel: 25
}), GAME_MODES.SPEED_DEMON, {
name: "Speed Demon",
description: "Fast-paced action from the start!\nHigh speed, quick reflexes required.",
initialSpeed: 3.0,
speedIncrease: 0.8,
maxBalls: 1,
hitsPerLevel: 15
}), GAME_MODES.MULTI_BALL, {
name: "Multi-Ball Madness",
description: "Multiple balls create chaos!\nManage several balls at once.",
initialSpeed: 1.2,
speedIncrease: 0.3,
maxBalls: 3,
hitsPerLevel: 35
}), GAME_MODES.SURVIVAL, {
name: "Survival Mode",
description: "How long can you last?\nIncreasing difficulty, one life only.",
initialSpeed: 1.0,
speedIncrease: 0.6,
maxBalls: 1,
hitsPerLevel: 20
});
// Visual customization settings with defaults
var visualSettings = {
// Colors
paddleColor: storage.paddleColor || 0xFFB612,
ballColor: storage.ballColor || 0xFFB612,
lavaColor: storage.lavaColor || 0xC60C30,
backgroundColor: storage.backgroundColor || 0xFFFFFF,
textColor: storage.textColor || 0x101820,
uiColor: storage.uiColor || 0xFFFFFF,
particleColors: storage.particleColors || [0xFFB612, 0xC60C30, 0x003087, 0xFFFFFF],
// Effects
trailsEnabled: storage.trailsEnabled !== undefined ? storage.trailsEnabled : true,
particlesEnabled: storage.particlesEnabled !== undefined ? storage.particlesEnabled : true,
screenShakeEnabled: storage.screenShakeEnabled !== undefined ? storage.screenShakeEnabled : true,
lavaAnimationEnabled: storage.lavaAnimationEnabled !== undefined ? storage.lavaAnimationEnabled : true,
// Sizes
ballSize: storage.ballSize || 1.0,
paddleSize: storage.paddleSize || 1.0,
textSize: storage.textSize || 1.0,
particleSize: storage.particleSize || 1.0,
// Visual intensity
trailIntensity: storage.trailIntensity || 1.0,
particleIntensity: storage.particleIntensity || 1.0,
animationSpeed: storage.animationSpeed || 1.0
};
// Default colors for game elements - using customizable settings
var gameColors = {
paddle: visualSettings.paddleColor,
ball: visualSettings.ballColor,
lava: visualSettings.lavaColor
};
// Function to save visual settings
function saveVisualSettings() {
storage.paddleColor = visualSettings.paddleColor;
storage.ballColor = visualSettings.ballColor;
storage.lavaColor = visualSettings.lavaColor;
storage.backgroundColor = visualSettings.backgroundColor;
storage.textColor = visualSettings.textColor;
storage.uiColor = visualSettings.uiColor;
storage.particleColors = visualSettings.particleColors;
storage.trailsEnabled = visualSettings.trailsEnabled;
storage.particlesEnabled = visualSettings.particlesEnabled;
storage.screenShakeEnabled = visualSettings.screenShakeEnabled;
storage.lavaAnimationEnabled = visualSettings.lavaAnimationEnabled;
storage.ballSize = visualSettings.ballSize;
storage.paddleSize = visualSettings.paddleSize;
storage.textSize = visualSettings.textSize;
storage.particleSize = visualSettings.particleSize;
storage.trailIntensity = visualSettings.trailIntensity;
storage.particleIntensity = visualSettings.particleIntensity;
storage.animationSpeed = visualSettings.animationSpeed;
}
// Function to apply visual settings to game elements
function applyVisualSettings() {
// Update game colors
gameColors.paddle = visualSettings.paddleColor;
gameColors.ball = visualSettings.ballColor;
gameColors.lava = visualSettings.lavaColor;
// Update background color
if (game) {
game.setBackgroundColor(visualSettings.backgroundColor);
}
if (background) {
background.tint = visualSettings.backgroundColor;
}
if (menuBackground) {
menuBackground.getChildAt(1).tint = visualSettings.backgroundColor;
}
}
// 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;
var backButton;
// Default sound settings
var soundEnabled = storage.soundEnabled !== undefined ? storage.soundEnabled : true;
var musicEnabled = storage.musicEnabled !== undefined ? storage.musicEnabled : true;
// 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 = visualSettings.lavaColor;
game.addChild(lava);
// Start lava animation
animateLava();
// Initialize paddle
paddle = new Paddle();
paddle.x = 2048 / 2;
paddle.y = 2732 - 250;
paddle.getChildAt(0).tint = visualSettings.paddleColor;
// Apply paddle size scaling - set absolute scale to prevent growth
paddle.scale.set(visualSettings.paddleSize, visualSettings.paddleSize);
// Ensure paddle tint is also reset
paddle.getChildAt(0).tint = visualSettings.paddleColor;
paddle.getChildAt(1).tint = visualSettings.paddleColor;
paddle.getChildAt(2).tint = visualSettings.paddleColor;
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 game mode indicator
var modeIndicator = new Text2(gameModeConfigs[currentGameMode].name, {
size: 50,
fill: 0x003087
});
modeIndicator.anchor.set(0, 0);
modeIndicator.x = 48;
modeIndicator.y = 130;
game.addChild(modeIndicator);
// 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
// Create back button
backButton = new Text2('← Menu', {
size: 50,
fill: 0xFFB612
});
backButton.anchor.set(0, 0);
backButton.x = 120; // Position to avoid platform menu icon
backButton.y = 120;
backButton.interactive = true;
backButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
// Stop current game
gameActive = false;
// Clear balls
for (var i = 0; i < balls.length; i++) {
balls[i].destroy();
}
balls = [];
ballsInPlay = 0;
// Hide game elements
hideGameElements();
// Show menu
showMenu();
};
game.addChild(backButton);
}
// 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;
backButton.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 and floating motion
function animateTitle2() {
// Bounce up animation
tween(titleNumber, {
y: 320,
// Move up slightly
scaleX: 1.1,
scaleY: 1.1,
rotation: 0.05
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Bounce down animation
tween(titleNumber, {
y: 350,
// Back to original position
scaleX: 1,
scaleY: 1,
rotation: -0.05
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: animateTitle2 // Loop the animation
});
}
});
}
// Start the animation
animateTitle2();
// Add floating animation for the "2"
function floatTitle2() {
tween(titleNumber, {
x: 1024 + Math.random() * 20 - 10
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: floatTitle2
});
}
floatTitle2();
// 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) {
// Add pulsing glow effect
var _pulseStartButton = function pulseStartButton() {
if (!startButton.isHovered) return;
tween(startButton, {
alpha: 1.0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!startButton.isHovered) return;
tween(startButton, {
alpha: 0.85
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: _pulseStartButton
});
}
});
};
startButton.isHovered = true;
// Apply sophisticated hover animation with glow effect
tween(startButton, {
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.9,
y: startButton.y - 5
}, {
duration: 150,
easing: tween.easeOut
});
_pulseStartButton();
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
});
}
});
}
// Initial entrance animation for start button
startButton.alpha = 0;
startButton.y = 1300;
tween(startButton, {
alpha: 1,
y: 1200
}, {
duration: 500,
easing: tween.bounceOut
});
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;
// Add hover animation for settings button
settingsButton.isHovered = false;
settingsButton.move = function (x, y, obj) {
// Check if cursor is over the button
if (x >= settingsButton.x - settingsButton.width / 2 && x <= settingsButton.x + settingsButton.width / 2 && y >= settingsButton.y - settingsButton.height / 2 && y <= settingsButton.y + settingsButton.height / 2) {
if (!settingsButton.isHovered) {
settingsButton.isHovered = true;
tween(settingsButton, {
scaleX: 1.05,
scaleY: 1.05,
rotation: 0.02
}, {
duration: 200,
easing: tween.easeOut
});
}
} else {
if (settingsButton.isHovered) {
settingsButton.isHovered = false;
tween(settingsButton, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
}
};
// Initial entrance animation
settingsButton.alpha = 0;
settingsButton.y = 1500;
tween(settingsButton, {
alpha: 1,
y: 1400
}, {
duration: 600,
easing: tween.bounceOut
});
game.addChild(settingsButton);
// Set up event handlers for menu
startButton.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
hideMenu();
showModeSelect();
};
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 hideGameElements() {
if (background) {
background.visible = false;
}
if (lava) {
lava.visible = false;
}
if (paddle) {
paddle.visible = false;
}
if (scoreTxt) {
scoreTxt.visible = false;
}
if (levelTxt) {
levelTxt.visible = false;
}
if (comboTxt) {
comboTxt.visible = false;
}
if (highScoreTxt) {
highScoreTxt.visible = false;
}
if (hitCounterText) {
hitCounterText.visible = false;
}
if (speedTxt) {
speedTxt.visible = false;
}
if (backButton) {
backButton.visible = false;
}
}
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
}
});
}
}
// Mode selection screen
var modeSelectPanel;
var modeButtons = [];
function showModeSelect() {
currentState = GAME_STATE.MODE_SELECT;
// Create mode select panel if it doesn't exist
if (!modeSelectPanel) {
modeSelectPanel = new Container();
game.addChild(modeSelectPanel);
// Mode select background
var modeBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
tint: 0xFFFFFF
});
modeSelectPanel.addChild(modeBg);
// Mode select title
var modeTitle = new Text2('Select Game Mode', {
size: 120,
fill: 0x101820
});
modeTitle.anchor.set(0.5, 0);
modeTitle.x = 1024;
modeTitle.y = 100;
modeSelectPanel.addChild(modeTitle);
// Create mode buttons
var modeKeys = Object.keys(GAME_MODES);
for (var i = 0; i < modeKeys.length; i++) {
var modeKey = modeKeys[i];
var modeId = GAME_MODES[modeKey];
var config = gameModeConfigs[modeId];
var yPos = 400 + i * 320;
// Mode button container
var modeContainer = new Container();
modeContainer.x = 1024;
modeContainer.y = yPos;
modeSelectPanel.addChild(modeContainer);
// Mode button background with rounded corners
var buttonBg = new Container();
// Main rectangle body
var mainRect = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
mainRect.width = 1700; // Slightly smaller for rounded ends
mainRect.height = 280;
mainRect.tint = 0x101820;
buttonBg.addChild(mainRect);
// Left rounded corner (circle)
var leftCorner = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 280,
height: 280,
tint: 0x101820
});
leftCorner.x = -850; // Position at left edge
leftCorner.y = 0;
buttonBg.addChild(leftCorner);
// Right rounded corner (circle)
var rightCorner = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 280,
height: 280,
tint: 0x101820
});
rightCorner.x = 850; // Position at right edge
rightCorner.y = 0;
buttonBg.addChild(rightCorner);
modeContainer.addChild(buttonBg);
// Mode name
var modeName = new Text2(config.name, {
size: 80,
fill: 0xFFB612
});
modeName.anchor.set(0.5, 0);
modeName.x = 0;
modeName.y = -80;
modeContainer.addChild(modeName);
// Mode description
var modeDesc = new Text2(config.description, {
size: 50,
fill: 0xFFFFFF
});
modeDesc.anchor.set(0.5, 0);
modeDesc.x = 0;
modeDesc.y = -20;
modeContainer.addChild(modeDesc);
// Make button interactive with hover effects
modeContainer.interactive = true;
modeContainer.modeId = modeId;
modeContainer.isHovered = false;
// Add entrance animation with staggered delay
modeContainer.alpha = 0;
modeContainer.x = 1024 + 300;
tween(modeContainer, {
alpha: 1,
x: 1024
}, {
duration: 400 + i * 100,
easing: tween.bounceOut
});
// Hover animation
modeContainer.move = function (x, y, obj) {
if (x >= this.x - 900 && x <= this.x + 900 && y >= this.y - 140 && y <= this.y + 140) {
if (!this.isHovered) {
this.isHovered = true;
tween(this, {
scaleX: 1.02,
scaleY: 1.02,
y: this.y - 5
}, {
duration: 200,
easing: tween.easeOut
});
}
} else {
if (this.isHovered) {
this.isHovered = false;
tween(this, {
scaleX: 1.0,
scaleY: 1.0,
y: yPos
}, {
duration: 200,
easing: tween.easeOut
});
}
}
};
modeContainer.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
currentGameMode = this.modeId;
hideModeSelect();
// Stop menu music with fade out
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0.6,
end: 0,
duration: 500
}
});
}
initializeGameElements();
startGame();
};
modeButtons.push(modeContainer);
}
// Back to menu button
var backToMenuBtn = new Text2('← Back to Menu', {
size: 70,
fill: 0x101820
});
backToMenuBtn.anchor.set(0.5, 0.5);
backToMenuBtn.x = 1024;
backToMenuBtn.y = 2500;
backToMenuBtn.interactive = true;
backToMenuBtn.down = function () {
if (soundEnabled) {
LK.getSound('click').play();
}
hideModeSelect();
showMenu();
};
modeSelectPanel.addChild(backToMenuBtn);
} else {
modeSelectPanel.visible = true;
}
}
function hideModeSelect() {
if (modeSelectPanel) {
modeSelectPanel.visible = false;
}
}
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
var panelBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
panelBg.width = 2048;
panelBg.height = 2732;
panelBg.tint = 0x101820;
settingsPanel.addChild(panelBg);
// Settings title
var settingsTitle = new Text2('Visual Settings', {
size: 100 * visualSettings.textSize,
fill: visualSettings.uiColor
});
settingsTitle.anchor.set(0.5, 0);
settingsTitle.x = 1024;
settingsTitle.y = 100;
settingsPanel.addChild(settingsTitle);
// Create scrollable settings content
var settingsContent = new Container();
settingsContent.y = 0;
settingsPanel.addChild(settingsContent);
var currentY = 250;
var spacing = 120;
// Color settings section
var colorTitle = new Text2('Colors', {
size: 80 * visualSettings.textSize,
fill: 0xFFB612
});
colorTitle.anchor.set(0, 0);
colorTitle.x = 100;
colorTitle.y = currentY;
settingsContent.addChild(colorTitle);
currentY += spacing;
// Color presets
var colorPresets = [{
name: 'Classic Gold',
paddle: 0xFFB612,
ball: 0xFFB612,
lava: 0xC60C30,
bg: 0xFFFFFF,
text: 0x101820,
ui: 0xFFFFFF
}, {
name: 'Ocean Blue',
paddle: 0x3498DB,
ball: 0x2980B9,
lava: 0xE74C3C,
bg: 0xECF0F1,
text: 0x2C3E50,
ui: 0xFFFFFF
}, {
name: 'Forest Green',
paddle: 0x27AE60,
ball: 0x2ECC71,
lava: 0xE67E22,
bg: 0xF8F9FA,
text: 0x1E3A8A,
ui: 0xFFFFFF
}, {
name: 'Purple Power',
paddle: 0x8E44AD,
ball: 0x9B59B6,
lava: 0xE74C3C,
bg: 0xF4F3FF,
text: 0x4C1D95,
ui: 0xFFFFFF
}, {
name: 'Sunset',
paddle: 0xFF6B35,
ball: 0xFF8C42,
lava: 0xC70025,
bg: 0xFFF8DC,
text: 0x8B0000,
ui: 0xFFFFFF
}];
for (var i = 0; i < colorPresets.length; i++) {
var preset = colorPresets[i];
var presetButton = new Text2(preset.name, {
size: 60 * visualSettings.textSize,
fill: preset.paddle
});
presetButton.anchor.set(0, 0);
presetButton.x = 150;
presetButton.y = currentY;
presetButton.interactive = true;
presetButton.preset = preset;
presetButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Apply preset colors
visualSettings.paddleColor = this.preset.paddle;
visualSettings.ballColor = this.preset.ball;
visualSettings.lavaColor = this.preset.lava;
visualSettings.backgroundColor = this.preset.bg;
visualSettings.textColor = this.preset.text;
visualSettings.uiColor = this.preset.ui;
saveVisualSettings();
applyVisualSettings();
// Refresh settings panel
settingsPanel.destroy();
settingsPanel = null;
showSettings();
};
settingsContent.addChild(presetButton);
currentY += 80;
}
currentY += 40;
// Effects section
var effectsTitle = new Text2('Effects', {
size: 80 * visualSettings.textSize,
fill: 0xFFB612
});
effectsTitle.anchor.set(0, 0);
effectsTitle.x = 100;
effectsTitle.y = currentY;
settingsContent.addChild(effectsTitle);
currentY += spacing;
// Effects toggles
var effectsSettings = [{
key: 'trailsEnabled',
name: 'Ball Trails'
}, {
key: 'particlesEnabled',
name: 'Particles'
}, {
key: 'screenShakeEnabled',
name: 'Screen Shake'
}, {
key: 'lavaAnimationEnabled',
name: 'Lava Animation'
}];
for (var i = 0; i < effectsSettings.length; i++) {
var setting = effectsSettings[i];
var toggleText = new Text2(setting.name + ': ' + (visualSettings[setting.key] ? 'ON' : 'OFF'), {
size: 60 * visualSettings.textSize,
fill: visualSettings[setting.key] ? 0x00FF00 : 0xFF0000
});
toggleText.anchor.set(0, 0);
toggleText.x = 150;
toggleText.y = currentY;
toggleText.interactive = true;
toggleText.settingKey = setting.key;
toggleText.settingName = setting.name;
toggleText.down = function () {
if (soundEnabled) LK.getSound('click').play();
visualSettings[this.settingKey] = !visualSettings[this.settingKey];
saveVisualSettings();
this.setText(this.settingName + ': ' + (visualSettings[this.settingKey] ? 'ON' : 'OFF'));
this.fill = visualSettings[this.settingKey] ? 0x00FF00 : 0xFF0000;
};
settingsContent.addChild(toggleText);
currentY += 80;
}
currentY += 40;
// Size settings section
var sizeTitle = new Text2('Sizes', {
size: 80 * visualSettings.textSize,
fill: 0xFFB612
});
sizeTitle.anchor.set(0, 0);
sizeTitle.x = 100;
sizeTitle.y = currentY;
settingsContent.addChild(sizeTitle);
currentY += spacing;
// Size presets
var sizePresets = [{
name: 'Small',
ball: 0.8,
paddle: 0.8,
text: 0.8,
particle: 0.8
}, {
name: 'Normal',
ball: 1.0,
paddle: 1.0,
text: 1.0,
particle: 1.0
}, {
name: 'Large',
ball: 1.3,
paddle: 1.2,
text: 1.2,
particle: 1.2
}, {
name: 'Extra Large',
ball: 1.6,
paddle: 1.4,
text: 1.4,
particle: 1.4
}];
for (var i = 0; i < sizePresets.length; i++) {
var preset = sizePresets[i];
var sizeButton = new Text2(preset.name, {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
sizeButton.anchor.set(0, 0);
sizeButton.x = 150;
sizeButton.y = currentY;
sizeButton.interactive = true;
sizeButton.preset = preset;
sizeButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Apply size preset
visualSettings.ballSize = this.preset.ball;
visualSettings.paddleSize = this.preset.paddle;
visualSettings.textSize = this.preset.text;
visualSettings.particleSize = this.preset.particle;
saveVisualSettings();
// Refresh settings panel
settingsPanel.destroy();
settingsPanel = null;
showSettings();
};
settingsContent.addChild(sizeButton);
currentY += 80;
}
currentY += 40;
// Reset all settings button
var resetAllButton = new Text2('Reset All Settings', {
size: 70 * visualSettings.textSize,
fill: 0xFF6B6B
});
resetAllButton.anchor.set(0.5, 0.5);
resetAllButton.x = 1024;
resetAllButton.y = currentY;
resetAllButton.interactive = true;
resetAllButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Reset to defaults
visualSettings.paddleColor = 0xFFB612;
visualSettings.ballColor = 0xFFB612;
visualSettings.lavaColor = 0xC60C30;
visualSettings.backgroundColor = 0xFFFFFF;
visualSettings.textColor = 0x101820;
visualSettings.uiColor = 0xFFFFFF;
visualSettings.trailsEnabled = true;
visualSettings.particlesEnabled = true;
visualSettings.screenShakeEnabled = true;
visualSettings.lavaAnimationEnabled = true;
visualSettings.ballSize = 1.0;
visualSettings.paddleSize = 1.0;
visualSettings.textSize = 1.0;
visualSettings.particleSize = 1.0;
visualSettings.trailIntensity = 1.0;
visualSettings.particleIntensity = 1.0;
visualSettings.animationSpeed = 1.0;
saveVisualSettings();
applyVisualSettings();
// Refresh settings panel
settingsPanel.destroy();
settingsPanel = null;
showSettings();
};
settingsContent.addChild(resetAllButton);
currentY += 120;
// High score display
var highScoreDisplay = new Text2('High Score: ' + highScore, {
size: 60 * visualSettings.textSize,
fill: visualSettings.uiColor
});
highScoreDisplay.anchor.set(0.5, 0.5);
highScoreDisplay.x = 1024;
highScoreDisplay.y = currentY;
settingsContent.addChild(highScoreDisplay);
currentY += 100;
// Reset high score button
var resetScoreButton = new Text2('Reset High Score', {
size: 60 * visualSettings.textSize,
fill: 0xFF6B6B
});
resetScoreButton.anchor.set(0.5, 0.5);
resetScoreButton.x = 1024;
resetScoreButton.y = currentY;
resetScoreButton.interactive = true;
resetScoreButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
showResetConfirmation();
};
settingsContent.addChild(resetScoreButton);
currentY += 100;
// Down arrow for sound settings
var soundSettingsArrow = new Text2('Sound Settings ↓', {
size: 70 * visualSettings.textSize,
fill: 0xFFB612
});
soundSettingsArrow.anchor.set(0.5, 0.5);
soundSettingsArrow.x = 1024;
soundSettingsArrow.y = currentY;
soundSettingsArrow.interactive = true;
soundSettingsArrow.down = function () {
if (soundEnabled) LK.getSound('click').play();
showSoundSettings();
};
settingsContent.addChild(soundSettingsArrow);
currentY += 100;
// Back to menu button
var backButton = new Text2('← Back to Menu', {
size: 70 * visualSettings.textSize,
fill: visualSettings.uiColor
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = currentY;
backButton.interactive = true;
backButton.down = function () {
if (soundEnabled) LK.getSound('click').play();
// Animate settings panel exit
tween(settingsPanel, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
settingsPanel.visible = false;
showMenu();
}
});
if (musicEnabled) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
}
};
settingsContent.addChild(backButton);
// Make settings panel scrollable
settingsPanel.interactive = true;
settingsPanel.scrollY = 0;
settingsPanel.move = function (x, y, obj) {
// Simple scroll implementation
if (obj && obj.event && obj.event.movementY) {
settingsContent.y += obj.event.movementY * 2;
settingsContent.y = Math.max(Math.min(0, settingsContent.y), -(currentY - 2732 + 200));
}
};
} else {
settingsPanel.visible = true;
}
// Settings panel enter animation
settingsPanel.alpha = 0;
settingsPanel.scale.set(0.8, 0.8);
tween(settingsPanel, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeOut
});
}
// Function to create particles at click location
function createClickParticles(x, y) {
// Only create particles if enabled
if (!visualSettings.particlesEnabled) {
return;
}
// Create 15-20 particles for a visually impressive effect, adjusted by intensity
var baseCount = 15 + Math.floor(Math.random() * 6);
var particleCount = Math.floor(baseCount * visualSettings.particleIntensity);
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) {
// Only create particles if enabled
if (!visualSettings.particlesEnabled) {
return;
}
// Create 8-12 particles for collision effect - fewer than click particles, adjusted by intensity
var baseCount = 8 + Math.floor(Math.random() * 5);
var particleCount = Math.floor(baseCount * visualSettings.particleIntensity);
for (var i = 0; i < particleCount; i++) {
var particle = new Particle();
particle.x = x;
particle.y = y;
particle.active = true;
var collisionScale = 0.3 * visualSettings.particleSize * visualSettings.particleIntensity;
particle.scale.set(collisionScale, collisionScale); // 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 = visualSettings.ballColor;
// Visual indicator of speed - make ball slightly smaller as it gets faster, adjusted by ball size setting
var scale = Math.max(0.6, 1 - (speedMultiplier - 1) * 0.15) * visualSettings.ballSize;
// Enhanced multi-stage animated entrance effect
ball.alpha = 0;
ball.scale.set(0, 0);
ball.rotation = Math.PI * 2;
// Stage 1: Dramatic entrance with spin
tween(ball, {
alpha: 0.7,
scaleX: scale * 1.3,
scaleY: scale * 1.3,
rotation: 0
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Stage 2: Settle to final size with bounce
tween(ball, {
alpha: 1,
scaleX: scale,
scaleY: scale
}, {
duration: 300,
easing: tween.bounceOut
});
}
});
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.up = function (x, y, obj) {
// Handle volume slider up events
if (game.sliderUpHandler) {
game.sliderUpHandler(x, y, obj);
}
};
game.move = function (x, y, obj) {
if (currentState === GAME_STATE.PLAYING) {
paddle.x = x;
}
// Handle volume slider movements
if (game.sliderMoveHandler) {
game.sliderMoveHandler(x, y, obj);
}
};
// 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));
}
// Create balls based on game mode
if (currentGameMode === GAME_MODES.MULTI_BALL) {
// Multi-ball mode: maintain multiple balls
while (ballsInPlay < maxBalls) {
createBall();
}
} else {
// Other modes: 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
// Enhanced paddle hit response with sophisticated multi-stage animation
var originalPaddleY = paddle.y;
var baseScale = visualSettings.paddleSize; // Use base scale from settings
// Stage 1: Impact compression with rotation
tween(paddle, {
scaleY: 0.7 * baseScale,
scaleX: 1.2 * baseScale,
y: originalPaddleY + 12,
rotation: ball.spin * 0.15,
alpha: 0.9
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
// Stage 2: Elastic rebound with overshoot
tween(paddle, {
scaleY: 1.1 * baseScale,
scaleX: 0.95 * baseScale,
y: originalPaddleY - 5,
rotation: ball.spin * 0.05,
alpha: 1.0
}, {
duration: 140,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Stage 3: Final settle with subtle bounce
tween(paddle, {
scaleX: baseScale,
scaleY: baseScale,
y: originalPaddleY,
rotation: 0
}, {
duration: 120,
easing: tween.bounceOut
});
}
});
}
});
// 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
// Enhanced score animation with multi-stage effects and particle burst
var originalFill = scoreTxt.fill;
var scoreColors = [0xFFB612, 0xFF6B35, 0xE74C3C, 0x8E44AD, 0x3498DB];
var colorIndex = 0;
// Stage 1: Dramatic scale up with rotation and glow effect
tween(scoreTxt, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.85,
rotation: 0.1
}, {
duration: 120,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Stage 2: Color cycling effect
function cycleColors() {
if (colorIndex < scoreColors.length) {
scoreTxt.fill = scoreColors[colorIndex];
colorIndex++;
LK.setTimeout(cycleColors, 50);
} else {
// Stage 3: Final settle with bounce
tween(scoreTxt, {
scaleX: 1,
scaleY: 1,
alpha: 1,
rotation: 0
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Reset fill color after animation
scoreTxt.fill = originalFill !== undefined ? originalFill : 0x101820;
}
});
}
}
cycleColors();
// Create score particle burst
for (var p = 0; p < 8; p++) {
var scoreParticle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: scoreTxt.x,
y: scoreTxt.y,
tint: scoreColors[Math.floor(Math.random() * scoreColors.length)]
});
scoreParticle.scale.set(0.3, 0.3);
var angle = Math.PI * 2 * p / 8;
var distance = 100 + Math.random() * 50;
game.addChild(scoreParticle);
tween(scoreParticle, {
x: scoreTxt.x + Math.cos(angle) * distance,
y: scoreTxt.y + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
scoreParticle.destroy();
}
});
}
}
});
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();
}
// Enhanced lava death animation with multiple effects
// Screen shake effect if enabled
if (visualSettings.screenShakeEnabled) {
var originalGameY = game.y;
for (var shake = 0; shake < 5; shake++) {
LK.setTimeout(function () {
game.y = originalGameY + (Math.random() - 0.5) * 20;
LK.setTimeout(function () {
game.y = originalGameY;
}, 50);
}, shake * 100);
}
}
// Enhanced lava flash with ripple effect
tween(lava, {
tint: 0xffffff,
scaleY: 1.3,
scaleX: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Ripple effect
tween(lava, {
scaleY: 0.8,
scaleX: 0.95
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lava, {
tint: visualSettings.lavaColor,
scaleY: 1.0,
scaleX: 1.0
}, {
duration: 250,
easing: tween.elasticOut
});
}
});
}
});
// 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);
// Enhanced level up animation with multiple effects
levelUpTxt.alpha = 0;
levelUpTxt.scale.set(0.1, 0.1);
levelUpTxt.rotation = Math.PI * 2;
// Multi-stage animation
tween(levelUpTxt, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
rotation: 0
}, {
duration: 400,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Pulsing effect
function pulse() {
tween(levelUpTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(levelUpTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: pulse
});
}
});
}
pulse();
// Final fade out after displaying
LK.setTimeout(function () {
tween(levelUpTxt, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: -Math.PI,
y: levelUpTxt.y - 100
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
levelUpTxt.destroy();
}
});
}, 2000);
}
});
// 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
}
});
}
// Get current mode configuration
var modeConfig = gameModeConfigs[currentGameMode];
// Increase difficulty based on game mode
speedMultiplier += modeConfig.speedIncrease + level * 0.08;
maxBalls = modeConfig.maxBalls;
// Reset hit counter for next level
currentHits = 0;
// Set hits required for next level based on mode
hitsToNextLevel = modeConfig.hitsPerLevel + (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 with dramatic entrance
var newHighScoreTxt = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0xFFB612
});
newHighScoreTxt.anchor.set(0.5, 0.5);
newHighScoreTxt.x = 2048 / 2; // True center horizontally
newHighScoreTxt.y = 1000;
// Dramatic entrance animation
newHighScoreTxt.alpha = 0;
newHighScoreTxt.scale.set(0.1, 0.1);
newHighScoreTxt.rotation = Math.PI * 4;
LK.gui.addChild(newHighScoreTxt);
// Multi-stage dramatic animation
tween(newHighScoreTxt, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5,
rotation: 0
}, {
duration: 500,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Pulsing rainbow effect simulation
var colors = [0xFFB612, 0xFF6B35, 0xE74C3C, 0x8E44AD, 0x3498DB, 0x27AE60];
var colorIndex = 0;
function colorCycle() {
newHighScoreTxt.fill = colors[colorIndex];
colorIndex = (colorIndex + 1) % colors.length;
if (newHighScoreTxt.parent) {
LK.setTimeout(colorCycle, 200);
}
}
colorCycle();
// Final dramatic exit
LK.setTimeout(function () {
tween(newHighScoreTxt, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.PI * 2,
y: 600
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
newHighScoreTxt.destroy();
}
});
}, 1000);
}
});
// Update high score display
if (highScoreTxt) {
highScoreTxt.setText('High Score: ' + highScore);
}
}
// Enhanced game over effects with particle explosion
LK.effects.flashScreen(0xff0000, 500);
// Create dramatic particle explosion from center
for (var explosion = 0; explosion < 25; explosion++) {
var explodeParticle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
tint: [0xFF6B35, 0xE74C3C, 0xFFB612, 0x8E44AD][Math.floor(Math.random() * 4)]
});
explodeParticle.scale.set(0.8 + Math.random() * 0.4, 0.8 + Math.random() * 0.4);
game.addChild(explodeParticle);
var explodeAngle = Math.random() * Math.PI * 2;
var explodeDistance = 200 + Math.random() * 300;
var explodeDuration = 800 + Math.random() * 400;
tween(explodeParticle, {
x: 1024 + Math.cos(explodeAngle) * explodeDistance,
y: 1366 + Math.sin(explodeAngle) * explodeDistance,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.random() * Math.PI * 4
}, {
duration: explodeDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
explodeParticle.destroy();
}
});
}
// 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() {
// Get current mode configuration
var modeConfig = gameModeConfigs[currentGameMode];
// Reset variables
score = 0;
level = 1;
combo = 0;
lastBallHit = 0;
gameActive = true;
speedMultiplier = modeConfig.initialSpeed;
maxBalls = modeConfig.maxBalls;
ballsInPlay = 0;
currentHits = 0;
hitsToNextLevel = modeConfig.hitsPerLevel;
// Update UI with smooth animations
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
// Animate score text entrance
scoreTxt.alpha = 0;
scoreTxt.scale.set(0.5, 0.5);
tween(scoreTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.bounceOut
});
levelTxt.setText('Level: 1');
// Animate level text
levelTxt.alpha = 0;
tween(levelTxt, {
alpha: 1
}, {
duration: 300
});
comboTxt.setText('');
comboTxt.alpha = 0;
hitCounterText.setText('0/' + hitsToNextLevel);
// Animate hit counter
hitCounterText.alpha = 0;
hitCounterText.y = 100;
tween(hitCounterText, {
alpha: 1,
y: 150
}, {
duration: 500,
easing: tween.elasticOut
});
// 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;
}
// Apply visual settings on initialization
applyVisualSettings();
// Sound settings panel
var soundSettingsPanel;
function showSoundSettings() {
// Create sound settings panel if it doesn't exist
if (!soundSettingsPanel) {
soundSettingsPanel = new Container();
game.addChild(soundSettingsPanel);
// Sound settings panel background
var soundPanelBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
soundPanelBg.width = 2048;
soundPanelBg.height = 2732;
soundPanelBg.tint = 0x2C3E50;
soundSettingsPanel.addChild(soundPanelBg);
// Sound settings title
var soundTitle = new Text2('Sound Settings', {
size: 100 * visualSettings.textSize,
fill: 0xFFB612
});
soundTitle.anchor.set(0.5, 0);
soundTitle.x = 1024;
soundTitle.y = 200;
soundSettingsPanel.addChild(soundTitle);
var soundCurrentY = 400;
var soundSpacing = 150;
// Sound Effects Toggle
var soundEffectsText = new Text2('Sound Effects: ' + (soundEnabled ? 'ON' : 'OFF'), {
size: 80 * visualSettings.textSize,
fill: soundEnabled ? 0x00FF00 : 0xFF0000
});
soundEffectsText.anchor.set(0.5, 0.5);
soundEffectsText.x = 1024;
soundEffectsText.y = soundCurrentY;
soundEffectsText.interactive = true;
soundEffectsText.down = function () {
if (soundEnabled) LK.getSound('click').play();
soundEnabled = !soundEnabled;
storage.soundEnabled = soundEnabled;
this.setText('Sound Effects: ' + (soundEnabled ? 'ON' : 'OFF'));
this.fill = soundEnabled ? 0x00FF00 : 0xFF0000;
};
soundSettingsPanel.addChild(soundEffectsText);
soundCurrentY += soundSpacing;
// Music Toggle
var musicText = new Text2('Music: ' + (musicEnabled ? 'ON' : 'OFF'), {
size: 80 * visualSettings.textSize,
fill: musicEnabled ? 0x00FF00 : 0xFF0000
});
musicText.anchor.set(0.5, 0.5);
musicText.x = 1024;
musicText.y = soundCurrentY;
musicText.interactive = true;
musicText.down = function () {
if (soundEnabled) LK.getSound('click').play();
musicEnabled = !musicEnabled;
storage.musicEnabled = musicEnabled;
this.setText('Music: ' + (musicEnabled ? 'ON' : 'OFF'));
this.fill = musicEnabled ? 0x00FF00 : 0xFF0000;
if (!musicEnabled) {
LK.stopMusic();
} else if (currentState === GAME_STATE.MENU) {
LK.playMusic('menuMusic', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
} else if (currentState === GAME_STATE.PLAYING && level >= 4) {
LK.playMusic('heavyMetalMusic', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
} else if (currentState === GAME_STATE.PLAYING) {
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
};
soundSettingsPanel.addChild(musicText);
soundCurrentY += soundSpacing;
// Volume controls section
var volumeTitle = new Text2('Volume Controls', {
size: 70 * visualSettings.textSize,
fill: 0xFFB612
});
volumeTitle.anchor.set(0.5, 0);
volumeTitle.x = 1024;
volumeTitle.y = soundCurrentY;
soundSettingsPanel.addChild(volumeTitle);
soundCurrentY += 100;
// Initialize volume settings if not present
if (storage.masterVolume === undefined) storage.masterVolume = 0.7;
if (storage.soundVolume === undefined) storage.soundVolume = 1.0;
if (storage.musicVolume === undefined) storage.musicVolume = 0.6;
// Master Volume Slider
var masterVolumeLabel = new Text2('Master Volume: ' + Math.round(storage.masterVolume * 100) + '%', {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
masterVolumeLabel.anchor.set(0.5, 0.5);
masterVolumeLabel.x = 1024;
masterVolumeLabel.y = soundCurrentY;
soundSettingsPanel.addChild(masterVolumeLabel);
soundCurrentY += 80;
// Master volume slider track
var masterTrack = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: soundCurrentY
});
masterTrack.width = 800;
masterTrack.height = 20;
masterTrack.tint = 0x333333;
soundSettingsPanel.addChild(masterTrack);
// Master volume slider handle
var masterHandle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50,
tint: 0xFFB612
});
masterHandle.x = 624 + storage.masterVolume * 800; // Position based on volume
masterHandle.y = soundCurrentY;
masterHandle.interactive = true;
masterHandle.isDragging = false;
masterHandle.volumeType = 'master';
masterHandle.label = masterVolumeLabel;
masterHandle.down = function () {
this.isDragging = true;
if (soundEnabled) LK.getSound('click').play();
};
masterHandle.move = function (x, y, obj) {
if (this.isDragging) {
var trackLeft = 624;
var trackRight = 1424;
this.x = Math.max(trackLeft, Math.min(trackRight, x));
var volume = (this.x - trackLeft) / 800;
storage.masterVolume = volume;
this.label.setText('Master Volume: ' + Math.round(volume * 100) + '%');
}
};
masterHandle.up = function () {
this.isDragging = false;
};
soundSettingsPanel.addChild(masterHandle);
soundCurrentY += 100;
// Sound Effects Volume Slider
var soundVolumeLabel = new Text2('Sound Effects: ' + Math.round(storage.soundVolume * 100) + '%', {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
soundVolumeLabel.anchor.set(0.5, 0.5);
soundVolumeLabel.x = 1024;
soundVolumeLabel.y = soundCurrentY;
soundSettingsPanel.addChild(soundVolumeLabel);
soundCurrentY += 80;
// Sound effects volume slider track
var soundTrack = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: soundCurrentY
});
soundTrack.width = 800;
soundTrack.height = 20;
soundTrack.tint = 0x333333;
soundSettingsPanel.addChild(soundTrack);
// Sound effects volume slider handle
var soundHandle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50,
tint: 0x3498DB
});
soundHandle.x = 624 + storage.soundVolume * 800;
soundHandle.y = soundCurrentY;
soundHandle.interactive = true;
soundHandle.isDragging = false;
soundHandle.volumeType = 'sound';
soundHandle.label = soundVolumeLabel;
soundHandle.down = function () {
this.isDragging = true;
if (soundEnabled) LK.getSound('click').play();
};
soundHandle.move = function (x, y, obj) {
if (this.isDragging) {
var trackLeft = 624;
var trackRight = 1424;
this.x = Math.max(trackLeft, Math.min(trackRight, x));
var volume = (this.x - trackLeft) / 800;
storage.soundVolume = volume;
this.label.setText('Sound Effects: ' + Math.round(volume * 100) + '%');
}
};
soundHandle.up = function () {
this.isDragging = false;
};
soundSettingsPanel.addChild(soundHandle);
soundCurrentY += 100;
// Music Volume Slider
var musicVolumeLabel = new Text2('Music Volume: ' + Math.round(storage.musicVolume * 100) + '%', {
size: 60 * visualSettings.textSize,
fill: 0xFFFFFF
});
musicVolumeLabel.anchor.set(0.5, 0.5);
musicVolumeLabel.x = 1024;
musicVolumeLabel.y = soundCurrentY;
soundSettingsPanel.addChild(musicVolumeLabel);
soundCurrentY += 80;
// Music volume slider track
var musicTrack = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: soundCurrentY
});
musicTrack.width = 800;
musicTrack.height = 20;
musicTrack.tint = 0x333333;
soundSettingsPanel.addChild(musicTrack);
// Music volume slider handle
var musicHandle = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50,
tint: 0xE74C3C
});
musicHandle.x = 624 + storage.musicVolume * 800;
musicHandle.y = soundCurrentY;
musicHandle.interactive = true;
musicHandle.isDragging = false;
musicHandle.volumeType = 'music';
musicHandle.label = musicVolumeLabel;
musicHandle.down = function () {
this.isDragging = true;
if (soundEnabled) LK.getSound('click').play();
};
musicHandle.move = function (x, y, obj) {
if (this.isDragging) {
var trackLeft = 624;
var trackRight = 1424;
this.x = Math.max(trackLeft, Math.min(trackRight, x));
var volume = (this.x - trackLeft) / 800;
storage.musicVolume = volume;
this.label.setText('Music Volume: ' + Math.round(volume * 100) + '%');
// Apply volume change immediately to current music
if (musicEnabled && currentState === GAME_STATE.MENU) {
LK.playMusic('menuMusic', {
fade: {
start: volume,
end: volume,
duration: 100
}
});
} else if (musicEnabled && currentState === GAME_STATE.PLAYING && level >= 4) {
LK.playMusic('heavyMetalMusic', {
fade: {
start: volume,
end: volume,
duration: 100
}
});
} else if (musicEnabled && currentState === GAME_STATE.PLAYING) {
LK.playMusic('gameMusic', {
fade: {
start: volume,
end: volume,
duration: 100
}
});
}
}
};
musicHandle.up = function () {
this.isDragging = false;
};
soundSettingsPanel.addChild(musicHandle);
soundCurrentY += 120;
// Add global move handler to catch slider movements
game.sliderMoveHandler = function (x, y, obj) {
// Find all slider handles and update them
var children = soundSettingsPanel.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.isDragging && child.move) {
child.move(x, y, obj);
}
}
};
// Add global up handler to stop dragging
game.sliderUpHandler = function (x, y, obj) {
var children = soundSettingsPanel.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.isDragging && child.up) {
child.up(x, y, obj);
}
}
};
// Back to visual settings button
var backToVisualBtn = new Text2('← Back to Visual Settings', {
size: 70 * visualSettings.textSize,
fill: 0xFFB612
});
backToVisualBtn.anchor.set(0.5, 0.5);
backToVisualBtn.x = 1024;
backToVisualBtn.y = soundCurrentY + 100;
backToVisualBtn.interactive = true;
backToVisualBtn.down = function () {
if (soundEnabled) LK.getSound('click').play();
hideSoundSettings();
};
soundSettingsPanel.addChild(backToVisualBtn);
} else {
soundSettingsPanel.visible = true;
}
// Page flip animation - slide from right to left
soundSettingsPanel.x = 2048;
soundSettingsPanel.alpha = 1;
tween(soundSettingsPanel, {
x: 0
}, {
duration: 500,
easing: tween.easeInOut
});
// Hide visual settings with slide animation
tween(settingsPanel, {
x: -2048
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
settingsPanel.visible = false;
settingsPanel.x = 0; // Reset position for next time
}
});
}
function hideSoundSettings() {
if (soundSettingsPanel) {
// Page flip animation back - slide from left to right
tween(soundSettingsPanel, {
x: 2048
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
soundSettingsPanel.visible = false;
soundSettingsPanel.x = 0; // Reset position
}
});
// Show visual settings with slide animation
settingsPanel.visible = true;
settingsPanel.x = -2048;
tween(settingsPanel, {
x: 0
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
// Game will start when player presses the Play button in the menu
// Function to animate the lava with multiple effects
function animateLava() {
if (!visualSettings.lavaAnimationEnabled) {
// Just apply the static lava color if animations are disabled
if (lava) {
lava.tint = visualSettings.lavaColor;
}
return;
}
// Pulsing scale animation
function pulseLava() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
scaleY: 1.05,
scaleX: 1.02
}, {
duration: (800 + Math.random() * 400) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
scaleY: 0.98,
scaleX: 1.01
}, {
duration: (600 + Math.random() * 400) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: pulseLava
});
}
});
}
// Color shifting animation - based on the selected lava color
function shiftLavaColor() {
if (!visualSettings.lavaAnimationEnabled) return;
// Create variations of the selected lava color
var baseLava = visualSettings.lavaColor;
var colors = [baseLava, baseLava + 0x111111, baseLava - 0x111111, baseLava + 0x220000, baseLava - 0x110011];
var randomColor = colors[Math.floor(Math.random() * colors.length)];
tween(lava, {
tint: randomColor
}, {
duration: (1500 + Math.random() * 1000) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: shiftLavaColor
});
}
// Subtle vertical movement to simulate bubbling
function bubbleLava() {
if (!visualSettings.lavaAnimationEnabled) return;
var originalY = lava.y;
tween(lava, {
y: originalY - 5 - Math.random() * 8
}, {
duration: (400 + Math.random() * 300) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
y: originalY + 3 + Math.random() * 4
}, {
duration: (500 + Math.random() * 200) / visualSettings.animationSpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!visualSettings.lavaAnimationEnabled) return;
tween(lava, {
y: originalY
}, {
duration: (300 + Math.random() * 200) / visualSettings.animationSpeed,
easing: tween.easeOut,
onFinish: function onFinish() {
// Wait a bit before next bubble cycle
LK.setTimeout(bubbleLava, (500 + Math.random() * 1500) / visualSettings.animationSpeed);
}
});
}
});
}
});
}
// Start all animations
pulseLava();
shiftLavaColor();
LK.setTimeout(bubbleLava, Math.random() * 1000 / visualSettings.animationSpeed); // Start bubbling with random delay
}