/**** * 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