/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Character: the player-controlled ellipse
var Character = Container.expand(function () {
var self = Container.call(this);
var _char = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Physics
self.velocity = 0;
self.gravity = 2.2;
self.flapStrength = -19;
// Flap (jump)
self.flap = function () {
self.velocity = self.flapStrength;
};
// Update position and velocity
self.update = function () {
// Always apply gravity and velocity, even during powerupActive
self.velocity += self.gravity;
self.y += self.velocity;
};
// For collision detection
self.getBounds = function () {
return {
x: self.x - _char.width / 2,
y: self.y - _char.height / 2,
width: _char.width,
height: _char.height
};
};
return self;
});
// CometParticle: a single particle for the comet trail effect
var CometParticle = Container.expand(function () {
var self = Container.call(this);
// Use pure white for a star-like effect
var color = 0xffffff;
// Use a bright ellipse as the particle
var _shape = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.22 + Math.random() * 0.18,
scaleY: 0.16 + Math.random() * 0.14,
color: color
});
// Particle properties
self.lifetime = 22 + Math.random() * 10; // frames
self.age = 0;
self.vx = -6 + Math.random() * -2; // leftward
self.vy = (Math.random() - 0.5) * 4; // slight up/down
self.alpha = 0.85 + Math.random() * 0.15; // Brighter
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.age++;
// Fade out, but keep a bit brighter
self.alpha *= 0.93;
if (self.age > self.lifetime) {
if (self.parent) self.parent.removeChild(self);
self.destroy();
}
};
return self;
});
// Gold: collectible coin between pipes
var Gold = Container.expand(function () {
var self = Container.call(this);
var _gold = self.attachAsset('gold', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self._bobPhase = Math.random() * Math.PI * 2; // randomize phase so coins don't bob in sync
self.update = function () {
self.x += self.speed;
// Animate gold with a gentle up-and-down bobbing motion
self.y += Math.sin(LK.ticks / 18 + self._bobPhase) * 1.5;
};
// For collision detection
self.getBounds = function () {
return {
x: self.x - _gold.width / 2,
y: self.y - _gold.height / 2,
width: _gold.width,
height: _gold.height
};
};
return self;
});
// PipePair: a pair of pipes (top and bottom) with a gap
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe dimensions
var pipeWidth = 220;
var pipeHeight = 1200;
var gapHeight = 500; // Will be set dynamically
// Top pipe
var topPipe = self.attachAsset('pipe_top', {
anchorX: 0,
anchorY: 1
});
// Bottom pipe
var bottomPipe = self.attachAsset('pipe_bottom', {
anchorX: 0,
anchorY: 0
});
// Set pipes' positions based on gapY and gapHeight
self.setGap = function (gapY, gapHeight) {
// Top pipe: bottom at gapY
topPipe.x = 0;
topPipe.y = gapY;
topPipe.height = gapY;
// Bottom pipe: top at gapY+gapHeight
bottomPipe.x = 0;
bottomPipe.y = gapY + gapHeight;
bottomPipe.height = 2732 - (gapY + gapHeight);
};
// For collision detection
self.getTopPipe = function () {
return topPipe;
};
self.getBottomPipe = function () {
return bottomPipe;
};
// Move pipes leftward
self.update = function () {
self.x += self.speed;
};
// Set initial speed
self.speed = -12;
return self;
});
// Powerup: special collectible (separate from Gold)
var Powerup = Container.expand(function () {
var self = Container.call(this);
var _powerup = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self._bobPhase = Math.random() * Math.PI * 2;
self.update = function () {
self.x += self.speed;
// Animate with a gentle up-and-down bobbing motion
self.y += Math.sin(LK.ticks / 18 + self._bobPhase) * 1.5;
};
self.getBounds = function () {
return {
x: self.x - _powerup.width / 2,
y: self.y - _powerup.height / 2,
width: _powerup.width,
height: _powerup.height
};
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4db8ff // Light blue sky
});
/****
* Game Code
****/
// Pipes: green boxes, character: yellow ellipse, background: blue
// Game constants
// Background landscape and clouds
// unique icon for powerup
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PIPE_WIDTH = 220;
var PIPE_GAP_MIN = 480;
var PIPE_GAP_MAX = 700;
var PIPE_DISTANCE = 900; // Horizontal distance between pipes
var PIPE_SPEED_START = -7; // Slower initial pipe speed
var PIPE_SPEED_MAX = -28;
var PIPE_FREQ_START = 120; // Pipes appear less frequently at start
var PIPE_FREQ_MIN = 45;
var GRAVITY_START = 1.05; // Slightly lower gravity for even gentler fall
var FLAP_STRENGTH = -22;
// Game variables
var pipes = [];
var golds = [];
var character;
var score = 0;
var scoreTxt;
var lastPipeTick = 0;
var pipeFreq = PIPE_FREQ_START;
var pipeSpeed = PIPE_SPEED_START;
var gameStarted = false;
var gameOver = false;
// Powerup/auto-fly state
var powerupActive = false;
var powerupPipesLeft = 0;
var autoFlyTargetY = null;
var autoFlyPipeIndex = null;
var autoFlyJustPassedPipe = false;
// Comet particle system for powerup effect
var cometParticles = [];
// Add city background image (behind landscape and clouds)
var bgCity = LK.getAsset('city', {
anchorX: 0,
anchorY: 1,
x: 0,
y: GAME_HEIGHT - 600,
width: GAME_WIDTH,
height: 900
});
var bgCity2 = LK.getAsset('city', {
anchorX: 0,
anchorY: 1,
x: GAME_WIDTH,
y: GAME_HEIGHT - 600,
width: GAME_WIDTH,
height: 900
});
game.addChild(bgCity);
game.addChild(bgCity2);
// Add background landscape (two for seamless looping)
var bgLandscape = LK.getAsset('bg_landscape', {
anchorX: 0,
anchorY: 1,
x: 0,
y: GAME_HEIGHT,
width: GAME_WIDTH,
height: 600
});
var bgLandscape2 = LK.getAsset('bg_landscape', {
anchorX: 0,
anchorY: 1,
x: GAME_WIDTH,
y: GAME_HEIGHT,
width: GAME_WIDTH,
height: 600
});
game.addChild(bgLandscape);
game.addChild(bgLandscape2);
// Add clouds (3 clouds at different positions and scales)
var clouds = [];
for (var i = 0; i < 3; i++) {
var cloud = LK.getAsset('cloud', {
anchorX: 0.5,
anchorY: 0.5,
x: 400 + i * 600,
y: 300 + i * 120,
scaleX: 0.8 + 0.3 * Math.random(),
scaleY: 0.8 + 0.3 * Math.random()
});
game.addChild(cloud);
clouds.push(cloud);
}
// Add score text to GUI
scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF,
stroke: 0x222222,
strokeThickness: 12
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Center score text horizontally, avoid top left 100x100
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = 40;
// Add countdown text for auto-fly powerup
var powerupCountdownTxt = new Text2('', {
size: 100,
fill: 0x00ff00,
font: "Impact, Arial Black, Tahoma"
});
powerupCountdownTxt.anchor.set(0.5, 0);
powerupCountdownTxt.x = LK.gui.top.width / 2;
powerupCountdownTxt.y = 200;
powerupCountdownTxt.visible = false;
LK.gui.top.addChild(powerupCountdownTxt);
// Add restart button to GUI (top right, not overlapping score)
var restartBtn = new Text2('⟳', {
size: 120,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
restartBtn.anchor.set(1, 0); // right-top
restartBtn.x = LK.gui.top.width - 40;
restartBtn.y = 40;
restartBtn.interactive = true;
restartBtn.buttonMode = true;
restartBtn.visible = false; // Only show after pause
restartBtn.down = function (x, y, obj) {
if (!gameOver) return;
resetGame();
};
LK.gui.top.addChild(restartBtn);
// Show restart button after pause, hide otherwise
LK.on('pause', function () {
restartBtn.visible = true;
});
LK.on('resume', function () {
restartBtn.visible = false;
});
LK.on('gameover', function () {
resetGame();
restartBtn.visible = false;
});
// Create character and position
character = new Character();
character.x = GAME_WIDTH * 0.35;
character.y = GAME_HEIGHT / 2;
character.gravity = GRAVITY_START;
character.flapStrength = FLAP_STRENGTH;
// Add character after pipes and collectibles so it renders above them
game.addChild(character);
// Reset game state
function resetGame() {
// Remove pipes
for (var i = pipes.length - 1; i >= 0; i--) {
pipes[i].destroy();
pipes.splice(i, 1);
}
// Reset lastX for all pipes (defensive, in case any remain)
for (var i = 0; i < pipes.length; i++) {
pipes[i].lastX = undefined;
}
// Remove golds
for (var i = golds.length - 1; i >= 0; i--) {
golds[i].destroy();
golds.splice(i, 1);
}
// Reset character
character.x = GAME_WIDTH * 0.35;
character.y = GAME_HEIGHT / 2;
character.velocity = 0;
character.gravity = GRAVITY_START;
character.flapStrength = FLAP_STRENGTH;
// Reset variables
score = 0;
scoreTxt.setText(score);
lastPipeTick = 0;
pipeFreq = PIPE_FREQ_START; // Use new slower frequency
pipeSpeed = PIPE_SPEED_START; // Use new slower speed
gameStarted = false;
gameOver = false;
// Reset powerup/auto-fly state
powerupActive = false;
powerupPipesLeft = 0;
autoFlyTargetY = null;
autoFlyPipeIndex = null;
autoFlyJustPassedPipe = false;
// Reset bird color and stop blinking
if (character._powerupBlinkTween) tween.stop(character, {
tint: true
});
character.tint = 0xffffff;
character._powerupBlinking = false;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = 0;
// Hide countdown text
if (typeof powerupCountdownTxt !== "undefined") {
powerupCountdownTxt.visible = false;
powerupCountdownTxt.setText('');
}
// Clear any pending auto-fly end timeout
if (game._powerupEndTimeout) {
LK.clearTimeout(game._powerupEndTimeout);
game._powerupEndTimeout = null;
}
}
// Start game on first tap
function startGame() {
if (!gameStarted && !gameOver) {
gameStarted = true;
character.flap();
}
}
// Flap on tap/click
game.down = function (x, y, obj) {
if (gameOver) return;
// Allow input during powerup auto-fly
if (!gameStarted) {
startGame();
} else {
character.flap();
}
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Animate clouds (move right to left, loop)
var bgMoveSpeed = Math.abs(pipeSpeed) * 0.5;
if (powerupActive && autoFlyPipeIndex !== null && pipes[autoFlyPipeIndex]) {
// During auto-fly, match background and clouds speed to the pipe's speed
bgMoveSpeed = Math.abs(pipes[autoFlyPipeIndex].speed) * 0.5;
}
for (var i = 0; i < clouds.length; i++) {
var cloud = clouds[i];
var cloudMoveSpeed = 0.7 + 0.2 * i;
if (powerupActive && autoFlyPipeIndex !== null && pipes[autoFlyPipeIndex]) {
cloudMoveSpeed = Math.abs(pipes[autoFlyPipeIndex].speed) * (0.7 + 0.2 * i) / Math.abs(pipeSpeed);
}
cloud.x -= cloudMoveSpeed;
if (cloud.x < -cloud.width / 2) {
cloud.x = GAME_WIDTH + cloud.width / 2 + Math.random() * 200;
cloud.y = 200 + Math.random() * 400 + i * 100;
}
}
// Animate city background to move leftward and loop (slower for parallax effect)
var cityMoveSpeed = bgMoveSpeed * 0.5;
bgCity.x -= cityMoveSpeed;
bgCity2.x -= cityMoveSpeed;
if (bgCity.x <= -GAME_WIDTH) {
bgCity.x = bgCity2.x + GAME_WIDTH;
}
if (bgCity2.x <= -GAME_WIDTH) {
bgCity2.x = bgCity.x + GAME_WIDTH;
}
// Animate background landscape to move leftward and loop
bgLandscape.x -= bgMoveSpeed;
bgLandscape2.x -= bgMoveSpeed;
// Loop both backgrounds for seamless scrolling
if (bgLandscape.x <= -GAME_WIDTH) {
bgLandscape.x = bgLandscape2.x + GAME_WIDTH;
}
if (bgLandscape2.x <= -GAME_WIDTH) {
bgLandscape2.x = bgLandscape.x + GAME_WIDTH;
}
if (!gameStarted) {
// Idle animation: bob up and down
character.y = GAME_HEIGHT / 2 + Math.sin(LK.ticks / 30) * 30;
return;
}
// Character physics
if (powerupActive) {
// Find the next pipe to pass if needed
if (autoFlyPipeIndex === null || autoFlyPipeIndex >= pipes.length) {
// Find the next pipe
for (var pi = 0; pi < pipes.length; pi++) {
if (!pipes[pi].passed && pipes[pi].x + PIPE_WIDTH / 2 > character.x) {
autoFlyPipeIndex = pi;
break;
}
}
if (autoFlyPipeIndex === null && pipes.length > 0) autoFlyPipeIndex = pipes.length - 1;
}
// Set target Y to the center of the gap of the next pipe
if (autoFlyPipeIndex !== null && pipes[autoFlyPipeIndex]) {
var pipePair = pipes[autoFlyPipeIndex];
// Calculate gap center
var gapY = pipePair.getTopPipe().height;
var gapHeight = pipePair.getBottomPipe().y - pipePair.getTopPipe().height;
autoFlyTargetY = gapY + gapHeight / 2;
// Do NOT override character.x or character.y; player keeps full control
// Calculate the pipe's center X
var pipeCenterX = pipePair.x + PIPE_WIDTH / 2;
// Store the last pipe center X for transition detection
if (pipePair.lastX === undefined) pipePair.lastX = pipeCenterX;
// Detect passing through the pipe (only when crossing the pipe center this frame)
if (pipePair.lastX >= character.x && pipeCenterX < character.x) {
powerupPipesLeft--;
autoFlyJustPassedPipe = true;
}
// When the pipe is behind, reset for next pipe
if (autoFlyJustPassedPipe && pipeCenterX < character.x - 40) {
autoFlyPipeIndex++;
autoFlyJustPassedPipe = false;
}
pipePair.lastX = pipeCenterX;
// Animate bird's color to blink green during auto-fly, increasing blink frequency towards the end
if (!character._powerupBlinking) {
character._powerupBlinking = true;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = powerupPipesLeft * 12; // estimate 12 blinks per pipe
}
if (character._powerupBlinking) {
// Calculate blink frequency: faster as powerupPipesLeft decreases
var minBlink = 180,
maxBlink = 60; // ms
var blinkDuration = minBlink - (minBlink - maxBlink) * (3 - powerupPipesLeft) / 3;
if (!character._powerupBlinkTween || character._powerupBlinkTween._finished) {
// Alternate between green and normal
var toGreen = character._powerupBlinkStep % 2 === 0;
var targetTint = toGreen ? 0x00ff00 : 0xffffff;
if (character._powerupBlinkTween) tween.stop(character, {
tint: true
});
character._powerupBlinkTween = tween(character, {
tint: targetTint
}, {
duration: blinkDuration,
easing: tween.linear
});
character._powerupBlinkStep++;
}
}
}
// Show and update countdown text during auto-fly
if (powerupActive && powerupPipesLeft > 0) {
powerupCountdownTxt.visible = true;
powerupCountdownTxt.setText("You are immortal for: " + powerupPipesLeft);
} else {
powerupCountdownTxt.visible = false;
}
// End powerup after 3 pipes, but delay deactivation by 0.5s
if (powerupPipesLeft <= 0 && powerupActive) {
if (!game._powerupEndTimeout) {
game._powerupEndTimeout = LK.setTimeout(function () {
powerupActive = false;
autoFlyTargetY = null;
autoFlyPipeIndex = null;
autoFlyJustPassedPipe = false;
// Stop blinking and reset color
if (character._powerupBlinkTween) tween.stop(character, {
tint: true
});
character.tint = 0xffffff;
character._powerupBlinking = false;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = 0;
// Stop powerupmusic and fade bgmusic back to 100%
LK.stopMusic('powerupmusic');
LK.playMusic('bgmusic', {
fade: {
start: 0.2,
end: 1,
duration: 400
}
});
game._powerupEndTimeout = null;
}, 500);
}
}
// Always call character.update() so player has full control
character.update();
// Spawn comet particles if powerupActive
if (powerupActive) {
// Spawn 2-3 particles per frame for a dense trail
for (var cp = 0; cp < 2 + Math.floor(Math.random() * 2); cp++) {
var p = new CometParticle();
// Place particle slightly behind and below the bird for a comet tail
p.x = character.x - 40 + Math.random() * 10;
p.y = character.y + 20 + Math.random() * 16 - 8;
p.alpha = 0.6 + Math.random() * 0.25;
cometParticles.push(p);
game.addChildAt ? game.addChildAt(p, 0) : game.addChild(p); // add behind bird if possible
}
}
// Update and clean up comet particles
for (var i = cometParticles.length - 1; i >= 0; i--) {
var part = cometParticles[i];
part.update();
if (part.age > part.lifetime || part.alpha < 0.05) {
if (part.parent) part.parent.removeChild(part);
cometParticles.splice(i, 1);
}
}
} else {
character.update();
}
// Clamp character to screen
if (character.y < character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2) {
character.y = character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2;
character.velocity = 0;
}
if (character.y > GAME_HEIGHT - character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2) {
character.y = GAME_HEIGHT - character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2;
character.velocity = 0;
if (!powerupActive) {
triggerGameOver();
return;
}
// If powerupActive, do not trigger game over, just clamp position
}
// Pipes movement and collision
for (var i = pipes.length - 1; i >= 0; i--) {
var pipePair = pipes[i];
pipePair.update();
// Remove pipes off screen
if (pipePair.x + PIPE_WIDTH < 0) {
pipePair.destroy();
pipes.splice(i, 1);
continue;
}
// Collision detection
var charBounds = character.getBounds();
var topPipe = pipePair.getTopPipe();
var bottomPipe = pipePair.getBottomPipe();
// Top pipe collision
if (rectsIntersect(charBounds, {
x: pipePair.x,
y: 0,
width: PIPE_WIDTH,
height: topPipe.height
})) {
if (!powerupActive) {
triggerGameOver();
return;
}
// If powerupActive, ignore collision and allow player to keep playing
}
// Bottom pipe collision
if (rectsIntersect(charBounds, {
x: pipePair.x,
y: bottomPipe.y,
width: PIPE_WIDTH,
height: bottomPipe.height
})) {
if (!powerupActive) {
triggerGameOver();
return;
}
// If powerupActive, ignore collision and allow player to keep playing
}
// Score: when character passes the pipe (center)
if (!pipePair.passed && pipePair.x + PIPE_WIDTH / 2 < character.x) {
pipePair.passed = true;
score += 1;
scoreTxt.setText(score);
// Increase difficulty
if (pipeFreq > PIPE_FREQ_MIN) {
pipeFreq -= 2;
if (pipeFreq < PIPE_FREQ_MIN) pipeFreq = PIPE_FREQ_MIN;
}
if (pipeSpeed > PIPE_SPEED_MAX) {
pipeSpeed -= 0.5;
if (pipeSpeed < PIPE_SPEED_MAX) pipeSpeed = PIPE_SPEED_MAX;
}
character.gravity += 0.02;
}
}
// Golds movement and collection
for (var i = golds.length - 1; i >= 0; i--) {
var gold = golds[i];
gold.update();
// Remove gold if off screen
if (gold.x + 50 < 0) {
gold.destroy();
golds.splice(i, 1);
continue;
}
// Collision with character
if (!gold.collected && rectsIntersect(character.getBounds(), gold.getBounds())) {
gold.collected = true;
gold.destroy();
golds.splice(i, 1);
// Only increase score if this is a Gold, not a Powerup
if (gold instanceof Gold) {
LK.getSound('goldcollect').play();
score += 3;
scoreTxt.setText(score);
}
// Powerup collection: activate or extend auto-fly mode
if (gold instanceof Powerup) {
LK.getSound('powerupcollect').play();
// Stop powerupmusic if already playing before starting again
LK.stopMusic('powerupmusic');
// Fade out bgmusic to 20% and play powerupmusic
LK.playMusic('bgmusic', {
fade: {
start: 1,
end: 0.2,
duration: 400
}
});
LK.playMusic('powerupmusic', {
loop: false
});
if (!powerupActive) {
powerupActive = true;
powerupPipesLeft = 3;
// Find the next pipe the player will pass
autoFlyPipeIndex = null;
for (var pi = 0; pi < pipes.length; pi++) {
if (!pipes[pi].passed && pipes[pi].x + PIPE_WIDTH / 2 > character.x) {
autoFlyPipeIndex = pi;
break;
}
}
// If not found, just use the last pipe
if (autoFlyPipeIndex === null && pipes.length > 0) autoFlyPipeIndex = pipes.length - 1;
autoFlyTargetY = null;
autoFlyJustPassedPipe = false;
// Set velocity to 0, but do NOT override y or gravity; player keeps control, collisions are ignored
character.velocity = 0;
// Start blinking effect
character._powerupBlinking = false;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = 0;
} else {
// If already active, increase duration by 3 more pipes, but cap at 3
powerupPipesLeft += 3;
if (powerupPipesLeft > 3) powerupPipesLeft = 3;
// Cancel pending end timeout if any, so effect doesn't end early
if (game._powerupEndTimeout) {
LK.clearTimeout(game._powerupEndTimeout);
game._powerupEndTimeout = null;
}
}
}
// Optionally: add a flash or effect here
}
}
// --- Ensure character is always rendered above pipes and golds ---
if (character.parent) {
character.parent.removeChild(character);
}
game.addChild(character);
// Pipe spawning
if (LK.ticks - lastPipeTick >= pipeFreq) {
spawnPipePair();
lastPipeTick = LK.ticks;
}
};
// Rectangle intersection helper
function rectsIntersect(a, b) {
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
}
// Spawn a new pipe pair
function spawnPipePair() {
var gapHeight = PIPE_GAP_MIN + Math.floor(Math.random() * (PIPE_GAP_MAX - PIPE_GAP_MIN + 1));
var minGapY = 180;
var maxGapY = GAME_HEIGHT - gapHeight - 180;
var gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY + 1));
var pipePair = new PipePair();
pipePair.x = GAME_WIDTH;
pipePair.y = 0;
pipePair.setGap(gapY, gapHeight);
pipePair.speed = pipeSpeed;
pipePair.passed = false;
pipes.push(pipePair);
game.addChild(pipePair);
// 15% chance to spawn Powerup, 85% chance to spawn Gold
if (Math.random() < 0.15) {
var powerup = new Powerup();
powerup.x = pipePair.x + PIPE_WIDTH / 2 + 60;
powerup.y = gapY + gapHeight / 2;
powerup.speed = pipeSpeed;
golds.push(powerup);
game.addChild(powerup);
} else {
var gold = new Gold();
gold.x = pipePair.x + PIPE_WIDTH / 2 + 60;
gold.y = gapY + gapHeight / 2;
gold.speed = pipeSpeed;
golds.push(gold);
game.addChild(gold);
}
}
// Game over logic
function triggerGameOver() {
if (gameOver) return;
gameOver = true;
LK.getSound('hitsound').play();
LK.effects.flashScreen(0xff0000, 800);
// Do not stop or fade out bgmusic here; let it continue during game over screen
// (No call to LK.stopMusic or LK.playMusic for bgmusic here)
LK.showGameOver();
}
// Reset game on game over
LK.on('gameover', function () {
resetGame();
});
// Initial game state
resetGame();
LK.playMusic('bgmusic'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Character: the player-controlled ellipse
var Character = Container.expand(function () {
var self = Container.call(this);
var _char = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Physics
self.velocity = 0;
self.gravity = 2.2;
self.flapStrength = -19;
// Flap (jump)
self.flap = function () {
self.velocity = self.flapStrength;
};
// Update position and velocity
self.update = function () {
// Always apply gravity and velocity, even during powerupActive
self.velocity += self.gravity;
self.y += self.velocity;
};
// For collision detection
self.getBounds = function () {
return {
x: self.x - _char.width / 2,
y: self.y - _char.height / 2,
width: _char.width,
height: _char.height
};
};
return self;
});
// CometParticle: a single particle for the comet trail effect
var CometParticle = Container.expand(function () {
var self = Container.call(this);
// Use pure white for a star-like effect
var color = 0xffffff;
// Use a bright ellipse as the particle
var _shape = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.22 + Math.random() * 0.18,
scaleY: 0.16 + Math.random() * 0.14,
color: color
});
// Particle properties
self.lifetime = 22 + Math.random() * 10; // frames
self.age = 0;
self.vx = -6 + Math.random() * -2; // leftward
self.vy = (Math.random() - 0.5) * 4; // slight up/down
self.alpha = 0.85 + Math.random() * 0.15; // Brighter
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.age++;
// Fade out, but keep a bit brighter
self.alpha *= 0.93;
if (self.age > self.lifetime) {
if (self.parent) self.parent.removeChild(self);
self.destroy();
}
};
return self;
});
// Gold: collectible coin between pipes
var Gold = Container.expand(function () {
var self = Container.call(this);
var _gold = self.attachAsset('gold', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self._bobPhase = Math.random() * Math.PI * 2; // randomize phase so coins don't bob in sync
self.update = function () {
self.x += self.speed;
// Animate gold with a gentle up-and-down bobbing motion
self.y += Math.sin(LK.ticks / 18 + self._bobPhase) * 1.5;
};
// For collision detection
self.getBounds = function () {
return {
x: self.x - _gold.width / 2,
y: self.y - _gold.height / 2,
width: _gold.width,
height: _gold.height
};
};
return self;
});
// PipePair: a pair of pipes (top and bottom) with a gap
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe dimensions
var pipeWidth = 220;
var pipeHeight = 1200;
var gapHeight = 500; // Will be set dynamically
// Top pipe
var topPipe = self.attachAsset('pipe_top', {
anchorX: 0,
anchorY: 1
});
// Bottom pipe
var bottomPipe = self.attachAsset('pipe_bottom', {
anchorX: 0,
anchorY: 0
});
// Set pipes' positions based on gapY and gapHeight
self.setGap = function (gapY, gapHeight) {
// Top pipe: bottom at gapY
topPipe.x = 0;
topPipe.y = gapY;
topPipe.height = gapY;
// Bottom pipe: top at gapY+gapHeight
bottomPipe.x = 0;
bottomPipe.y = gapY + gapHeight;
bottomPipe.height = 2732 - (gapY + gapHeight);
};
// For collision detection
self.getTopPipe = function () {
return topPipe;
};
self.getBottomPipe = function () {
return bottomPipe;
};
// Move pipes leftward
self.update = function () {
self.x += self.speed;
};
// Set initial speed
self.speed = -12;
return self;
});
// Powerup: special collectible (separate from Gold)
var Powerup = Container.expand(function () {
var self = Container.call(this);
var _powerup = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self._bobPhase = Math.random() * Math.PI * 2;
self.update = function () {
self.x += self.speed;
// Animate with a gentle up-and-down bobbing motion
self.y += Math.sin(LK.ticks / 18 + self._bobPhase) * 1.5;
};
self.getBounds = function () {
return {
x: self.x - _powerup.width / 2,
y: self.y - _powerup.height / 2,
width: _powerup.width,
height: _powerup.height
};
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4db8ff // Light blue sky
});
/****
* Game Code
****/
// Pipes: green boxes, character: yellow ellipse, background: blue
// Game constants
// Background landscape and clouds
// unique icon for powerup
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PIPE_WIDTH = 220;
var PIPE_GAP_MIN = 480;
var PIPE_GAP_MAX = 700;
var PIPE_DISTANCE = 900; // Horizontal distance between pipes
var PIPE_SPEED_START = -7; // Slower initial pipe speed
var PIPE_SPEED_MAX = -28;
var PIPE_FREQ_START = 120; // Pipes appear less frequently at start
var PIPE_FREQ_MIN = 45;
var GRAVITY_START = 1.05; // Slightly lower gravity for even gentler fall
var FLAP_STRENGTH = -22;
// Game variables
var pipes = [];
var golds = [];
var character;
var score = 0;
var scoreTxt;
var lastPipeTick = 0;
var pipeFreq = PIPE_FREQ_START;
var pipeSpeed = PIPE_SPEED_START;
var gameStarted = false;
var gameOver = false;
// Powerup/auto-fly state
var powerupActive = false;
var powerupPipesLeft = 0;
var autoFlyTargetY = null;
var autoFlyPipeIndex = null;
var autoFlyJustPassedPipe = false;
// Comet particle system for powerup effect
var cometParticles = [];
// Add city background image (behind landscape and clouds)
var bgCity = LK.getAsset('city', {
anchorX: 0,
anchorY: 1,
x: 0,
y: GAME_HEIGHT - 600,
width: GAME_WIDTH,
height: 900
});
var bgCity2 = LK.getAsset('city', {
anchorX: 0,
anchorY: 1,
x: GAME_WIDTH,
y: GAME_HEIGHT - 600,
width: GAME_WIDTH,
height: 900
});
game.addChild(bgCity);
game.addChild(bgCity2);
// Add background landscape (two for seamless looping)
var bgLandscape = LK.getAsset('bg_landscape', {
anchorX: 0,
anchorY: 1,
x: 0,
y: GAME_HEIGHT,
width: GAME_WIDTH,
height: 600
});
var bgLandscape2 = LK.getAsset('bg_landscape', {
anchorX: 0,
anchorY: 1,
x: GAME_WIDTH,
y: GAME_HEIGHT,
width: GAME_WIDTH,
height: 600
});
game.addChild(bgLandscape);
game.addChild(bgLandscape2);
// Add clouds (3 clouds at different positions and scales)
var clouds = [];
for (var i = 0; i < 3; i++) {
var cloud = LK.getAsset('cloud', {
anchorX: 0.5,
anchorY: 0.5,
x: 400 + i * 600,
y: 300 + i * 120,
scaleX: 0.8 + 0.3 * Math.random(),
scaleY: 0.8 + 0.3 * Math.random()
});
game.addChild(cloud);
clouds.push(cloud);
}
// Add score text to GUI
scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF,
stroke: 0x222222,
strokeThickness: 12
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Center score text horizontally, avoid top left 100x100
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = 40;
// Add countdown text for auto-fly powerup
var powerupCountdownTxt = new Text2('', {
size: 100,
fill: 0x00ff00,
font: "Impact, Arial Black, Tahoma"
});
powerupCountdownTxt.anchor.set(0.5, 0);
powerupCountdownTxt.x = LK.gui.top.width / 2;
powerupCountdownTxt.y = 200;
powerupCountdownTxt.visible = false;
LK.gui.top.addChild(powerupCountdownTxt);
// Add restart button to GUI (top right, not overlapping score)
var restartBtn = new Text2('⟳', {
size: 120,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
restartBtn.anchor.set(1, 0); // right-top
restartBtn.x = LK.gui.top.width - 40;
restartBtn.y = 40;
restartBtn.interactive = true;
restartBtn.buttonMode = true;
restartBtn.visible = false; // Only show after pause
restartBtn.down = function (x, y, obj) {
if (!gameOver) return;
resetGame();
};
LK.gui.top.addChild(restartBtn);
// Show restart button after pause, hide otherwise
LK.on('pause', function () {
restartBtn.visible = true;
});
LK.on('resume', function () {
restartBtn.visible = false;
});
LK.on('gameover', function () {
resetGame();
restartBtn.visible = false;
});
// Create character and position
character = new Character();
character.x = GAME_WIDTH * 0.35;
character.y = GAME_HEIGHT / 2;
character.gravity = GRAVITY_START;
character.flapStrength = FLAP_STRENGTH;
// Add character after pipes and collectibles so it renders above them
game.addChild(character);
// Reset game state
function resetGame() {
// Remove pipes
for (var i = pipes.length - 1; i >= 0; i--) {
pipes[i].destroy();
pipes.splice(i, 1);
}
// Reset lastX for all pipes (defensive, in case any remain)
for (var i = 0; i < pipes.length; i++) {
pipes[i].lastX = undefined;
}
// Remove golds
for (var i = golds.length - 1; i >= 0; i--) {
golds[i].destroy();
golds.splice(i, 1);
}
// Reset character
character.x = GAME_WIDTH * 0.35;
character.y = GAME_HEIGHT / 2;
character.velocity = 0;
character.gravity = GRAVITY_START;
character.flapStrength = FLAP_STRENGTH;
// Reset variables
score = 0;
scoreTxt.setText(score);
lastPipeTick = 0;
pipeFreq = PIPE_FREQ_START; // Use new slower frequency
pipeSpeed = PIPE_SPEED_START; // Use new slower speed
gameStarted = false;
gameOver = false;
// Reset powerup/auto-fly state
powerupActive = false;
powerupPipesLeft = 0;
autoFlyTargetY = null;
autoFlyPipeIndex = null;
autoFlyJustPassedPipe = false;
// Reset bird color and stop blinking
if (character._powerupBlinkTween) tween.stop(character, {
tint: true
});
character.tint = 0xffffff;
character._powerupBlinking = false;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = 0;
// Hide countdown text
if (typeof powerupCountdownTxt !== "undefined") {
powerupCountdownTxt.visible = false;
powerupCountdownTxt.setText('');
}
// Clear any pending auto-fly end timeout
if (game._powerupEndTimeout) {
LK.clearTimeout(game._powerupEndTimeout);
game._powerupEndTimeout = null;
}
}
// Start game on first tap
function startGame() {
if (!gameStarted && !gameOver) {
gameStarted = true;
character.flap();
}
}
// Flap on tap/click
game.down = function (x, y, obj) {
if (gameOver) return;
// Allow input during powerup auto-fly
if (!gameStarted) {
startGame();
} else {
character.flap();
}
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Animate clouds (move right to left, loop)
var bgMoveSpeed = Math.abs(pipeSpeed) * 0.5;
if (powerupActive && autoFlyPipeIndex !== null && pipes[autoFlyPipeIndex]) {
// During auto-fly, match background and clouds speed to the pipe's speed
bgMoveSpeed = Math.abs(pipes[autoFlyPipeIndex].speed) * 0.5;
}
for (var i = 0; i < clouds.length; i++) {
var cloud = clouds[i];
var cloudMoveSpeed = 0.7 + 0.2 * i;
if (powerupActive && autoFlyPipeIndex !== null && pipes[autoFlyPipeIndex]) {
cloudMoveSpeed = Math.abs(pipes[autoFlyPipeIndex].speed) * (0.7 + 0.2 * i) / Math.abs(pipeSpeed);
}
cloud.x -= cloudMoveSpeed;
if (cloud.x < -cloud.width / 2) {
cloud.x = GAME_WIDTH + cloud.width / 2 + Math.random() * 200;
cloud.y = 200 + Math.random() * 400 + i * 100;
}
}
// Animate city background to move leftward and loop (slower for parallax effect)
var cityMoveSpeed = bgMoveSpeed * 0.5;
bgCity.x -= cityMoveSpeed;
bgCity2.x -= cityMoveSpeed;
if (bgCity.x <= -GAME_WIDTH) {
bgCity.x = bgCity2.x + GAME_WIDTH;
}
if (bgCity2.x <= -GAME_WIDTH) {
bgCity2.x = bgCity.x + GAME_WIDTH;
}
// Animate background landscape to move leftward and loop
bgLandscape.x -= bgMoveSpeed;
bgLandscape2.x -= bgMoveSpeed;
// Loop both backgrounds for seamless scrolling
if (bgLandscape.x <= -GAME_WIDTH) {
bgLandscape.x = bgLandscape2.x + GAME_WIDTH;
}
if (bgLandscape2.x <= -GAME_WIDTH) {
bgLandscape2.x = bgLandscape.x + GAME_WIDTH;
}
if (!gameStarted) {
// Idle animation: bob up and down
character.y = GAME_HEIGHT / 2 + Math.sin(LK.ticks / 30) * 30;
return;
}
// Character physics
if (powerupActive) {
// Find the next pipe to pass if needed
if (autoFlyPipeIndex === null || autoFlyPipeIndex >= pipes.length) {
// Find the next pipe
for (var pi = 0; pi < pipes.length; pi++) {
if (!pipes[pi].passed && pipes[pi].x + PIPE_WIDTH / 2 > character.x) {
autoFlyPipeIndex = pi;
break;
}
}
if (autoFlyPipeIndex === null && pipes.length > 0) autoFlyPipeIndex = pipes.length - 1;
}
// Set target Y to the center of the gap of the next pipe
if (autoFlyPipeIndex !== null && pipes[autoFlyPipeIndex]) {
var pipePair = pipes[autoFlyPipeIndex];
// Calculate gap center
var gapY = pipePair.getTopPipe().height;
var gapHeight = pipePair.getBottomPipe().y - pipePair.getTopPipe().height;
autoFlyTargetY = gapY + gapHeight / 2;
// Do NOT override character.x or character.y; player keeps full control
// Calculate the pipe's center X
var pipeCenterX = pipePair.x + PIPE_WIDTH / 2;
// Store the last pipe center X for transition detection
if (pipePair.lastX === undefined) pipePair.lastX = pipeCenterX;
// Detect passing through the pipe (only when crossing the pipe center this frame)
if (pipePair.lastX >= character.x && pipeCenterX < character.x) {
powerupPipesLeft--;
autoFlyJustPassedPipe = true;
}
// When the pipe is behind, reset for next pipe
if (autoFlyJustPassedPipe && pipeCenterX < character.x - 40) {
autoFlyPipeIndex++;
autoFlyJustPassedPipe = false;
}
pipePair.lastX = pipeCenterX;
// Animate bird's color to blink green during auto-fly, increasing blink frequency towards the end
if (!character._powerupBlinking) {
character._powerupBlinking = true;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = powerupPipesLeft * 12; // estimate 12 blinks per pipe
}
if (character._powerupBlinking) {
// Calculate blink frequency: faster as powerupPipesLeft decreases
var minBlink = 180,
maxBlink = 60; // ms
var blinkDuration = minBlink - (minBlink - maxBlink) * (3 - powerupPipesLeft) / 3;
if (!character._powerupBlinkTween || character._powerupBlinkTween._finished) {
// Alternate between green and normal
var toGreen = character._powerupBlinkStep % 2 === 0;
var targetTint = toGreen ? 0x00ff00 : 0xffffff;
if (character._powerupBlinkTween) tween.stop(character, {
tint: true
});
character._powerupBlinkTween = tween(character, {
tint: targetTint
}, {
duration: blinkDuration,
easing: tween.linear
});
character._powerupBlinkStep++;
}
}
}
// Show and update countdown text during auto-fly
if (powerupActive && powerupPipesLeft > 0) {
powerupCountdownTxt.visible = true;
powerupCountdownTxt.setText("You are immortal for: " + powerupPipesLeft);
} else {
powerupCountdownTxt.visible = false;
}
// End powerup after 3 pipes, but delay deactivation by 0.5s
if (powerupPipesLeft <= 0 && powerupActive) {
if (!game._powerupEndTimeout) {
game._powerupEndTimeout = LK.setTimeout(function () {
powerupActive = false;
autoFlyTargetY = null;
autoFlyPipeIndex = null;
autoFlyJustPassedPipe = false;
// Stop blinking and reset color
if (character._powerupBlinkTween) tween.stop(character, {
tint: true
});
character.tint = 0xffffff;
character._powerupBlinking = false;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = 0;
// Stop powerupmusic and fade bgmusic back to 100%
LK.stopMusic('powerupmusic');
LK.playMusic('bgmusic', {
fade: {
start: 0.2,
end: 1,
duration: 400
}
});
game._powerupEndTimeout = null;
}, 500);
}
}
// Always call character.update() so player has full control
character.update();
// Spawn comet particles if powerupActive
if (powerupActive) {
// Spawn 2-3 particles per frame for a dense trail
for (var cp = 0; cp < 2 + Math.floor(Math.random() * 2); cp++) {
var p = new CometParticle();
// Place particle slightly behind and below the bird for a comet tail
p.x = character.x - 40 + Math.random() * 10;
p.y = character.y + 20 + Math.random() * 16 - 8;
p.alpha = 0.6 + Math.random() * 0.25;
cometParticles.push(p);
game.addChildAt ? game.addChildAt(p, 0) : game.addChild(p); // add behind bird if possible
}
}
// Update and clean up comet particles
for (var i = cometParticles.length - 1; i >= 0; i--) {
var part = cometParticles[i];
part.update();
if (part.age > part.lifetime || part.alpha < 0.05) {
if (part.parent) part.parent.removeChild(part);
cometParticles.splice(i, 1);
}
}
} else {
character.update();
}
// Clamp character to screen
if (character.y < character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2) {
character.y = character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2;
character.velocity = 0;
}
if (character.y > GAME_HEIGHT - character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2) {
character.y = GAME_HEIGHT - character.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
}).height / 2;
character.velocity = 0;
if (!powerupActive) {
triggerGameOver();
return;
}
// If powerupActive, do not trigger game over, just clamp position
}
// Pipes movement and collision
for (var i = pipes.length - 1; i >= 0; i--) {
var pipePair = pipes[i];
pipePair.update();
// Remove pipes off screen
if (pipePair.x + PIPE_WIDTH < 0) {
pipePair.destroy();
pipes.splice(i, 1);
continue;
}
// Collision detection
var charBounds = character.getBounds();
var topPipe = pipePair.getTopPipe();
var bottomPipe = pipePair.getBottomPipe();
// Top pipe collision
if (rectsIntersect(charBounds, {
x: pipePair.x,
y: 0,
width: PIPE_WIDTH,
height: topPipe.height
})) {
if (!powerupActive) {
triggerGameOver();
return;
}
// If powerupActive, ignore collision and allow player to keep playing
}
// Bottom pipe collision
if (rectsIntersect(charBounds, {
x: pipePair.x,
y: bottomPipe.y,
width: PIPE_WIDTH,
height: bottomPipe.height
})) {
if (!powerupActive) {
triggerGameOver();
return;
}
// If powerupActive, ignore collision and allow player to keep playing
}
// Score: when character passes the pipe (center)
if (!pipePair.passed && pipePair.x + PIPE_WIDTH / 2 < character.x) {
pipePair.passed = true;
score += 1;
scoreTxt.setText(score);
// Increase difficulty
if (pipeFreq > PIPE_FREQ_MIN) {
pipeFreq -= 2;
if (pipeFreq < PIPE_FREQ_MIN) pipeFreq = PIPE_FREQ_MIN;
}
if (pipeSpeed > PIPE_SPEED_MAX) {
pipeSpeed -= 0.5;
if (pipeSpeed < PIPE_SPEED_MAX) pipeSpeed = PIPE_SPEED_MAX;
}
character.gravity += 0.02;
}
}
// Golds movement and collection
for (var i = golds.length - 1; i >= 0; i--) {
var gold = golds[i];
gold.update();
// Remove gold if off screen
if (gold.x + 50 < 0) {
gold.destroy();
golds.splice(i, 1);
continue;
}
// Collision with character
if (!gold.collected && rectsIntersect(character.getBounds(), gold.getBounds())) {
gold.collected = true;
gold.destroy();
golds.splice(i, 1);
// Only increase score if this is a Gold, not a Powerup
if (gold instanceof Gold) {
LK.getSound('goldcollect').play();
score += 3;
scoreTxt.setText(score);
}
// Powerup collection: activate or extend auto-fly mode
if (gold instanceof Powerup) {
LK.getSound('powerupcollect').play();
// Stop powerupmusic if already playing before starting again
LK.stopMusic('powerupmusic');
// Fade out bgmusic to 20% and play powerupmusic
LK.playMusic('bgmusic', {
fade: {
start: 1,
end: 0.2,
duration: 400
}
});
LK.playMusic('powerupmusic', {
loop: false
});
if (!powerupActive) {
powerupActive = true;
powerupPipesLeft = 3;
// Find the next pipe the player will pass
autoFlyPipeIndex = null;
for (var pi = 0; pi < pipes.length; pi++) {
if (!pipes[pi].passed && pipes[pi].x + PIPE_WIDTH / 2 > character.x) {
autoFlyPipeIndex = pi;
break;
}
}
// If not found, just use the last pipe
if (autoFlyPipeIndex === null && pipes.length > 0) autoFlyPipeIndex = pipes.length - 1;
autoFlyTargetY = null;
autoFlyJustPassedPipe = false;
// Set velocity to 0, but do NOT override y or gravity; player keeps control, collisions are ignored
character.velocity = 0;
// Start blinking effect
character._powerupBlinking = false;
character._powerupBlinkTween = null;
character._powerupBlinkStep = 0;
character._powerupBlinkTotal = 0;
} else {
// If already active, increase duration by 3 more pipes, but cap at 3
powerupPipesLeft += 3;
if (powerupPipesLeft > 3) powerupPipesLeft = 3;
// Cancel pending end timeout if any, so effect doesn't end early
if (game._powerupEndTimeout) {
LK.clearTimeout(game._powerupEndTimeout);
game._powerupEndTimeout = null;
}
}
}
// Optionally: add a flash or effect here
}
}
// --- Ensure character is always rendered above pipes and golds ---
if (character.parent) {
character.parent.removeChild(character);
}
game.addChild(character);
// Pipe spawning
if (LK.ticks - lastPipeTick >= pipeFreq) {
spawnPipePair();
lastPipeTick = LK.ticks;
}
};
// Rectangle intersection helper
function rectsIntersect(a, b) {
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
}
// Spawn a new pipe pair
function spawnPipePair() {
var gapHeight = PIPE_GAP_MIN + Math.floor(Math.random() * (PIPE_GAP_MAX - PIPE_GAP_MIN + 1));
var minGapY = 180;
var maxGapY = GAME_HEIGHT - gapHeight - 180;
var gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY + 1));
var pipePair = new PipePair();
pipePair.x = GAME_WIDTH;
pipePair.y = 0;
pipePair.setGap(gapY, gapHeight);
pipePair.speed = pipeSpeed;
pipePair.passed = false;
pipes.push(pipePair);
game.addChild(pipePair);
// 15% chance to spawn Powerup, 85% chance to spawn Gold
if (Math.random() < 0.15) {
var powerup = new Powerup();
powerup.x = pipePair.x + PIPE_WIDTH / 2 + 60;
powerup.y = gapY + gapHeight / 2;
powerup.speed = pipeSpeed;
golds.push(powerup);
game.addChild(powerup);
} else {
var gold = new Gold();
gold.x = pipePair.x + PIPE_WIDTH / 2 + 60;
gold.y = gapY + gapHeight / 2;
gold.speed = pipeSpeed;
golds.push(gold);
game.addChild(gold);
}
}
// Game over logic
function triggerGameOver() {
if (gameOver) return;
gameOver = true;
LK.getSound('hitsound').play();
LK.effects.flashScreen(0xff0000, 800);
// Do not stop or fade out bgmusic here; let it continue during game over screen
// (No call to LK.stopMusic or LK.playMusic for bgmusic here)
LK.showGameOver();
}
// Reset game on game over
LK.on('gameover', function () {
resetGame();
});
// Initial game state
resetGame();
LK.playMusic('bgmusic');
give me some alternatives of real flappy bird. In-Game asset. 2d. High contrast. No shadows. Just one image
flappy bird cloud, pixel art. In-Game asset. 2d. High contrast. No shadows
flappy bird ground. In-Game asset. 2d. High contrast. No shadows. pixel art. suitable for looping
flappy bird gold icon. pixel art style. In-Game asset. 2d. High contrast. No shadows
flappy bird power up icon. pixel art style. In-Game asset. 2d. High contrast. No shadows
Restyled