/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGfx = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = ballGfx.width / 2;
self.vx = 0;
self.vy = 0;
self.speed = 18;
self.stuck = true; // Ball is stuck to paddle at start
// Powerup: slow ball
self.slow = function () {
self.speed = 10;
LK.setTimeout(function () {
self.speed = 18 + (level - 1) * 2;
}, 6000);
};
// Powerup: multi-ball handled in game code
self.update = function () {
if (self.stuck) return;
self.x += self.vx * self.speed / Math.sqrt(self.vx * self.vx + self.vy * self.vy);
self.y += self.vy * self.speed / Math.sqrt(self.vx * self.vx + self.vy * self.vy);
};
return self;
});
// Block class
var Block = Container.expand(function () {
var self = Container.call(this);
self.type = 1; // 1: normal, 2: strong, 3: very strong, 4: hard block
self.hp = 1;
self.score = 50;
self.assetId = 'block';
self.setType = function (t) {
self.type = t;
if (t === 1) {
self.hp = 1;
self.score = 50;
self.assetId = 'block';
} else if (t === 2) {
self.hp = 2; //{p} // block2 now always has 2 HP
self.score = 100;
self.assetId = 'block2';
} else if (t === 3) {
self.hp = 3;
self.score = 200;
self.assetId = 'block3';
} else if (t === 4) {
self.hp = 2;
self.score = 150;
self.assetId = 'hardblock';
}
self.removeChildren();
self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setType(1);
return self;
});
// Laser class
var Laser = Container.expand(function () {
var self = Container.call(this);
var laserGfx = self.attachAsset('laser_shot', {
anchorX: 0.5,
anchorY: 0.5
});
laserGfx.tint = 0xff2222;
self.vy = -40;
self.update = function () {
self.y += self.vy;
};
return self;
});
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleGfx = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = paddleGfx.width;
self.height = paddleGfx.height;
self.poweredUp = false;
self.powerupTimeout = null;
// Laser mode state
self.laserMode = false;
self.laserModeTimeout = null;
self.laserModeTimerBar = null;
self.laserModeStartTime = 0;
self.laserModeDuration = 10000; // ms
// Helper to swap paddle appearance
self.setLaserAppearance = function (on) {
self.removeChildren();
if (on) {
self.attachAsset('laser_paddle', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Show/hide/update laser timer bar
self.showLaserTimerBar = function () {
if (!self.laserModeTimerBar) {
var bar = new Container();
var bg = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
bg.width = self.width * 0.7;
bg.height = 18;
bg.tint = 0x222222;
bar.addChild(bg);
var fg = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
fg.width = self.width * 0.68;
fg.height = 12;
fg.tint = 0xff2222;
fg.x = 0;
fg.y = 0;
bar.addChild(fg);
bar.fg = fg;
bar.x = 0;
bar.y = -self.height / 2 - 24;
self.addChild(bar);
self.laserModeTimerBar = bar;
}
};
self.hideLaserTimerBar = function () {
if (self.laserModeTimerBar) {
self.laserModeTimerBar.destroy();
self.laserModeTimerBar = null;
}
};
self.updateLaserTimerBar = function () {
if (self.laserModeTimerBar) {
var elapsed = Date.now() - self.laserModeStartTime;
var remain = Math.max(0, self.laserModeDuration - elapsed);
var pct = remain / self.laserModeDuration;
self.laserModeTimerBar.fg.width = self.width * 0.68 * pct;
}
};
// Activate laser mode
self.activateLaserMode = function () {
if (self.laserModeTimeout) LK.clearTimeout(self.laserModeTimeout);
self.laserMode = true;
self.laserModeStartTime = Date.now();
self.setLaserAppearance(true);
self.showLaserTimerBar();
self.laserModeTimeout = LK.setTimeout(function () {
self.laserMode = false;
self.setLaserAppearance(false);
self.hideLaserTimerBar();
}, self.laserModeDuration);
};
// Powerup: widen paddle
self.setWide = function () {
if (self.poweredUp) {
if (self.powerupTimeout) LK.clearTimeout(self.powerupTimeout);
}
self.poweredUp = true;
tween(self, {
scaleX: 1.7
}, {
duration: 300,
easing: tween.easeOut
});
self.powerupTimeout = LK.setTimeout(function () {
tween(self, {
scaleX: 1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.poweredUp = false;
}
});
}, 6000);
};
// Powerup: laser
self.fireLaser = function () {
self.activateLaserMode();
};
self.update = function () {
if (self.laserMode) {
self.updateLaserTimerBar();
}
};
return self;
});
// Powerup class
var Powerup = Container.expand(function () {
var self = Container.call(this);
self.type = 'wide'; // 'wide', 'laser', 'slow', 'multi', 'heart'
self.vy = 10;
self.powerupGfx = null;
self.setType = function (t) {
self.type = t;
// Remove previous icon if any
if (self.powerupGfx) {
self.powerupGfx.destroy();
self.powerupGfx = null;
}
// Choose asset id based on type
var assetId = 'powerup_wide';
if (t === 'wide') assetId = 'powerup_wide';else if (t === 'laser') assetId = 'powerup_laser';else if (t === 'slow') assetId = 'powerup_slow';else if (t === 'multi') assetId = 'powerup_multi';else if (t === 'heart') assetId = 'powerup_heart';
self.powerupGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setType('wide'); // Default
self.update = function () {
self.y += self.vy;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Game variables
// Paddle
// Ball
// Block (normal)
// Block (strong)
// Block (very strong)
// Powerup
// Laser
// Sound effects
// Music
// Hard Block base color (purple)
// Hard Block damaged color (yellow)
var paddle, balls, blocks, powerups, lasers;
var lives = 3;
var level = 1;
var scoreTxt, livesTxt, levelTxt;
var dragNode = null;
var lastTouchX = 0;
var gameArea = {
x: 0,
y: 0,
width: 2048,
height: 2732
};
var blockRows = 5,
blockCols = 8,
blockMargin = 18;
var blockTop = 300;
var blockTypes = [1, 1, 1, 1, 1]; // Will be set per level
var laserActive = false;
var laserTimeout = null;
// GUI
scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
livesTxt = new Text2('♥♥♥', {
size: 80,
fill: 0xFF4D4D
});
livesTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(livesTxt);
levelTxt = new Text2('Level 1', {
size: 80,
fill: "#fff"
});
levelTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelTxt);
// Setup game
function setupGame() {
// Remove all children
game.removeChildren();
// Destroy and clear all blocks from previous level
if (typeof blocks !== "undefined" && blocks && blocks.length) {
for (var i = blocks.length - 1; i >= 0; i--) {
if (blocks[i] && typeof blocks[i].destroy === "function") blocks[i].destroy();
}
}
blocks = [];
// Destroy and clear all powerups from previous level
if (typeof powerups !== "undefined" && powerups && powerups.length) {
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] && typeof powerups[i].destroy === "function") powerups[i].destroy();
}
}
powerups = [];
// Destroy and clear all lasers from previous level
if (typeof lasers !== "undefined" && lasers && lasers.length) {
for (var i = lasers.length - 1; i >= 0; i--) {
if (lasers[i] && typeof lasers[i].destroy === "function") lasers[i].destroy();
}
}
lasers = [];
// Destroy and clear all balls from previous level
if (typeof balls !== "undefined" && balls && balls.length) {
for (var i = balls.length - 1; i >= 0; i--) {
if (balls[i] && typeof balls[i].destroy === "function") balls[i].destroy();
}
}
balls = [];
// Do NOT reset lives here; lives should persist between levels
// Paddle
paddle = new Paddle();
paddle.x = gameArea.width / 2;
paddle.y = gameArea.height - 200;
game.addChild(paddle);
// Ball
var ball = new Ball();
// Place ball at center, above paddle, but not overlapping paddle or blocks
ball.x = gameArea.width / 2;
ball.y = paddle.y - paddle.height / 2 - ball.radius - 10;
// Ensure no block overlaps with ball or paddle at start
for (var tries = 0; tries < 10; tries++) {
var overlap = false;
for (var b = 0; b < blocks.length; b++) {
var block = blocks[b];
var bw = 200,
bh = 80;
if (ball.x + ball.radius > block.x - bw / 2 && ball.x - ball.radius < block.x + bw / 2 && ball.y + ball.radius > block.y - bh / 2 && ball.y - ball.radius < block.y + bh / 2) {
overlap = true;
break;
}
}
if (!overlap) break;
// Move ball down if overlapping
ball.y += 40;
}
// Ball is stuck until player taps
ball.stuck = true;
ball.vx = 0;
ball.vy = -1;
balls.push(ball);
game.addChild(ball);
// Blocks
blockRows = 4 + Math.min(level, 6); // up to 10 rows
blockCols = 6 + Math.min(level, 4); // up to 10 cols
blockTypes = [];
for (var r = 0; r < blockRows; r++) {
if (level >= 2 && r === 0) {
blockTypes[r] = 4; // Hard Block in first row for level 2+
} else if (level < 3) {
blockTypes[r] = 1;
} else if (level < 5) {
blockTypes[r] = r < 2 ? 2 : 1;
} else {
blockTypes[r] = r < 2 ? 3 : r < 4 ? 2 : 1;
}
}
var blockW = 200,
blockH = 80;
var totalW = blockCols * blockW + (blockCols - 1) * blockMargin;
var startX = (gameArea.width - totalW) / 2 + blockW / 2;
for (var row = 0; row < blockRows; row++) {
for (var col = 0; col < blockCols; col++) {
var block = new Block();
block.setType(blockTypes[row]);
block.x = startX + col * (blockW + blockMargin);
block.y = blockTop + row * (blockH + blockMargin);
blocks.push(block);
game.addChild(block);
}
}
// Powerups, lasers
powerups = [];
lasers = [];
// Reset GUI
scoreTxt.setText(LK.getScore());
livesTxt.setText('♥'.repeat(lives > 0 ? lives : 0));
levelTxt.setText('Level ' + level);
// Play music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// --- Tap to Start overlay logic ---
if (typeof game.tapToStartMsg !== "undefined" && game.tapToStartMsg) {
game.tapToStartMsg.destroy();
delete game.tapToStartMsg;
}
var tapMsg = new Text2('Tap to Start', {
size: 180,
fill: 0x00ffff,
stroke: "#000",
strokeThickness: 10
});
tapMsg.anchor.set(0.5, 0.5);
tapMsg.x = gameArea.width / 2;
tapMsg.y = gameArea.height / 2;
game.addChild(tapMsg);
game.tapToStartMsg = tapMsg;
game.waitingForTap = true;
}
setupGame();
// Touch controls
game.down = function (x, y, obj) {
// Tap to Start overlay logic
if (game.waitingForTap) {
// Remove overlay
if (typeof game.tapToStartMsg !== "undefined" && game.tapToStartMsg) {
game.tapToStartMsg.destroy();
delete game.tapToStartMsg;
}
// Launch all stuck balls
for (var i = 0; i < balls.length; i++) {
if (balls[i].stuck) {
balls[i].stuck = false;
// Launch at random angle
var angle = Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3;
balls[i].vx = Math.cos(angle);
balls[i].vy = -Math.abs(Math.sin(angle));
}
}
game.waitingForTap = false;
return;
}
// Only allow drag on paddle area
if (y > paddle.y - paddle.height / 2 - 100) {
dragNode = paddle;
lastTouchX = x;
}
// If ball is stuck, launch it
for (var i = 0; i < balls.length; i++) {
if (balls[i].stuck) {
// Only launch if all blocks are visible (not during level transition)
if (!game.levelComplete) {
balls[i].stuck = false;
// Launch at random angle
var angle = Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3;
balls[i].vx = Math.cos(angle);
balls[i].vy = -Math.abs(Math.sin(angle));
}
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
game.move = function (x, y, obj) {
if (dragNode === paddle) {
// Clamp paddle to game area
var minX = paddle.width * paddle.scaleX / 2 + 20;
var maxX = gameArea.width - paddle.width * paddle.scaleX / 2 - 20;
paddle.x = Math.max(minX, Math.min(maxX, x));
// If any ball is stuck, move it with paddle
for (var i = 0; i < balls.length; i++) {
if (balls[i].stuck) {
balls[i].x = paddle.x;
}
}
}
};
// Main update loop
game.update = function () {
// If waiting for tap, do not update game state (pause everything except overlay)
if (game.waitingForTap) {
return;
}
// Update paddle (for timer bar and laser mode)
if (typeof paddle.update === "function") paddle.update();
// Update balls
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
ball.update();
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx = Math.abs(ball.vx);
}
if (ball.x + ball.radius > gameArea.width) {
ball.x = gameArea.width - ball.radius;
ball.vx = -Math.abs(ball.vx);
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
ball.vy = Math.abs(ball.vy);
}
// Paddle collision
if (!ball.stuck && ball.y + ball.radius >= paddle.y - paddle.height / 2 && ball.y - ball.radius <= paddle.y + paddle.height / 2 && ball.x + ball.radius >= paddle.x - paddle.width * paddle.scaleX / 2 && ball.x - ball.radius <= paddle.x + paddle.width * paddle.scaleX / 2 && ball.vy > 0) {
// Calculate hit position relative to paddle center (-1 = left edge, 0 = center, 1 = right edge)
var hit = (ball.x - paddle.x) / (paddle.width * paddle.scaleX / 2);
// Clamp hit to [-1, 1]
hit = Math.max(-1, Math.min(1, hit));
// Calculate bounce angle: center = straight up, edge = more horizontal
// Angle range: [minBounceAngle, maxBounceAngle] from vertical (PI/2)
var minBounceAngle = Math.PI / 7; // ~25.7 deg from vertical (up)
var maxBounceAngle = Math.PI / 2 - minBounceAngle; // ~64.3 deg from vertical
// Map hit to angle: left edge = PI/2 + maxBounceAngle, right edge = PI/2 - maxBounceAngle, center = PI/2
var angle = Math.PI / 2 - hit * maxBounceAngle;
// Clamp angle to avoid too-shallow bounces (ensure ball always goes up)
if (angle < minBounceAngle) angle = minBounceAngle;
if (angle > Math.PI - minBounceAngle) angle = Math.PI - minBounceAngle;
ball.vx = Math.cos(angle);
ball.vy = -Math.abs(Math.sin(angle));
// Add a little speed
ball.speed += 0.2;
}
// Block collisions
for (var j = blocks.length - 1; j >= 0; j--) {
var block = blocks[j];
var bw = 200,
bh = 80;
var bx = block.x,
by = block.y;
// Ensure block2 (type 2) is included in collision and hit logic
if (ball.x + ball.radius > bx - bw / 2 && ball.x - ball.radius < bx + bw / 2 && ball.y + ball.radius > by - bh / 2 && ball.y - ball.radius < by + bh / 2) {
// Hit!
LK.getSound('blockhit').play();
// Calculate which side of the block was hit to prevent edge sticking
var ballCenterX = ball.x;
var ballCenterY = ball.y;
var blockLeft = bx - bw / 2;
var blockRight = bx + bw / 2;
var blockTop = by - bh / 2;
var blockBottom = by + bh / 2;
// Calculate overlaps with proper direction consideration
var overlapLeft = ballCenterX + ball.radius - blockLeft;
var overlapRight = blockRight - (ballCenterX - ball.radius);
var overlapTop = ballCenterY + ball.radius - blockTop;
var overlapBottom = blockBottom - (ballCenterY - ball.radius);
// Find minimum overlap to determine collision side
var minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
// Bounce based on the side with minimum overlap and ball velocity direction
if (minOverlap === overlapLeft && ball.vx > 0) {
// Hit left side while moving right
ball.vx = -Math.abs(ball.vx);
ball.x = blockLeft - ball.radius - 1; // Push ball away from block
} else if (minOverlap === overlapRight && ball.vx < 0) {
// Hit right side while moving left
ball.vx = Math.abs(ball.vx);
ball.x = blockRight + ball.radius + 1; // Push ball away from block
} else if (minOverlap === overlapTop && ball.vy > 0) {
// Hit top side while moving down
ball.vy = -Math.abs(ball.vy);
ball.y = blockTop - ball.radius - 1; // Push ball away from block
} else if (minOverlap === overlapBottom && ball.vy < 0) {
// Hit bottom side while moving up
ball.vy = Math.abs(ball.vy);
ball.y = blockBottom + ball.radius + 1; // Push ball away from block
} else {
// Corner collision or ambiguous case - use simpler logic
var overlapX = Math.min(overlapLeft, overlapRight);
var overlapY = Math.min(overlapTop, overlapBottom);
if (overlapX < overlapY) {
ball.vx = -ball.vx;
// Push ball away horizontally
if (ballCenterX < bx) {
ball.x = blockLeft - ball.radius - 1;
} else {
ball.x = blockRight + ball.radius + 1;
}
} else {
ball.vy = -ball.vy;
// Push ball away vertically
if (ballCenterY < by) {
ball.y = blockTop - ball.radius - 1;
} else {
ball.y = blockBottom + ball.radius + 1;
}
}
}
// Damage block (including block2)
block.hp--;
if (block.hp <= 0) {
// Remove block
block.destroy();
blocks.splice(j, 1);
LK.setScore(LK.getScore() + block.score);
scoreTxt.setText(LK.getScore());
// Powerup drop chance
if (Math.random() < 0.18) {
var p = new Powerup();
var types = ['wide', 'laser', 'slow', 'multi'];
p.setType(types[Math.floor(Math.random() * types.length)]);
p.x = block.x;
p.y = block.y;
powerups.push(p);
game.addChild(p);
}
} else {
// Change block appearance and color for multi-hit blocks
if (block.type === 4) {
// Hard Block: 2 hits, color change after first hit
if (block.hp === 1) {
block.removeChildren();
block.attachAsset('hardblock_dmg', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
block.removeChildren();
block.attachAsset('hardblock', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (block.type === 2) {
// block2: Strong Block, reduce HP, update appearance, and tint if last hit
if (block.hp === 1) {
// Last hit, will be destroyed next
block.removeChildren();
block.attachAsset('block2', {
anchorX: 0.5,
anchorY: 0.5
});
block.children[0].tint = 0xffa500; // orange for last hit
} else if (block.hp > 1) {
// Still has more than 1 HP, show normal block2
block.removeChildren();
block.attachAsset('block2', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (block.type === 3) {
// Steel Block: flashes when hit, color changes with hp
block.setType(3);
block.removeChildren();
block.attachAsset('block3', {
anchorX: 0.5,
anchorY: 0.5
});
// Flash effect
LK.effects.flashObject(block, 0xffffff, 120);
// Color by hp
if (block.hp === 2) {
block.children[0].tint = 0x555555; // medium gray
} else if (block.hp === 1) {
block.children[0].tint = 0xbbbbbb; // lighter gray
}
} else {
// Normal block, just refresh
block.setType(1);
}
}
break;
}
}
// Ball out of bounds
if (ball.y - ball.radius > gameArea.height + 100) {
ball.destroy();
balls.splice(i, 1);
if (balls.length === 0) {
// Lose a life
lives--;
LK.getSound('loseball').play();
livesTxt.setText('♥'.repeat(lives > 0 ? lives : 0));
if (lives <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
} else {
// Reset paddle and ball
var newBall = new Ball();
newBall.x = paddle.x;
newBall.y = paddle.y - paddle.height / 2 - newBall.radius - 10;
newBall.stuck = true;
newBall.vx = 0;
newBall.vy = -1;
balls.push(newBall);
game.addChild(newBall);
}
}
}
}
// Powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
// Paddle catch
if (p.y + 40 >= paddle.y - paddle.height / 2 && p.y - 40 <= paddle.y + paddle.height / 2 && p.x + 40 >= paddle.x - paddle.width * paddle.scaleX / 2 && p.x - 40 <= paddle.x + paddle.width * paddle.scaleX / 2) {
// Activate powerup
LK.getSound('powerup').play();
if (p.type === 'wide') {
paddle.setWide();
} else if (p.type === 'laser') {
paddle.fireLaser();
} else if (p.type === 'slow') {
for (var b = 0; b < balls.length; b++) balls[b].slow();
} else if (p.type === 'multi') {
// Add 2 more balls
var newBalls = [];
for (var b = 0; b < balls.length; b++) {
for (var m = 0; m < 2; m++) {
var nb = new Ball();
nb.x = balls[b].x;
nb.y = balls[b].y;
var angle = Math.atan2(balls[b].vy, balls[b].vx) + (m === 0 ? -0.3 : 0.3);
nb.vx = Math.cos(angle);
nb.vy = Math.sin(angle);
nb.stuck = false;
nb.speed = balls[b].speed;
newBalls.push(nb);
game.addChild(nb);
}
}
for (var nb = 0; nb < newBalls.length; nb++) balls.push(newBalls[nb]);
}
p.destroy();
powerups.splice(i, 1);
} else if (p.y > gameArea.height + 100) {
p.destroy();
powerups.splice(i, 1);
}
}
// Lasers
if (typeof paddle.laserMode !== "undefined" && paddle.laserMode) {
if (typeof paddle._lastLaserFire === "undefined") paddle._lastLaserFire = 0;
if (Date.now() - paddle._lastLaserFire >= 500) {
LK.getSound('laserfire').play();
for (var side = -1; side <= 1; side += 2) {
var laser = new Laser();
laser.x = paddle.x + side * (paddle.width * paddle.scaleX / 2 - 30);
laser.y = paddle.y - paddle.height / 2 - 40;
lasers.push(laser);
game.addChild(laser);
}
paddle._lastLaserFire = Date.now();
}
if (typeof paddle.updateLaserTimerBar === "function") {
paddle.updateLaserTimerBar();
}
}
for (var i = lasers.length - 1; i >= 0; i--) {
var laser = lasers[i];
laser.update();
// Block hit
for (var j = blocks.length - 1; j >= 0; j--) {
var block = blocks[j];
var bw = 200,
bh = 80;
var bx = block.x,
by = block.y;
if (laser.x > bx - bw / 2 && laser.x < bx + bw / 2 && laser.y > by - bh / 2 && laser.y < by + bh / 2) {
// Hit!
block.hp = 0;
block.destroy();
blocks.splice(j, 1);
LK.setScore(LK.getScore() + block.score);
scoreTxt.setText(LK.getScore());
laser.destroy();
lasers.splice(i, 1);
break;
}
}
// Out of bounds
if (laser.y < -100) {
laser.destroy();
lasers.splice(i, 1);
}
}
// Win/level complete condition: all blocks destroyed, but only if there were blocks to begin with
if (blocks.length === 0 && typeof game.levelComplete === "undefined") {
// Double-check: ensure no blocks remain in the game scene
var anyBlockLeft = false;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] && typeof game.children[i].setType === "function") {
anyBlockLeft = true;
break;
}
}
if (!anyBlockLeft) {
// Set a flag so this only triggers once per level
game.levelComplete = true;
// Show "Level Complete" message for 2 seconds, then next level
var levelMsg = new Text2('Level Complete!', {
size: 180,
fill: 0xFFFF00,
stroke: "#000",
strokeThickness: 10
});
levelMsg.anchor.set(0.5, 0.5);
levelMsg.x = gameArea.width / 2;
levelMsg.y = gameArea.height / 2;
game.addChild(levelMsg);
// Prepare for next level
level++;
// Endless play: never show win, just keep increasing levels
// If you want a final level, set a max and show win, otherwise just keep going
// Example: if (level > 15) { ... show win ... } else { ... keep going ... }
// For endless, just always continue
// Increase ball speed by 5% for all new balls
for (var i = 0; i < balls.length; i++) {
balls[i].speed *= 1.05;
}
LK.setTimeout(function () {
// Remove message
levelMsg.destroy();
// Reset game state for next level
setupGame();
// Set all balls' speed to increased value for new level
for (var i = 0; i < balls.length; i++) {
balls[i].speed = 18 + (level - 1) * 2;
}
// Reset the levelComplete flag so next level can trigger again
delete game.levelComplete;
}, 2000);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGfx = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = ballGfx.width / 2;
self.vx = 0;
self.vy = 0;
self.speed = 18;
self.stuck = true; // Ball is stuck to paddle at start
// Powerup: slow ball
self.slow = function () {
self.speed = 10;
LK.setTimeout(function () {
self.speed = 18 + (level - 1) * 2;
}, 6000);
};
// Powerup: multi-ball handled in game code
self.update = function () {
if (self.stuck) return;
self.x += self.vx * self.speed / Math.sqrt(self.vx * self.vx + self.vy * self.vy);
self.y += self.vy * self.speed / Math.sqrt(self.vx * self.vx + self.vy * self.vy);
};
return self;
});
// Block class
var Block = Container.expand(function () {
var self = Container.call(this);
self.type = 1; // 1: normal, 2: strong, 3: very strong, 4: hard block
self.hp = 1;
self.score = 50;
self.assetId = 'block';
self.setType = function (t) {
self.type = t;
if (t === 1) {
self.hp = 1;
self.score = 50;
self.assetId = 'block';
} else if (t === 2) {
self.hp = 2; //{p} // block2 now always has 2 HP
self.score = 100;
self.assetId = 'block2';
} else if (t === 3) {
self.hp = 3;
self.score = 200;
self.assetId = 'block3';
} else if (t === 4) {
self.hp = 2;
self.score = 150;
self.assetId = 'hardblock';
}
self.removeChildren();
self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setType(1);
return self;
});
// Laser class
var Laser = Container.expand(function () {
var self = Container.call(this);
var laserGfx = self.attachAsset('laser_shot', {
anchorX: 0.5,
anchorY: 0.5
});
laserGfx.tint = 0xff2222;
self.vy = -40;
self.update = function () {
self.y += self.vy;
};
return self;
});
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleGfx = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = paddleGfx.width;
self.height = paddleGfx.height;
self.poweredUp = false;
self.powerupTimeout = null;
// Laser mode state
self.laserMode = false;
self.laserModeTimeout = null;
self.laserModeTimerBar = null;
self.laserModeStartTime = 0;
self.laserModeDuration = 10000; // ms
// Helper to swap paddle appearance
self.setLaserAppearance = function (on) {
self.removeChildren();
if (on) {
self.attachAsset('laser_paddle', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Show/hide/update laser timer bar
self.showLaserTimerBar = function () {
if (!self.laserModeTimerBar) {
var bar = new Container();
var bg = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
bg.width = self.width * 0.7;
bg.height = 18;
bg.tint = 0x222222;
bar.addChild(bg);
var fg = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
fg.width = self.width * 0.68;
fg.height = 12;
fg.tint = 0xff2222;
fg.x = 0;
fg.y = 0;
bar.addChild(fg);
bar.fg = fg;
bar.x = 0;
bar.y = -self.height / 2 - 24;
self.addChild(bar);
self.laserModeTimerBar = bar;
}
};
self.hideLaserTimerBar = function () {
if (self.laserModeTimerBar) {
self.laserModeTimerBar.destroy();
self.laserModeTimerBar = null;
}
};
self.updateLaserTimerBar = function () {
if (self.laserModeTimerBar) {
var elapsed = Date.now() - self.laserModeStartTime;
var remain = Math.max(0, self.laserModeDuration - elapsed);
var pct = remain / self.laserModeDuration;
self.laserModeTimerBar.fg.width = self.width * 0.68 * pct;
}
};
// Activate laser mode
self.activateLaserMode = function () {
if (self.laserModeTimeout) LK.clearTimeout(self.laserModeTimeout);
self.laserMode = true;
self.laserModeStartTime = Date.now();
self.setLaserAppearance(true);
self.showLaserTimerBar();
self.laserModeTimeout = LK.setTimeout(function () {
self.laserMode = false;
self.setLaserAppearance(false);
self.hideLaserTimerBar();
}, self.laserModeDuration);
};
// Powerup: widen paddle
self.setWide = function () {
if (self.poweredUp) {
if (self.powerupTimeout) LK.clearTimeout(self.powerupTimeout);
}
self.poweredUp = true;
tween(self, {
scaleX: 1.7
}, {
duration: 300,
easing: tween.easeOut
});
self.powerupTimeout = LK.setTimeout(function () {
tween(self, {
scaleX: 1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.poweredUp = false;
}
});
}, 6000);
};
// Powerup: laser
self.fireLaser = function () {
self.activateLaserMode();
};
self.update = function () {
if (self.laserMode) {
self.updateLaserTimerBar();
}
};
return self;
});
// Powerup class
var Powerup = Container.expand(function () {
var self = Container.call(this);
self.type = 'wide'; // 'wide', 'laser', 'slow', 'multi', 'heart'
self.vy = 10;
self.powerupGfx = null;
self.setType = function (t) {
self.type = t;
// Remove previous icon if any
if (self.powerupGfx) {
self.powerupGfx.destroy();
self.powerupGfx = null;
}
// Choose asset id based on type
var assetId = 'powerup_wide';
if (t === 'wide') assetId = 'powerup_wide';else if (t === 'laser') assetId = 'powerup_laser';else if (t === 'slow') assetId = 'powerup_slow';else if (t === 'multi') assetId = 'powerup_multi';else if (t === 'heart') assetId = 'powerup_heart';
self.powerupGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setType('wide'); // Default
self.update = function () {
self.y += self.vy;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Game variables
// Paddle
// Ball
// Block (normal)
// Block (strong)
// Block (very strong)
// Powerup
// Laser
// Sound effects
// Music
// Hard Block base color (purple)
// Hard Block damaged color (yellow)
var paddle, balls, blocks, powerups, lasers;
var lives = 3;
var level = 1;
var scoreTxt, livesTxt, levelTxt;
var dragNode = null;
var lastTouchX = 0;
var gameArea = {
x: 0,
y: 0,
width: 2048,
height: 2732
};
var blockRows = 5,
blockCols = 8,
blockMargin = 18;
var blockTop = 300;
var blockTypes = [1, 1, 1, 1, 1]; // Will be set per level
var laserActive = false;
var laserTimeout = null;
// GUI
scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
livesTxt = new Text2('♥♥♥', {
size: 80,
fill: 0xFF4D4D
});
livesTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(livesTxt);
levelTxt = new Text2('Level 1', {
size: 80,
fill: "#fff"
});
levelTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelTxt);
// Setup game
function setupGame() {
// Remove all children
game.removeChildren();
// Destroy and clear all blocks from previous level
if (typeof blocks !== "undefined" && blocks && blocks.length) {
for (var i = blocks.length - 1; i >= 0; i--) {
if (blocks[i] && typeof blocks[i].destroy === "function") blocks[i].destroy();
}
}
blocks = [];
// Destroy and clear all powerups from previous level
if (typeof powerups !== "undefined" && powerups && powerups.length) {
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] && typeof powerups[i].destroy === "function") powerups[i].destroy();
}
}
powerups = [];
// Destroy and clear all lasers from previous level
if (typeof lasers !== "undefined" && lasers && lasers.length) {
for (var i = lasers.length - 1; i >= 0; i--) {
if (lasers[i] && typeof lasers[i].destroy === "function") lasers[i].destroy();
}
}
lasers = [];
// Destroy and clear all balls from previous level
if (typeof balls !== "undefined" && balls && balls.length) {
for (var i = balls.length - 1; i >= 0; i--) {
if (balls[i] && typeof balls[i].destroy === "function") balls[i].destroy();
}
}
balls = [];
// Do NOT reset lives here; lives should persist between levels
// Paddle
paddle = new Paddle();
paddle.x = gameArea.width / 2;
paddle.y = gameArea.height - 200;
game.addChild(paddle);
// Ball
var ball = new Ball();
// Place ball at center, above paddle, but not overlapping paddle or blocks
ball.x = gameArea.width / 2;
ball.y = paddle.y - paddle.height / 2 - ball.radius - 10;
// Ensure no block overlaps with ball or paddle at start
for (var tries = 0; tries < 10; tries++) {
var overlap = false;
for (var b = 0; b < blocks.length; b++) {
var block = blocks[b];
var bw = 200,
bh = 80;
if (ball.x + ball.radius > block.x - bw / 2 && ball.x - ball.radius < block.x + bw / 2 && ball.y + ball.radius > block.y - bh / 2 && ball.y - ball.radius < block.y + bh / 2) {
overlap = true;
break;
}
}
if (!overlap) break;
// Move ball down if overlapping
ball.y += 40;
}
// Ball is stuck until player taps
ball.stuck = true;
ball.vx = 0;
ball.vy = -1;
balls.push(ball);
game.addChild(ball);
// Blocks
blockRows = 4 + Math.min(level, 6); // up to 10 rows
blockCols = 6 + Math.min(level, 4); // up to 10 cols
blockTypes = [];
for (var r = 0; r < blockRows; r++) {
if (level >= 2 && r === 0) {
blockTypes[r] = 4; // Hard Block in first row for level 2+
} else if (level < 3) {
blockTypes[r] = 1;
} else if (level < 5) {
blockTypes[r] = r < 2 ? 2 : 1;
} else {
blockTypes[r] = r < 2 ? 3 : r < 4 ? 2 : 1;
}
}
var blockW = 200,
blockH = 80;
var totalW = blockCols * blockW + (blockCols - 1) * blockMargin;
var startX = (gameArea.width - totalW) / 2 + blockW / 2;
for (var row = 0; row < blockRows; row++) {
for (var col = 0; col < blockCols; col++) {
var block = new Block();
block.setType(blockTypes[row]);
block.x = startX + col * (blockW + blockMargin);
block.y = blockTop + row * (blockH + blockMargin);
blocks.push(block);
game.addChild(block);
}
}
// Powerups, lasers
powerups = [];
lasers = [];
// Reset GUI
scoreTxt.setText(LK.getScore());
livesTxt.setText('♥'.repeat(lives > 0 ? lives : 0));
levelTxt.setText('Level ' + level);
// Play music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// --- Tap to Start overlay logic ---
if (typeof game.tapToStartMsg !== "undefined" && game.tapToStartMsg) {
game.tapToStartMsg.destroy();
delete game.tapToStartMsg;
}
var tapMsg = new Text2('Tap to Start', {
size: 180,
fill: 0x00ffff,
stroke: "#000",
strokeThickness: 10
});
tapMsg.anchor.set(0.5, 0.5);
tapMsg.x = gameArea.width / 2;
tapMsg.y = gameArea.height / 2;
game.addChild(tapMsg);
game.tapToStartMsg = tapMsg;
game.waitingForTap = true;
}
setupGame();
// Touch controls
game.down = function (x, y, obj) {
// Tap to Start overlay logic
if (game.waitingForTap) {
// Remove overlay
if (typeof game.tapToStartMsg !== "undefined" && game.tapToStartMsg) {
game.tapToStartMsg.destroy();
delete game.tapToStartMsg;
}
// Launch all stuck balls
for (var i = 0; i < balls.length; i++) {
if (balls[i].stuck) {
balls[i].stuck = false;
// Launch at random angle
var angle = Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3;
balls[i].vx = Math.cos(angle);
balls[i].vy = -Math.abs(Math.sin(angle));
}
}
game.waitingForTap = false;
return;
}
// Only allow drag on paddle area
if (y > paddle.y - paddle.height / 2 - 100) {
dragNode = paddle;
lastTouchX = x;
}
// If ball is stuck, launch it
for (var i = 0; i < balls.length; i++) {
if (balls[i].stuck) {
// Only launch if all blocks are visible (not during level transition)
if (!game.levelComplete) {
balls[i].stuck = false;
// Launch at random angle
var angle = Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3;
balls[i].vx = Math.cos(angle);
balls[i].vy = -Math.abs(Math.sin(angle));
}
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
game.move = function (x, y, obj) {
if (dragNode === paddle) {
// Clamp paddle to game area
var minX = paddle.width * paddle.scaleX / 2 + 20;
var maxX = gameArea.width - paddle.width * paddle.scaleX / 2 - 20;
paddle.x = Math.max(minX, Math.min(maxX, x));
// If any ball is stuck, move it with paddle
for (var i = 0; i < balls.length; i++) {
if (balls[i].stuck) {
balls[i].x = paddle.x;
}
}
}
};
// Main update loop
game.update = function () {
// If waiting for tap, do not update game state (pause everything except overlay)
if (game.waitingForTap) {
return;
}
// Update paddle (for timer bar and laser mode)
if (typeof paddle.update === "function") paddle.update();
// Update balls
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
ball.update();
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx = Math.abs(ball.vx);
}
if (ball.x + ball.radius > gameArea.width) {
ball.x = gameArea.width - ball.radius;
ball.vx = -Math.abs(ball.vx);
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
ball.vy = Math.abs(ball.vy);
}
// Paddle collision
if (!ball.stuck && ball.y + ball.radius >= paddle.y - paddle.height / 2 && ball.y - ball.radius <= paddle.y + paddle.height / 2 && ball.x + ball.radius >= paddle.x - paddle.width * paddle.scaleX / 2 && ball.x - ball.radius <= paddle.x + paddle.width * paddle.scaleX / 2 && ball.vy > 0) {
// Calculate hit position relative to paddle center (-1 = left edge, 0 = center, 1 = right edge)
var hit = (ball.x - paddle.x) / (paddle.width * paddle.scaleX / 2);
// Clamp hit to [-1, 1]
hit = Math.max(-1, Math.min(1, hit));
// Calculate bounce angle: center = straight up, edge = more horizontal
// Angle range: [minBounceAngle, maxBounceAngle] from vertical (PI/2)
var minBounceAngle = Math.PI / 7; // ~25.7 deg from vertical (up)
var maxBounceAngle = Math.PI / 2 - minBounceAngle; // ~64.3 deg from vertical
// Map hit to angle: left edge = PI/2 + maxBounceAngle, right edge = PI/2 - maxBounceAngle, center = PI/2
var angle = Math.PI / 2 - hit * maxBounceAngle;
// Clamp angle to avoid too-shallow bounces (ensure ball always goes up)
if (angle < minBounceAngle) angle = minBounceAngle;
if (angle > Math.PI - minBounceAngle) angle = Math.PI - minBounceAngle;
ball.vx = Math.cos(angle);
ball.vy = -Math.abs(Math.sin(angle));
// Add a little speed
ball.speed += 0.2;
}
// Block collisions
for (var j = blocks.length - 1; j >= 0; j--) {
var block = blocks[j];
var bw = 200,
bh = 80;
var bx = block.x,
by = block.y;
// Ensure block2 (type 2) is included in collision and hit logic
if (ball.x + ball.radius > bx - bw / 2 && ball.x - ball.radius < bx + bw / 2 && ball.y + ball.radius > by - bh / 2 && ball.y - ball.radius < by + bh / 2) {
// Hit!
LK.getSound('blockhit').play();
// Calculate which side of the block was hit to prevent edge sticking
var ballCenterX = ball.x;
var ballCenterY = ball.y;
var blockLeft = bx - bw / 2;
var blockRight = bx + bw / 2;
var blockTop = by - bh / 2;
var blockBottom = by + bh / 2;
// Calculate overlaps with proper direction consideration
var overlapLeft = ballCenterX + ball.radius - blockLeft;
var overlapRight = blockRight - (ballCenterX - ball.radius);
var overlapTop = ballCenterY + ball.radius - blockTop;
var overlapBottom = blockBottom - (ballCenterY - ball.radius);
// Find minimum overlap to determine collision side
var minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
// Bounce based on the side with minimum overlap and ball velocity direction
if (minOverlap === overlapLeft && ball.vx > 0) {
// Hit left side while moving right
ball.vx = -Math.abs(ball.vx);
ball.x = blockLeft - ball.radius - 1; // Push ball away from block
} else if (minOverlap === overlapRight && ball.vx < 0) {
// Hit right side while moving left
ball.vx = Math.abs(ball.vx);
ball.x = blockRight + ball.radius + 1; // Push ball away from block
} else if (minOverlap === overlapTop && ball.vy > 0) {
// Hit top side while moving down
ball.vy = -Math.abs(ball.vy);
ball.y = blockTop - ball.radius - 1; // Push ball away from block
} else if (minOverlap === overlapBottom && ball.vy < 0) {
// Hit bottom side while moving up
ball.vy = Math.abs(ball.vy);
ball.y = blockBottom + ball.radius + 1; // Push ball away from block
} else {
// Corner collision or ambiguous case - use simpler logic
var overlapX = Math.min(overlapLeft, overlapRight);
var overlapY = Math.min(overlapTop, overlapBottom);
if (overlapX < overlapY) {
ball.vx = -ball.vx;
// Push ball away horizontally
if (ballCenterX < bx) {
ball.x = blockLeft - ball.radius - 1;
} else {
ball.x = blockRight + ball.radius + 1;
}
} else {
ball.vy = -ball.vy;
// Push ball away vertically
if (ballCenterY < by) {
ball.y = blockTop - ball.radius - 1;
} else {
ball.y = blockBottom + ball.radius + 1;
}
}
}
// Damage block (including block2)
block.hp--;
if (block.hp <= 0) {
// Remove block
block.destroy();
blocks.splice(j, 1);
LK.setScore(LK.getScore() + block.score);
scoreTxt.setText(LK.getScore());
// Powerup drop chance
if (Math.random() < 0.18) {
var p = new Powerup();
var types = ['wide', 'laser', 'slow', 'multi'];
p.setType(types[Math.floor(Math.random() * types.length)]);
p.x = block.x;
p.y = block.y;
powerups.push(p);
game.addChild(p);
}
} else {
// Change block appearance and color for multi-hit blocks
if (block.type === 4) {
// Hard Block: 2 hits, color change after first hit
if (block.hp === 1) {
block.removeChildren();
block.attachAsset('hardblock_dmg', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
block.removeChildren();
block.attachAsset('hardblock', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (block.type === 2) {
// block2: Strong Block, reduce HP, update appearance, and tint if last hit
if (block.hp === 1) {
// Last hit, will be destroyed next
block.removeChildren();
block.attachAsset('block2', {
anchorX: 0.5,
anchorY: 0.5
});
block.children[0].tint = 0xffa500; // orange for last hit
} else if (block.hp > 1) {
// Still has more than 1 HP, show normal block2
block.removeChildren();
block.attachAsset('block2', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (block.type === 3) {
// Steel Block: flashes when hit, color changes with hp
block.setType(3);
block.removeChildren();
block.attachAsset('block3', {
anchorX: 0.5,
anchorY: 0.5
});
// Flash effect
LK.effects.flashObject(block, 0xffffff, 120);
// Color by hp
if (block.hp === 2) {
block.children[0].tint = 0x555555; // medium gray
} else if (block.hp === 1) {
block.children[0].tint = 0xbbbbbb; // lighter gray
}
} else {
// Normal block, just refresh
block.setType(1);
}
}
break;
}
}
// Ball out of bounds
if (ball.y - ball.radius > gameArea.height + 100) {
ball.destroy();
balls.splice(i, 1);
if (balls.length === 0) {
// Lose a life
lives--;
LK.getSound('loseball').play();
livesTxt.setText('♥'.repeat(lives > 0 ? lives : 0));
if (lives <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
} else {
// Reset paddle and ball
var newBall = new Ball();
newBall.x = paddle.x;
newBall.y = paddle.y - paddle.height / 2 - newBall.radius - 10;
newBall.stuck = true;
newBall.vx = 0;
newBall.vy = -1;
balls.push(newBall);
game.addChild(newBall);
}
}
}
}
// Powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
// Paddle catch
if (p.y + 40 >= paddle.y - paddle.height / 2 && p.y - 40 <= paddle.y + paddle.height / 2 && p.x + 40 >= paddle.x - paddle.width * paddle.scaleX / 2 && p.x - 40 <= paddle.x + paddle.width * paddle.scaleX / 2) {
// Activate powerup
LK.getSound('powerup').play();
if (p.type === 'wide') {
paddle.setWide();
} else if (p.type === 'laser') {
paddle.fireLaser();
} else if (p.type === 'slow') {
for (var b = 0; b < balls.length; b++) balls[b].slow();
} else if (p.type === 'multi') {
// Add 2 more balls
var newBalls = [];
for (var b = 0; b < balls.length; b++) {
for (var m = 0; m < 2; m++) {
var nb = new Ball();
nb.x = balls[b].x;
nb.y = balls[b].y;
var angle = Math.atan2(balls[b].vy, balls[b].vx) + (m === 0 ? -0.3 : 0.3);
nb.vx = Math.cos(angle);
nb.vy = Math.sin(angle);
nb.stuck = false;
nb.speed = balls[b].speed;
newBalls.push(nb);
game.addChild(nb);
}
}
for (var nb = 0; nb < newBalls.length; nb++) balls.push(newBalls[nb]);
}
p.destroy();
powerups.splice(i, 1);
} else if (p.y > gameArea.height + 100) {
p.destroy();
powerups.splice(i, 1);
}
}
// Lasers
if (typeof paddle.laserMode !== "undefined" && paddle.laserMode) {
if (typeof paddle._lastLaserFire === "undefined") paddle._lastLaserFire = 0;
if (Date.now() - paddle._lastLaserFire >= 500) {
LK.getSound('laserfire').play();
for (var side = -1; side <= 1; side += 2) {
var laser = new Laser();
laser.x = paddle.x + side * (paddle.width * paddle.scaleX / 2 - 30);
laser.y = paddle.y - paddle.height / 2 - 40;
lasers.push(laser);
game.addChild(laser);
}
paddle._lastLaserFire = Date.now();
}
if (typeof paddle.updateLaserTimerBar === "function") {
paddle.updateLaserTimerBar();
}
}
for (var i = lasers.length - 1; i >= 0; i--) {
var laser = lasers[i];
laser.update();
// Block hit
for (var j = blocks.length - 1; j >= 0; j--) {
var block = blocks[j];
var bw = 200,
bh = 80;
var bx = block.x,
by = block.y;
if (laser.x > bx - bw / 2 && laser.x < bx + bw / 2 && laser.y > by - bh / 2 && laser.y < by + bh / 2) {
// Hit!
block.hp = 0;
block.destroy();
blocks.splice(j, 1);
LK.setScore(LK.getScore() + block.score);
scoreTxt.setText(LK.getScore());
laser.destroy();
lasers.splice(i, 1);
break;
}
}
// Out of bounds
if (laser.y < -100) {
laser.destroy();
lasers.splice(i, 1);
}
}
// Win/level complete condition: all blocks destroyed, but only if there were blocks to begin with
if (blocks.length === 0 && typeof game.levelComplete === "undefined") {
// Double-check: ensure no blocks remain in the game scene
var anyBlockLeft = false;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] && typeof game.children[i].setType === "function") {
anyBlockLeft = true;
break;
}
}
if (!anyBlockLeft) {
// Set a flag so this only triggers once per level
game.levelComplete = true;
// Show "Level Complete" message for 2 seconds, then next level
var levelMsg = new Text2('Level Complete!', {
size: 180,
fill: 0xFFFF00,
stroke: "#000",
strokeThickness: 10
});
levelMsg.anchor.set(0.5, 0.5);
levelMsg.x = gameArea.width / 2;
levelMsg.y = gameArea.height / 2;
game.addChild(levelMsg);
// Prepare for next level
level++;
// Endless play: never show win, just keep increasing levels
// If you want a final level, set a max and show win, otherwise just keep going
// Example: if (level > 15) { ... show win ... } else { ... keep going ... }
// For endless, just always continue
// Increase ball speed by 5% for all new balls
for (var i = 0; i < balls.length; i++) {
balls[i].speed *= 1.05;
}
LK.setTimeout(function () {
// Remove message
levelMsg.destroy();
// Reset game state for next level
setupGame();
// Set all balls' speed to increased value for new level
for (var i = 0; i < balls.length; i++) {
balls[i].speed = 18 + (level - 1) * 2;
}
// Reset the levelComplete flag so next level can trigger again
delete game.levelComplete;
}, 2000);
}
}
};
Neon ball. In-Game asset. 2d. High contrast. No shadows
yellow neon rectangle block. In-Game asset. 2d. High contrast. No shadows
Res neon rectangle block. In-Game asset. 2d. High contrast. No shadows
Green neon rectangle block. In-Game asset. 2d. High contrast. No shadows
Purple neon rectangle block. In-Game asset. 2d. High contrast. No shadows
Baby blue neon rectangle block. In-Game asset. 2d. High contrast. No shadows
A horizontal glowing paddle for a 2D breakout-style arcade game. Make it bright red with a futuristic laser cannon design. The paddle should have two small laser emitters on each side pointing upward. Style: minimal, pixel-art or arcade-style, flat background. Top-down view, transparent background, centered. Resolution: 512x128 px.. In-Game asset. 2d. High contrast. No shadows
A bright red laser beam for a 2D arcade breakout game. It should be a narrow vertical beam, glowing with energy and fading slightly toward the tip. Style: pixel-art or retro arcade effect. Straight line, no curve. Transparent background, top-down view. Use neon red with a light glow. Resolution: 16x64 px or 32x128 px.. In-Game asset. 2d. High contrast. No shadows
A horizontal paddle for a 2D breakout arcade game, styled like a laser weapon but without active lasers. Color: dark metallic blue with red glowing accents on each side, similar to a powered-down version of a laser cannon. Keep the same shape and style as a laser paddle but make it look inactive. Top-down view, centered, transparent background. Resolution: 128x32 px.
Powerup heart. In-Game asset. 2d. High contrast. No shadows
Powerup laser. In-Game asset. 2d. High contrast. No shadows
Powerup multi ball. In-Game asset. 2d. High contrast. No shadows
Powerup slow. In-Game asset. 2d. High contrast. No shadows
Powerup wide paddle. In-Game asset. 2d. High contrast. No shadows