User prompt
platforms should start from the bottom
Code edit (1 edits merged)
Please save this source code
User prompt
Color Bounce
Initial prompt
Build a complete, mobile-friendly rhythm-reaction game called **Color Bounce** with the following specifications: ๐ฎ Core Mechanics: 1. A ball automatically bounces downward onto colored platforms. 2. Each platform has one of 4 colors: red, blue, yellow and green. 3. The player must tap to **cycle the ball's color** in the order: red โ blue โ yellow โ green โ red... 4. When the ball lands on a platform: - โ If the color matches: it bounces again and the game continues. - โ If the color doesn't match: the game ends. 5. The ball falls faster as the game progresses โ **increase tempo over time.** ๐ฏ Controls: - Single-tap input anywhere on screen = cycle ball color to next one. ๐ง Difficulty Progression: - Start with 4 colored platforms, slow tempo. - Every 10 bounces, increase fall speed slightly. - After 20 bounces, introduce *moving* platforms (horizontal slide). - After 40 bounces, introduce random gaps (platform missing). - Cap difficulty at 80 bounces for now. ๐ Visual & Audio Feedback: - Each color has a **distinct glow and trail**. - Add a **particle burst** on successful bounce (small confetti or glow). - Add a **shake + screen flash** on failure. - Add SFX: - Bounce success = pitch-matched ping - Color switch = soft click - Fail = low thud + screen flash ๐ Score & Game Flow: - Show **score as total successful bounces**. - At game over, show: - Final score - Best score - Retry button โจ Cosmetic System: - Add **coin pickup items** that appear randomly on platforms (10% chance). - Player earns coins to unlock: - Ball skins (change ball appearance) - Trail styles (fire, smoke, sparkles) - Background themes (night, neon, pixel) - Add a basic **shop UI** with 6 total unlockable items (2 per category).
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
unlockedSkins: [],
selectedSkin: 0
});
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
// Ball color index: 0=red, 1=blue, 2=green, 3=yellow
self.colorIndex = 0;
self.colors = ['red', 'blue', 'green', 'yellow'];
self.asset = null;
// Attach initial asset
self.setColor = function (idx) {
if (self.asset) {
self.removeChild(self.asset);
}
self.colorIndex = idx;
var colorName = self.colors[self.colorIndex];
self.asset = self.attachAsset('ball_' + colorName, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.cycleColor = function () {
var next = (self.colorIndex + 1) % self.colors.length;
self.setColor(next);
};
// For simple trail effect (MVP: just a quick flash)
self.flash = function () {
tween(self.asset, {
alpha: 0.5
}, {
duration: 60,
easing: tween.linear,
onFinish: function onFinish() {
tween(self.asset, {
alpha: 1
}, {
duration: 80
});
}
});
};
// Set initial color
self.setColor(0);
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// MVP: no animation
};
return self;
});
// Platform class
var Platform = Container.expand(function () {
var self = Container.call(this);
// Color index: 0=red, 1=blue, 2=green, 3=yellow
self.colorIndex = 0;
self.colors = ['red', 'blue', 'green', 'yellow'];
self.asset = null;
// For moving platforms (future)
self.vx = 0;
self.setColor = function (idx) {
if (self.asset) {
self.removeChild(self.asset);
}
self.colorIndex = idx;
var colorName = self.colors[self.colorIndex];
self.asset = self.attachAsset('platform_' + colorName, {
anchorX: 0.5,
anchorY: 0.5
});
};
// For MVP, platforms are static
self.update = function () {
// No horizontal movement; platforms remain centered
};
// Set initial color
self.setColor(0);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// --- Game variables ---
// Ball colors (platforms and ball)
// SFX
// Music (stub, not played in MVP)
var ball = null;
var platforms = [];
var coins = [];
var score = 0;
var coinCount = 0;
var platformSpacing = 400; // vertical distance between platforms
// platformSpeed is now calculated dynamically based on score
var gravity = 1; // increases as score increases (slightly higher)
var bounceVelocity = -31; // initial bounce velocity (slightly higher magnitude)
var ballVy = 0;
var isFalling = true;
var currentPlatformIdx = 0;
var lastTapTick = 0;
var gameStarted = false;
var dragNode = null; // not used, but required by guidelines
var lastIntersecting = false;
var canTap = true; // prevent double tap in one frame
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: '#ffd700'
});
coinTxt.anchor.set(1, 0);
coinTxt.x = -40;
coinTxt.y = 20;
LK.gui.topRight.addChild(coinTxt);
// --- Music Play/Pause and Volume Switch (Top Left) ---
var musicPlaying = true;
var musicVolume = 1; // 1 = max, 0 = muted
// Play music at start
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: musicVolume,
duration: 600
}
});
// Play/Pause Button
var playPauseBtn = new Text2('โธ', {
size: 90,
fill: '#fff'
});
playPauseBtn.anchor.set(0, 0);
playPauseBtn.x = 110;
playPauseBtn.y = 10;
// Volume Button
var volumeBtn = new Text2('๐', {
size: 90,
fill: '#fff'
});
volumeBtn.anchor.set(0, 0);
volumeBtn.x = 220;
volumeBtn.y = 10;
// Add to top left GUI (leave 100x100 px clear for menu)
LK.gui.topLeft.addChild(playPauseBtn);
LK.gui.topLeft.addChild(volumeBtn);
// Play/Pause toggle
playPauseBtn.down = function (x, y, obj) {
if (musicPlaying) {
LK.stopMusic();
playPauseBtn.setText('โถ๏ธ');
musicPlaying = false;
} else {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: musicVolume,
duration: 400
}
});
playPauseBtn.setText('โธ');
musicPlaying = true;
}
};
// Volume toggle
volumeBtn.down = function (x, y, obj) {
if (musicVolume === 1) {
musicVolume = 0;
volumeBtn.setText('๐');
if (musicPlaying) {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 1,
end: 0,
duration: 300
}
});
}
} else {
musicVolume = 1;
volumeBtn.setText('๐');
if (musicPlaying) {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 300
}
});
}
}
};
// --- Helper functions ---
function getRandomColorIdx() {
// 0=red, 1=blue, 2=green, 3=yellow
return Math.floor(Math.random() * 4);
}
function spawnPlatform(y, colorIdx) {
var plat = new Platform();
plat.setColor(colorIdx);
plat.x = 2048 / 2;
plat.y = y;
// No horizontal movement; platforms remain centered
plat.vx = 0;
platforms.push(plat);
game.addChild(plat);
return plat;
}
function spawnCoin(x, y) {
var c = new Coin();
c.x = x;
c.y = y - 60;
coins.push(c);
game.addChild(c);
return c;
}
function resetGameVars() {
score = 0;
coinCount = 0;
// platformSpeed is now calculated dynamically based on score
gravity = 1.23; // increased initial gravity for even faster ball
bounceVelocity = -43; // increased initial bounce velocity for even faster ball
ballVy = 0;
isFalling = true;
currentPlatformIdx = 0;
lastTapTick = 0;
canTap = true;
gameStarted = false;
lastIntersecting = false;
// Remove old platforms and coins
for (var i = 0; i < platforms.length; ++i) {
platforms[i].destroy();
}
for (var i = 0; i < coins.length; ++i) {
coins[i].destroy();
}
platforms = [];
coins = [];
// Reset lastPlatformY to initial spawn value
lastPlatformY = 1600;
// Remove ball
if (ball) {
ball.destroy();
ball = null;
}
}
// --- Game start ---
function startGame() {
resetGameVars();
// Add background image behind all gameplay elements
var bg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(bg);
// Ball and Platforms: fill screen
// Place first platform lower to give player time to react
var y = 1600; // Lower on screen for first platform
var firstPlatformColorIdx = getRandomColorIdx();
ball = new Ball();
ball.setColor(firstPlatformColorIdx);
ball.x = 2048 / 2;
ball.y = 600;
game.addChild(ball);
for (var i = 0; i < 8; ++i) {
var colorIdx = i === 0 ? firstPlatformColorIdx : getRandomColorIdx();
var plat = spawnPlatform(y, colorIdx);
lastPlatformY = plat.y;
// Place a coin on every 3rd platform
if (i > 0 && i % 3 === 0) {
spawnCoin(plat.x, plat.y);
}
y += platformSpacing;
}
currentPlatformIdx = 0;
isFalling = true;
ballVy = 0;
score = 0;
coinCount = 0;
scoreTxt.setText('0');
coinTxt.setText(storage.coins || 0);
gameStarted = true;
}
// --- Input: tap to cycle color ---
game.down = function (x, y, obj) {
if (!gameStarted) {
startGame();
return;
}
if (!canTap) {
return;
}
canTap = false;
if (ball) {
ball.cycleColor();
ball.flash();
}
lastTapTick = LK.ticks;
};
game.up = function (x, y, obj) {
canTap = true;
};
// --- Main game loop ---
game.update = function () {
if (!gameStarted) {
return;
}
// Ball physics
if (isFalling) {
ballVy += gravity;
ball.y += ballVy;
}
// Move platforms upward to simulate ball falling
// Platform upward speed: increases slightly at each difficulty level for smooth progression
var moveUp = 0;
if (score >= 0) {
// Base speed (increased even more at first interval)
moveUp = 6.2;
// For every 7 points, increase speed by 0.27, up to a max
var level = Math.floor(score / 7);
moveUp += Math.min(level * 0.27, 4.5); // max speed increase is 4.5 (so max moveUp = 10.7)
// Prevent platforms from moving up so fast that the ball can exit the top
// If the ball is within 200px of the top, pause upward movement
if (ball && ball.y < 200) {
moveUp = 0;
}
}
for (var i = 0; i < platforms.length; ++i) {
platforms[i].y -= moveUp;
platforms[i].update();
}
for (var i = 0; i < coins.length; ++i) {
coins[i].y -= moveUp;
coins[i].update();
}
// Remove platforms that go off top and immediately recycle/spawn new ones at the bottom to maintain infinite flow
if (typeof lastPlatformY === "undefined") {
var lastPlatformY = 1600;
}
// Remove platforms that go off top and robustly recycle to always maintain at least 3 platforms below the ball
for (var i = platforms.length - 1; i >= 0; --i) {
if (platforms[i].y < -100) {
// Remove the platform from the game and array
platforms[i].destroy();
platforms.splice(i, 1);
}
}
// After removals, ensure at least 3 platforms exist, all spaced consistently below the lowest
// Find the current lowest platform Y
var lowestY = -Infinity;
for (var j = 0; j < platforms.length; ++j) {
if (platforms[j].y > lowestY) {
lowestY = platforms[j].y;
}
}
// If there are no platforms left, use the last known platform Y
if (lowestY === -Infinity) {
lowestY = lastPlatformY;
}
// Always keep at least 3 platforms
while (platforms.length < 3) {
var colorIdx = getRandomColorIdx();
var plat = spawnPlatform(lowestY + platformSpacing, colorIdx);
lastPlatformY = plat.y;
lowestY = plat.y;
// Place a coin on every 3rd platform
if ((score + platforms.length) % 3 === 0) {
spawnCoin(plat.x, plat.y);
}
}
for (var i = coins.length - 1; i >= 0; --i) {
if (coins[i].y < -100) {
coins[i].destroy();
coins.splice(i, 1);
}
}
// (Platform count is now managed by recycling above. No need for extra always-ensure loop.)
// Ball collision with platform (only check nearest platform)
var hit = false;
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
// Only check platforms below ball
if (plat.y > ball.y && plat.y - ball.y < 120) {
// Check horizontal overlap
var dx = Math.abs(ball.x - plat.x);
if (dx < plat.asset.width / 2 - 30) {
// Check vertical overlap
var dy = Math.abs(ball.y - plat.y);
if (dy < 90) {
// Ball is landing on platform
hit = true;
// Only trigger bounce if falling
if (ballVy > 0) {
// Color match?
if (ball.colorIndex === plat.colorIndex) {
// Success!
ballVy = bounceVelocity;
isFalling = true;
score += 1;
scoreTxt.setText(score);
LK.setScore(score);
LK.getSound('bounce').play();
// Flash ball
ball.flash();
// Remove platform after bounce
plat.destroy();
platforms.splice(i, 1);
// Increase difficulty
if (score === 3) {
// After first 3 bounces, increase ball speed (slightly more)
gravity += 0.18;
bounceVelocity -= 3.5;
}
// Difficulty increases every 7 points, with smooth transitions and more levels
if (score % 7 === 0) {
// Level up: increase gravity and platform speed smoothly
// Add more levels with different increments for smoothness
if (score < 21) {
gravity += 0.09;
} else if (score < 35) {
gravity += 0.08;
} else if (score < 56) {
gravity += 0.07;
} else if (score < 84) {
gravity += 0.06;
} else {
gravity += 0.05;
}
}
} else {
// Fail: wrong color
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
}
}
}
}
}
// Ball falls off bottom
if (ball.y > 2732 + 100) {
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
// Coin collection
for (var i = coins.length - 1; i >= 0; --i) {
var c = coins[i];
var dx = Math.abs(ball.x - c.x);
var dy = Math.abs(ball.y - c.y);
if (dx < 90 && dy < 90) {
// Collect coin
LK.getSound('coin').play();
c.destroy();
coins.splice(i, 1);
coinCount += 1;
storage.coins = (storage.coins || 0) + 1;
coinTxt.setText(storage.coins);
// Simple coin pop
// (future: animate)
}
}
};
// --- Game over / win handling is automatic by LK ---
// --- Start screen: tap to start ---
startGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
unlockedSkins: [],
selectedSkin: 0
});
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
// Ball color index: 0=red, 1=blue, 2=green, 3=yellow
self.colorIndex = 0;
self.colors = ['red', 'blue', 'green', 'yellow'];
self.asset = null;
// Attach initial asset
self.setColor = function (idx) {
if (self.asset) {
self.removeChild(self.asset);
}
self.colorIndex = idx;
var colorName = self.colors[self.colorIndex];
self.asset = self.attachAsset('ball_' + colorName, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.cycleColor = function () {
var next = (self.colorIndex + 1) % self.colors.length;
self.setColor(next);
};
// For simple trail effect (MVP: just a quick flash)
self.flash = function () {
tween(self.asset, {
alpha: 0.5
}, {
duration: 60,
easing: tween.linear,
onFinish: function onFinish() {
tween(self.asset, {
alpha: 1
}, {
duration: 80
});
}
});
};
// Set initial color
self.setColor(0);
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// MVP: no animation
};
return self;
});
// Platform class
var Platform = Container.expand(function () {
var self = Container.call(this);
// Color index: 0=red, 1=blue, 2=green, 3=yellow
self.colorIndex = 0;
self.colors = ['red', 'blue', 'green', 'yellow'];
self.asset = null;
// For moving platforms (future)
self.vx = 0;
self.setColor = function (idx) {
if (self.asset) {
self.removeChild(self.asset);
}
self.colorIndex = idx;
var colorName = self.colors[self.colorIndex];
self.asset = self.attachAsset('platform_' + colorName, {
anchorX: 0.5,
anchorY: 0.5
});
};
// For MVP, platforms are static
self.update = function () {
// No horizontal movement; platforms remain centered
};
// Set initial color
self.setColor(0);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// --- Game variables ---
// Ball colors (platforms and ball)
// SFX
// Music (stub, not played in MVP)
var ball = null;
var platforms = [];
var coins = [];
var score = 0;
var coinCount = 0;
var platformSpacing = 400; // vertical distance between platforms
// platformSpeed is now calculated dynamically based on score
var gravity = 1; // increases as score increases (slightly higher)
var bounceVelocity = -31; // initial bounce velocity (slightly higher magnitude)
var ballVy = 0;
var isFalling = true;
var currentPlatformIdx = 0;
var lastTapTick = 0;
var gameStarted = false;
var dragNode = null; // not used, but required by guidelines
var lastIntersecting = false;
var canTap = true; // prevent double tap in one frame
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: '#ffd700'
});
coinTxt.anchor.set(1, 0);
coinTxt.x = -40;
coinTxt.y = 20;
LK.gui.topRight.addChild(coinTxt);
// --- Music Play/Pause and Volume Switch (Top Left) ---
var musicPlaying = true;
var musicVolume = 1; // 1 = max, 0 = muted
// Play music at start
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: musicVolume,
duration: 600
}
});
// Play/Pause Button
var playPauseBtn = new Text2('โธ', {
size: 90,
fill: '#fff'
});
playPauseBtn.anchor.set(0, 0);
playPauseBtn.x = 110;
playPauseBtn.y = 10;
// Volume Button
var volumeBtn = new Text2('๐', {
size: 90,
fill: '#fff'
});
volumeBtn.anchor.set(0, 0);
volumeBtn.x = 220;
volumeBtn.y = 10;
// Add to top left GUI (leave 100x100 px clear for menu)
LK.gui.topLeft.addChild(playPauseBtn);
LK.gui.topLeft.addChild(volumeBtn);
// Play/Pause toggle
playPauseBtn.down = function (x, y, obj) {
if (musicPlaying) {
LK.stopMusic();
playPauseBtn.setText('โถ๏ธ');
musicPlaying = false;
} else {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: musicVolume,
duration: 400
}
});
playPauseBtn.setText('โธ');
musicPlaying = true;
}
};
// Volume toggle
volumeBtn.down = function (x, y, obj) {
if (musicVolume === 1) {
musicVolume = 0;
volumeBtn.setText('๐');
if (musicPlaying) {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 1,
end: 0,
duration: 300
}
});
}
} else {
musicVolume = 1;
volumeBtn.setText('๐');
if (musicPlaying) {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 300
}
});
}
}
};
// --- Helper functions ---
function getRandomColorIdx() {
// 0=red, 1=blue, 2=green, 3=yellow
return Math.floor(Math.random() * 4);
}
function spawnPlatform(y, colorIdx) {
var plat = new Platform();
plat.setColor(colorIdx);
plat.x = 2048 / 2;
plat.y = y;
// No horizontal movement; platforms remain centered
plat.vx = 0;
platforms.push(plat);
game.addChild(plat);
return plat;
}
function spawnCoin(x, y) {
var c = new Coin();
c.x = x;
c.y = y - 60;
coins.push(c);
game.addChild(c);
return c;
}
function resetGameVars() {
score = 0;
coinCount = 0;
// platformSpeed is now calculated dynamically based on score
gravity = 1.23; // increased initial gravity for even faster ball
bounceVelocity = -43; // increased initial bounce velocity for even faster ball
ballVy = 0;
isFalling = true;
currentPlatformIdx = 0;
lastTapTick = 0;
canTap = true;
gameStarted = false;
lastIntersecting = false;
// Remove old platforms and coins
for (var i = 0; i < platforms.length; ++i) {
platforms[i].destroy();
}
for (var i = 0; i < coins.length; ++i) {
coins[i].destroy();
}
platforms = [];
coins = [];
// Reset lastPlatformY to initial spawn value
lastPlatformY = 1600;
// Remove ball
if (ball) {
ball.destroy();
ball = null;
}
}
// --- Game start ---
function startGame() {
resetGameVars();
// Add background image behind all gameplay elements
var bg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(bg);
// Ball and Platforms: fill screen
// Place first platform lower to give player time to react
var y = 1600; // Lower on screen for first platform
var firstPlatformColorIdx = getRandomColorIdx();
ball = new Ball();
ball.setColor(firstPlatformColorIdx);
ball.x = 2048 / 2;
ball.y = 600;
game.addChild(ball);
for (var i = 0; i < 8; ++i) {
var colorIdx = i === 0 ? firstPlatformColorIdx : getRandomColorIdx();
var plat = spawnPlatform(y, colorIdx);
lastPlatformY = plat.y;
// Place a coin on every 3rd platform
if (i > 0 && i % 3 === 0) {
spawnCoin(plat.x, plat.y);
}
y += platformSpacing;
}
currentPlatformIdx = 0;
isFalling = true;
ballVy = 0;
score = 0;
coinCount = 0;
scoreTxt.setText('0');
coinTxt.setText(storage.coins || 0);
gameStarted = true;
}
// --- Input: tap to cycle color ---
game.down = function (x, y, obj) {
if (!gameStarted) {
startGame();
return;
}
if (!canTap) {
return;
}
canTap = false;
if (ball) {
ball.cycleColor();
ball.flash();
}
lastTapTick = LK.ticks;
};
game.up = function (x, y, obj) {
canTap = true;
};
// --- Main game loop ---
game.update = function () {
if (!gameStarted) {
return;
}
// Ball physics
if (isFalling) {
ballVy += gravity;
ball.y += ballVy;
}
// Move platforms upward to simulate ball falling
// Platform upward speed: increases slightly at each difficulty level for smooth progression
var moveUp = 0;
if (score >= 0) {
// Base speed (increased even more at first interval)
moveUp = 6.2;
// For every 7 points, increase speed by 0.27, up to a max
var level = Math.floor(score / 7);
moveUp += Math.min(level * 0.27, 4.5); // max speed increase is 4.5 (so max moveUp = 10.7)
// Prevent platforms from moving up so fast that the ball can exit the top
// If the ball is within 200px of the top, pause upward movement
if (ball && ball.y < 200) {
moveUp = 0;
}
}
for (var i = 0; i < platforms.length; ++i) {
platforms[i].y -= moveUp;
platforms[i].update();
}
for (var i = 0; i < coins.length; ++i) {
coins[i].y -= moveUp;
coins[i].update();
}
// Remove platforms that go off top and immediately recycle/spawn new ones at the bottom to maintain infinite flow
if (typeof lastPlatformY === "undefined") {
var lastPlatformY = 1600;
}
// Remove platforms that go off top and robustly recycle to always maintain at least 3 platforms below the ball
for (var i = platforms.length - 1; i >= 0; --i) {
if (platforms[i].y < -100) {
// Remove the platform from the game and array
platforms[i].destroy();
platforms.splice(i, 1);
}
}
// After removals, ensure at least 3 platforms exist, all spaced consistently below the lowest
// Find the current lowest platform Y
var lowestY = -Infinity;
for (var j = 0; j < platforms.length; ++j) {
if (platforms[j].y > lowestY) {
lowestY = platforms[j].y;
}
}
// If there are no platforms left, use the last known platform Y
if (lowestY === -Infinity) {
lowestY = lastPlatformY;
}
// Always keep at least 3 platforms
while (platforms.length < 3) {
var colorIdx = getRandomColorIdx();
var plat = spawnPlatform(lowestY + platformSpacing, colorIdx);
lastPlatformY = plat.y;
lowestY = plat.y;
// Place a coin on every 3rd platform
if ((score + platforms.length) % 3 === 0) {
spawnCoin(plat.x, plat.y);
}
}
for (var i = coins.length - 1; i >= 0; --i) {
if (coins[i].y < -100) {
coins[i].destroy();
coins.splice(i, 1);
}
}
// (Platform count is now managed by recycling above. No need for extra always-ensure loop.)
// Ball collision with platform (only check nearest platform)
var hit = false;
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
// Only check platforms below ball
if (plat.y > ball.y && plat.y - ball.y < 120) {
// Check horizontal overlap
var dx = Math.abs(ball.x - plat.x);
if (dx < plat.asset.width / 2 - 30) {
// Check vertical overlap
var dy = Math.abs(ball.y - plat.y);
if (dy < 90) {
// Ball is landing on platform
hit = true;
// Only trigger bounce if falling
if (ballVy > 0) {
// Color match?
if (ball.colorIndex === plat.colorIndex) {
// Success!
ballVy = bounceVelocity;
isFalling = true;
score += 1;
scoreTxt.setText(score);
LK.setScore(score);
LK.getSound('bounce').play();
// Flash ball
ball.flash();
// Remove platform after bounce
plat.destroy();
platforms.splice(i, 1);
// Increase difficulty
if (score === 3) {
// After first 3 bounces, increase ball speed (slightly more)
gravity += 0.18;
bounceVelocity -= 3.5;
}
// Difficulty increases every 7 points, with smooth transitions and more levels
if (score % 7 === 0) {
// Level up: increase gravity and platform speed smoothly
// Add more levels with different increments for smoothness
if (score < 21) {
gravity += 0.09;
} else if (score < 35) {
gravity += 0.08;
} else if (score < 56) {
gravity += 0.07;
} else if (score < 84) {
gravity += 0.06;
} else {
gravity += 0.05;
}
}
} else {
// Fail: wrong color
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
}
}
}
}
}
// Ball falls off bottom
if (ball.y > 2732 + 100) {
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
// Coin collection
for (var i = coins.length - 1; i >= 0; --i) {
var c = coins[i];
var dx = Math.abs(ball.x - c.x);
var dy = Math.abs(ball.y - c.y);
if (dx < 90 && dy < 90) {
// Collect coin
LK.getSound('coin').play();
c.destroy();
coins.splice(i, 1);
coinCount += 1;
storage.coins = (storage.coins || 0) + 1;
coinTxt.setText(storage.coins);
// Simple coin pop
// (future: animate)
}
}
};
// --- Game over / win handling is automatic by LK ---
// --- Start screen: tap to start ---
startGame();
blue ball. In-Game asset. 2d. High contrast. No shadows
green ball. In-Game asset. 2d. High contrast. No shadows
red ball. In-Game asset. 2d. High contrast. No shadows
yellow ball. In-Game asset. 2d. High contrast. No shadows
coin. In-Game asset. 2d. High contrast. No shadows
blue platform. In-Game asset. 2d. High contrast. No shadows
green platform. In-Game asset. 2d. High contrast. No shadows
red platform. In-Game asset. 2d. High contrast. No shadows
yellow platform. In-Game asset. 2d. High contrast. No shadows
Create a vertical-scrolling, futuristic game background for a rhythm-based mobile game. The style should be vibrant and glowing, with a deep gradient from dark purple to electric blue. Include subtle abstract shapes like floating geometric particles, soft energy lines, and a faint digital grid. The mood should feel like you're inside a pulsing rhythm tunnel or neon cyber tower. No text, no characters โ just atmospheric depth and motion-friendly layers. Must loop seamlessly for vertical scrolling. Style: sleek, minimal, energetic.. In-Game asset. 2d. High contrast. No shadows