/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Ball = Container.expand(function (size, speedX, speedY) { var self = Container.call(this); self.size = size || 'large'; self.speedX = speedX || Math.random() * 10 - 5; self.speedY = speedY || Math.random() * 5 + 2; var assetName = self.size + 'Ball'; var ballGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.radius = ballGraphics.width / 2; self.update = function () { // Move the ball self.x += self.speedX; self.y += self.speedY; // Bounce off walls if (self.x < self.radius || self.x > 2048 - self.radius) { self.speedX *= -1; // Adjust position to prevent sticking to walls if (self.x < self.radius) { self.x = self.radius; } if (self.x > 2048 - self.radius) { self.x = 2048 - self.radius; } } // Bounce off top if (self.y < self.radius) { self.speedY *= -1; self.y = self.radius; } // Ball bounces off the ground if it's a large or medium ball if ((self.size === 'large' || self.size === 'medium') && self.y > 2732 - self.radius) { self.speedY *= -1; // Adjust position to prevent sticking to the ground self.y = 2732 - self.radius; } // Apply gravity self.speedY += 0.2; }; self.split = function () { // Create two smaller balls when hit var nextSize; if (self.size === 'large') { nextSize = 'medium'; score += 100; } else if (self.size === 'medium') { // Use custom split ball asset assetName = 'customSplitBall'; nextSize = 'small'; score += 200; } else { // Small balls just disappear score += 300; return []; } var newBalls = []; // Create two smaller balls with slightly altered velocities for (var i = 0; i < 2; i++) { var speedMultiplier = i === 0 ? 1 : -1; var colors = [0xff0000, 0x00ff00, 0x0000ff, 0x800080, 0xffffff, 0xffc0cb, 0xfff700]; // Red, Green, Blue, Purple, White, Pink, Lemon var randomColor = colors[Math.floor(Math.random() * colors.length)]; var newBall = new Ball(nextSize, self.speedX * 1.2 * speedMultiplier, self.speedY * 0.8); newBall.x = self.x; newBall.y = self.y; newBall.speedX += (Math.random() - 0.5) * 2; // Add slight random variation to speedX newBall.speedY += (Math.random() - 0.5) * 2; // Add slight random variation to speedY newBall.tint = randomColor; // Apply random color newBalls.push(newBall); } return newBalls; }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -15; self.update = function () { self.y += self.speed; // Remove bullet if it goes off-screen if (self.y < -50) { self.markForRemoval = true; } }; return self; }); var Cannon = Container.expand(function () { var self = Container.call(this); // Base of the cannon var base = self.attachAsset('cannonBase', { anchorX: 0.5, anchorY: 0.5, y: 20 }); // The cannon barrel var barrel = self.attachAsset('cannon', { anchorX: 0.5, anchorY: 1.0, y: 0 }); // Fire rate control self.lastFired = 0; self.fireRate = 500; // ms between shots // Cannon shoots automatically self.tryToFire = function () { var currentTime = Date.now(); if (currentTime - self.lastFired > self.fireRate) { self.fire(); self.lastFired = currentTime; } }; self.fire = function () { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y - barrel.height; game.addChild(bullet); bullets.push(bullet); LK.getSound('shoot').play(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x202060 }); /**** * Game Code ****/ // Attach background image var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5 }); background.x = 2048 / 2; background.y = 2732 / 2; game.addChild(background); var balls = []; var bullets = []; var score = 0; var isGameOver = false; var lastBallSpawnTime = 0; var ballSpawnInterval = 3000; // ms between ball spawns var timeSinceLastHit = 0; var comboMultiplier = 1; var comboTimeout = 1000; // ms to maintain combo // Create cannon var cannon = new Cannon(); cannon.x = 2048 / 2; cannon.y = 2732 - 100; game.addChild(cannon); // Create score display var scoreTxt = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0, 0); LK.gui.topRight.addChild(scoreTxt); scoreTxt.x = -scoreTxt.width - 20; scoreTxt.y = 20; // Create multiplier display var multiplierTxt = new Text2('x1', { size: 60, fill: 0xFFCC00 }); multiplierTxt.anchor.set(0, 0); LK.gui.topRight.addChild(multiplierTxt); multiplierTxt.x = -multiplierTxt.width - 20; multiplierTxt.y = scoreTxt.height + 30; // Handle ball spawning function spawnBall() { var ballColorSequence = ['largeBall', 'largeBallYellow', 'largeBallOrangeYellow']; var ballColorIndex = balls.length % ballColorSequence.length; var ball = new Ball('large', undefined, undefined); ball.attachAsset(ballColorSequence[ballColorIndex], { anchorX: 0.5, anchorY: 0.5 }); ball.x = Math.random() * (2048 - 200) + 100; // Avoid spawning too close to edges ball.y = -100; game.addChild(ball); balls.push(ball); } // Cannon movement var isDragging = false; game.down = function (x, y, obj) { isDragging = true; moveCannonTo(x); }; game.move = function (x, y, obj) { if (isDragging) { moveCannonTo(x); } }; game.up = function (x, y, obj) { isDragging = false; }; function moveCannonTo(x) { // Constrain cannon position to screen boundaries var minX = 100; var maxX = 2048 - 100; cannon.x = Math.max(minX, Math.min(maxX, x)); } // Update game state game.update = function () { if (isGameOver) { return; } // Update combo multiplier timeSinceLastHit += 16.67; // Approximate ms per frame at 60fps if (timeSinceLastHit > comboTimeout && comboMultiplier > 1) { comboMultiplier = 1; updateMultiplierDisplay(); } // Spawn new balls periodically var currentTime = Date.now(); if (currentTime - lastBallSpawnTime > ballSpawnInterval) { spawnBall(); lastBallSpawnTime = currentTime; // Gradually decrease spawn interval to increase difficulty ballSpawnInterval = Math.max(1000, ballSpawnInterval - 50); } // Automatically fire cannon cannon.tryToFire(); // Update all bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Check for collisions with balls for (var j = balls.length - 1; j >= 0; j--) { var ball = balls[j]; // Simple distance-based collision detection var dx = bullet.x - ball.x; var dy = bullet.y - ball.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < ball.radius) { // Hit detected LK.getSound('hit').play(); // Update combo system timeSinceLastHit = 0; comboMultiplier = Math.min(10, comboMultiplier + 1); updateMultiplierDisplay(); // Check if the ball is red and split the medium blue ball if (ball.size === 'large' && ball.assetName === 'largeBall') { for (var b = balls.length - 1; b >= 0; b--) { var otherBall = balls[b]; if (otherBall.size === 'medium' && otherBall.assetName === 'mediumBallBlue') { var newBalls = otherBall.split(); for (var k = 0; k < newBalls.length; k++) { game.addChild(newBalls[k]); balls.push(newBalls[k]); } game.removeChild(otherBall); balls.splice(b, 1); } } } // Create new smaller balls var newBalls = ball.split(); for (var k = 0; k < newBalls.length; k++) { game.addChild(newBalls[k]); balls.push(newBalls[k]); } // Remove the current ball and bullet game.removeChild(ball); balls.splice(j, 1); game.removeChild(bullet); bullets.splice(i, 1); // Update score updateScore(); // Break out of inner loop since bullet is gone break; } } // Remove bullets marked for removal if (bullet.markForRemoval) { game.removeChild(bullet); bullets.splice(i, 1); } } // Update all balls for (var i = balls.length - 1; i >= 0; i--) { var ball = balls[i]; // Check collision with cannon var dx = ball.x - cannon.x; var dy = ball.y - cannon.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < ball.radius + 75) { // Approximate cannon collision radius gameOver(); break; } // Trigger game over for small ball if (ball.size === 'small' && distance < ball.radius + 75) { gameOver(); break; } // Remove balls marked for removal if (ball.markForRemoval) { game.removeChild(ball); balls.splice(i, 1); } } }; function updateScore() { // Apply the multiplier to score updates score += 50 * comboMultiplier; scoreTxt.setText('Score: ' + score); // Update the internal LK score for leaderboards LK.setScore(score); } function updateMultiplierDisplay() { multiplierTxt.setText('x' + comboMultiplier); // Change color based on multiplier level if (comboMultiplier > 5) { multiplierTxt.setStyle({ fill: 0xFF3333 }); // Red for high multiplier } else if (comboMultiplier > 2) { multiplierTxt.setStyle({ fill: 0xFFAA00 }); // Orange for medium multiplier } else { multiplierTxt.setStyle({ fill: 0xFFCC00 }); // Default yellow } } function gameOver() { if (isGameOver) { return; } isGameOver = true; LK.effects.flashScreen(0xff0000, 500); LK.showGameOver(); } // Start background music LK.playMusic('gameMusic', { fade: { start: 0, end: 0.7, duration: 1000 } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Ball = Container.expand(function (size, speedX, speedY) {
var self = Container.call(this);
self.size = size || 'large';
self.speedX = speedX || Math.random() * 10 - 5;
self.speedY = speedY || Math.random() * 5 + 2;
var assetName = self.size + 'Ball';
var ballGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = ballGraphics.width / 2;
self.update = function () {
// Move the ball
self.x += self.speedX;
self.y += self.speedY;
// Bounce off walls
if (self.x < self.radius || self.x > 2048 - self.radius) {
self.speedX *= -1;
// Adjust position to prevent sticking to walls
if (self.x < self.radius) {
self.x = self.radius;
}
if (self.x > 2048 - self.radius) {
self.x = 2048 - self.radius;
}
}
// Bounce off top
if (self.y < self.radius) {
self.speedY *= -1;
self.y = self.radius;
}
// Ball bounces off the ground if it's a large or medium ball
if ((self.size === 'large' || self.size === 'medium') && self.y > 2732 - self.radius) {
self.speedY *= -1;
// Adjust position to prevent sticking to the ground
self.y = 2732 - self.radius;
}
// Apply gravity
self.speedY += 0.2;
};
self.split = function () {
// Create two smaller balls when hit
var nextSize;
if (self.size === 'large') {
nextSize = 'medium';
score += 100;
} else if (self.size === 'medium') {
// Use custom split ball asset
assetName = 'customSplitBall';
nextSize = 'small';
score += 200;
} else {
// Small balls just disappear
score += 300;
return [];
}
var newBalls = [];
// Create two smaller balls with slightly altered velocities
for (var i = 0; i < 2; i++) {
var speedMultiplier = i === 0 ? 1 : -1;
var colors = [0xff0000, 0x00ff00, 0x0000ff, 0x800080, 0xffffff, 0xffc0cb, 0xfff700]; // Red, Green, Blue, Purple, White, Pink, Lemon
var randomColor = colors[Math.floor(Math.random() * colors.length)];
var newBall = new Ball(nextSize, self.speedX * 1.2 * speedMultiplier, self.speedY * 0.8);
newBall.x = self.x;
newBall.y = self.y;
newBall.speedX += (Math.random() - 0.5) * 2; // Add slight random variation to speedX
newBall.speedY += (Math.random() - 0.5) * 2; // Add slight random variation to speedY
newBall.tint = randomColor; // Apply random color
newBalls.push(newBall);
}
return newBalls;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -15;
self.update = function () {
self.y += self.speed;
// Remove bullet if it goes off-screen
if (self.y < -50) {
self.markForRemoval = true;
}
};
return self;
});
var Cannon = Container.expand(function () {
var self = Container.call(this);
// Base of the cannon
var base = self.attachAsset('cannonBase', {
anchorX: 0.5,
anchorY: 0.5,
y: 20
});
// The cannon barrel
var barrel = self.attachAsset('cannon', {
anchorX: 0.5,
anchorY: 1.0,
y: 0
});
// Fire rate control
self.lastFired = 0;
self.fireRate = 500; // ms between shots
// Cannon shoots automatically
self.tryToFire = function () {
var currentTime = Date.now();
if (currentTime - self.lastFired > self.fireRate) {
self.fire();
self.lastFired = currentTime;
}
};
self.fire = function () {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y - barrel.height;
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x202060
});
/****
* Game Code
****/
// Attach background image
var background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = 2048 / 2;
background.y = 2732 / 2;
game.addChild(background);
var balls = [];
var bullets = [];
var score = 0;
var isGameOver = false;
var lastBallSpawnTime = 0;
var ballSpawnInterval = 3000; // ms between ball spawns
var timeSinceLastHit = 0;
var comboMultiplier = 1;
var comboTimeout = 1000; // ms to maintain combo
// Create cannon
var cannon = new Cannon();
cannon.x = 2048 / 2;
cannon.y = 2732 - 100;
game.addChild(cannon);
// Create score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -scoreTxt.width - 20;
scoreTxt.y = 20;
// Create multiplier display
var multiplierTxt = new Text2('x1', {
size: 60,
fill: 0xFFCC00
});
multiplierTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(multiplierTxt);
multiplierTxt.x = -multiplierTxt.width - 20;
multiplierTxt.y = scoreTxt.height + 30;
// Handle ball spawning
function spawnBall() {
var ballColorSequence = ['largeBall', 'largeBallYellow', 'largeBallOrangeYellow'];
var ballColorIndex = balls.length % ballColorSequence.length;
var ball = new Ball('large', undefined, undefined);
ball.attachAsset(ballColorSequence[ballColorIndex], {
anchorX: 0.5,
anchorY: 0.5
});
ball.x = Math.random() * (2048 - 200) + 100; // Avoid spawning too close to edges
ball.y = -100;
game.addChild(ball);
balls.push(ball);
}
// Cannon movement
var isDragging = false;
game.down = function (x, y, obj) {
isDragging = true;
moveCannonTo(x);
};
game.move = function (x, y, obj) {
if (isDragging) {
moveCannonTo(x);
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
function moveCannonTo(x) {
// Constrain cannon position to screen boundaries
var minX = 100;
var maxX = 2048 - 100;
cannon.x = Math.max(minX, Math.min(maxX, x));
}
// Update game state
game.update = function () {
if (isGameOver) {
return;
}
// Update combo multiplier
timeSinceLastHit += 16.67; // Approximate ms per frame at 60fps
if (timeSinceLastHit > comboTimeout && comboMultiplier > 1) {
comboMultiplier = 1;
updateMultiplierDisplay();
}
// Spawn new balls periodically
var currentTime = Date.now();
if (currentTime - lastBallSpawnTime > ballSpawnInterval) {
spawnBall();
lastBallSpawnTime = currentTime;
// Gradually decrease spawn interval to increase difficulty
ballSpawnInterval = Math.max(1000, ballSpawnInterval - 50);
}
// Automatically fire cannon
cannon.tryToFire();
// Update all bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check for collisions with balls
for (var j = balls.length - 1; j >= 0; j--) {
var ball = balls[j];
// Simple distance-based collision detection
var dx = bullet.x - ball.x;
var dy = bullet.y - ball.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball.radius) {
// Hit detected
LK.getSound('hit').play();
// Update combo system
timeSinceLastHit = 0;
comboMultiplier = Math.min(10, comboMultiplier + 1);
updateMultiplierDisplay();
// Check if the ball is red and split the medium blue ball
if (ball.size === 'large' && ball.assetName === 'largeBall') {
for (var b = balls.length - 1; b >= 0; b--) {
var otherBall = balls[b];
if (otherBall.size === 'medium' && otherBall.assetName === 'mediumBallBlue') {
var newBalls = otherBall.split();
for (var k = 0; k < newBalls.length; k++) {
game.addChild(newBalls[k]);
balls.push(newBalls[k]);
}
game.removeChild(otherBall);
balls.splice(b, 1);
}
}
}
// Create new smaller balls
var newBalls = ball.split();
for (var k = 0; k < newBalls.length; k++) {
game.addChild(newBalls[k]);
balls.push(newBalls[k]);
}
// Remove the current ball and bullet
game.removeChild(ball);
balls.splice(j, 1);
game.removeChild(bullet);
bullets.splice(i, 1);
// Update score
updateScore();
// Break out of inner loop since bullet is gone
break;
}
}
// Remove bullets marked for removal
if (bullet.markForRemoval) {
game.removeChild(bullet);
bullets.splice(i, 1);
}
}
// Update all balls
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
// Check collision with cannon
var dx = ball.x - cannon.x;
var dy = ball.y - cannon.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball.radius + 75) {
// Approximate cannon collision radius
gameOver();
break;
}
// Trigger game over for small ball
if (ball.size === 'small' && distance < ball.radius + 75) {
gameOver();
break;
}
// Remove balls marked for removal
if (ball.markForRemoval) {
game.removeChild(ball);
balls.splice(i, 1);
}
}
};
function updateScore() {
// Apply the multiplier to score updates
score += 50 * comboMultiplier;
scoreTxt.setText('Score: ' + score);
// Update the internal LK score for leaderboards
LK.setScore(score);
}
function updateMultiplierDisplay() {
multiplierTxt.setText('x' + comboMultiplier);
// Change color based on multiplier level
if (comboMultiplier > 5) {
multiplierTxt.setStyle({
fill: 0xFF3333
}); // Red for high multiplier
} else if (comboMultiplier > 2) {
multiplierTxt.setStyle({
fill: 0xFFAA00
}); // Orange for medium multiplier
} else {
multiplierTxt.setStyle({
fill: 0xFFCC00
}); // Default yellow
}
}
function gameOver() {
if (isGameOver) {
return;
}
isGameOver = true;
LK.effects.flashScreen(0xff0000, 500);
LK.showGameOver();
}
// Start background music
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});