User prompt
Make the easiest, most fun and non-realistic version of Flappy Bird ever! Here’s exactly what I want: Super Easy Mode: Very slow gameplay (bird moves forward at a snail’s pace). HUGE gaps between pipes (at least 2x wider than normal). Low gravity (bird falls super slowly, like a feather). Auto-gliding (bird floats smoothly, no sudden drops). Cartoonish & Fun: Silly physics (bird bounces harmlessly off pipes, no ‘game over’). Rainbow trails or sparkles when flying. Funny sound effects (boing on bounce, party horn on score). Zero Stress: Unlimited lives (never die, just keep flying forever). Happy music (upbeat ukulele or chiptune). Smiling obstacles (pipes with googly eyes or clouds that cheer). Extra Fun: Tap to do a flip (just for visual fun, no penalty). Confetti explosion every 10 points. Make it so easy my grandma could play it blindfolded! Bonus: If possible, add a ‘Super Easy’ toggle in settings to make it even easier later!" ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Lower the gravity so the bird falls more slowly, and keep all the other easy-mode changes (slower speed, wider pipe gaps). Make it super casual!"
User prompt
Make the game slower, increase the distance between pipes, and keep it easy but fun
Code edit (1 edits merged)
Please save this source code
User prompt
Flap & Dash: Endless Flight
Initial prompt
Core Requirements: Basic Gameplay: Control a bird (or another character) that moves continuously to the right. Tap/Press to make the bird flap and avoid obstacles (pipes, buildings, etc.). Colliding with obstacles or the ground ends the game. Enhanced Features: Smoother Physics: Improve the jump/flap mechanics to feel more responsive. Procedural Levels: Randomly generated gaps and obstacles for replayability. Visual Upgrades: High-quality pixel art, smooth animations, or minimalist modern design. Particles & Effects: Add dust clouds, wing flaps, and screen shake on collisions. Difficulty Curve: Gradually increase speed and obstacle density. Additional Content: Multiple Characters: Unlockable birds/characters with unique abilities (e.g., faster fall, double jump). Themes: Day/night cycles or seasonal backgrounds. Power-ups: Temporary invincibility, score multipliers, or slow-mo. Sound Design: Background music + satisfying flap/crash sounds. UI/UX: Main menu with Start, Settings, and Character Select. Score counter + high score saving (local storage). Pause button and "Game Over" screen with a restart option. Optimization: Ensure it runs smoothly on web/mobile (60 FPS). Test for input lag (critical for a flappy-style game). Instructions for Upitop: Provide the full source code (HTML5/JavaScript, Unity, or exported executable). Include assets (sprites, sounds) and documentation if needed. Explain how to modify difficulty, art, or controls. Let me know if you need clarification or want to adjust features!"
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ // Bird (player) class var Bird = Container.expand(function () { var self = Container.call(this); var birdSprite = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); self.radius = birdSprite.width * 0.5; self.vy = 0; // vertical velocity self.gravity = 0.22; // super low gravity for feather-like fall self.flapStrength = -16; // gentle flap self.alive = true; self.rotationMax = Math.PI / 4; // 45deg self.rotationMin = -Math.PI / 3; // -60deg // Flap (jump) self.flap = function () { if (!self.alive) return; self.vy = self.flapStrength; LK.getSound('flap').play(); // Animate a quick upward tilt tween(self, { rotation: self.rotationMin }, { duration: 80, easing: tween.cubicOut }); // Particle burst spawnParticles(self.x - 40, self.y, 8); }; // Die self.die = function () { self.alive = false; // Play hit sound LK.getSound('hit').play(); // Flash effect LK.effects.flashScreen(0xff0000, 400); // Quick spin tween(self, { rotation: Math.PI * 2 }, { duration: 400, easing: tween.cubicIn }); }; // Update per frame self.update = function () { if (!self.alive) return; self.vy += self.gravity; self.y += self.vy; // Rotate bird based on velocity var targetRot = Math.max(self.rotationMin, Math.min(self.rotationMax, self.vy * 0.025)); self.rotation += (targetRot - self.rotation) * 0.25; }; return self; }); // Ground class var Ground = Container.expand(function () { var self = Container.call(this); var groundSprite = self.attachAsset('ground', { anchorX: 0, anchorY: 0 }); self.getSprite = function () { return groundSprite; }; return self; }); // Particle class (for flapping effect) var Particle = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); self.vx = 0; self.vy = 0; self.life = 0; self.init = function (x, y, vx, vy, life) { self.x = x; self.y = y; self.vx = vx; self.vy = vy; self.life = life; sprite.alpha = 1; sprite.scaleX = sprite.scaleY = 1; }; self.update = function () { self.x += self.vx; self.y += self.vy; self.vy += 0.7; // gravity self.life--; sprite.alpha = Math.max(0, self.life / 20); sprite.scaleX = sprite.scaleY = 0.7 + 0.3 * (self.life / 20); }; return self; }); // Pipe (obstacle) class var Pipe = Container.expand(function () { var self = Container.call(this); // Top or bottom pipe self.isTop = false; self.passed = false; // Has the bird passed this pipe? // Pipe body var pipeSprite = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 0 }); // Add googly eyes and smile var eyeL = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: 0xffffff, width: 32, height: 32 }); var eyeR = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: 0xffffff, width: 32, height: 32 }); var pupilL = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: 0x222222, width: 12, height: 12 }); var pupilR = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: 0x222222, width: 12, height: 12 }); var smile = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: 0xffe066, width: 28, height: 8 }); // Position eyes and smile eyeL.x = -32; eyeL.y = 38; eyeR.x = 32; eyeR.y = 38; pupilL.x = -32; pupilL.y = 38; pupilR.x = 32; pupilR.y = 38; smile.x = 0; smile.y = 68; smile.scaleX = 1.2; smile.scaleY = 0.7; // Animate pupils for fun self.update = function () { self.x -= pipeSpeed; var t = LK.ticks * 0.13; pupilL.x = eyeL.x + Math.sin(t) * 4; pupilL.y = eyeL.y + Math.cos(t) * 2; pupilR.x = eyeR.x + Math.cos(t) * 4; pupilR.y = eyeR.y + Math.sin(t) * 2; smile.scaleX = 1.2 + Math.sin(t) * 0.15; smile.scaleY = 0.7 + Math.cos(t) * 0.07; }; self.setHeight = function (h) { pipeSprite.height = h; }; // For collision, use the pipeSprite self.getSprite = function () { return pipeSprite; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ LK.playMusic('happy_uke', { loop: true, fade: { start: 0, end: 0.7, duration: 1200 } }); // Sound: Hit // Sound: Score // Sound: Flap // Particle asset: white ellipse // Ground asset: brown box // Obstacle (pipe) asset: green box // Bird (player) asset: yellow ellipse // Game constants var BIRD_START_X = 600; var BIRD_START_Y = 1100; var PIPE_GAP = 900; // HUGE gap for super easy mode var PIPE_MIN = 350; var PIPE_MAX = 1700; var PIPE_INTERVAL = 1700; // much more distance between pipes var pipeSpeed = 3.2; // very slow pipe speed var GROUND_Y = 2732 - 120; var PARTICLE_POOL_SIZE = 32; // Game state var bird; var pipes = []; var ground; var score = 0; var highScore = storage.highScore || 0; var gameStarted = false; var gameOver = false; var lastPipeX = 0; var dragNode = null; var particles = []; var particlePool = []; var scoreTxt, highScoreTxt, tapTxt; // GUI: Score display scoreTxt = new Text2('0', { size: 140, fill: 0xFFF700, font: "Impact" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // GUI: High score display highScoreTxt = new Text2('BEST: ' + highScore, { size: 60, fill: 0xFFFFFF, font: "Impact" }); highScoreTxt.anchor.set(1, 0); LK.gui.topRight.addChild(highScoreTxt); // GUI: Tap to start tapTxt = new Text2('TAP TO FLAP', { size: 100, fill: 0xFFFFFF, font: "Impact" }); tapTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(tapTxt); // Spawn ground ground = new Ground(); ground.x = 0; ground.y = GROUND_Y; game.addChild(ground); // Spawn bird bird = new Bird(); bird.x = BIRD_START_X; bird.y = BIRD_START_Y; game.addChild(bird); // Particle pool for (var i = 0; i < PARTICLE_POOL_SIZE; i++) { var p = new Particle(); particlePool.push(p); } // Reset game state function resetGame() { // Remove pipes for (var i = 0; i < pipes.length; i++) { pipes[i].destroy(); } pipes = []; // Reset bird bird.x = BIRD_START_X; bird.y = BIRD_START_Y; bird.vy = 0; bird.rotation = 0; bird.alive = true; // Reset score score = 0; scoreTxt.setText(score); // Remove tap text if present tapTxt.visible = true; // Reset state gameStarted = false; gameOver = false; lastPipeX = 1700; // Remove all particles for (var i = 0; i < particles.length; i++) { particles[i].destroy(); } particles = []; } // Spawn a pair of pipes (top and bottom) function spawnPipePair(x) { var gapY = PIPE_MIN + Math.floor(Math.random() * (PIPE_MAX - PIPE_MIN)); // Top pipe var topPipe = new Pipe(); topPipe.isTop = true; topPipe.x = x; topPipe.y = gapY - PIPE_GAP - 800; // pipe height is 800 topPipe.setHeight(800); game.addChild(topPipe); pipes.push(topPipe); // Bottom pipe var bottomPipe = new Pipe(); bottomPipe.isTop = false; bottomPipe.x = x; bottomPipe.y = gapY; bottomPipe.setHeight(800); game.addChild(bottomPipe); pipes.push(bottomPipe); } // Particle burst at (x, y) function spawnParticles(x, y, count) { for (var i = 0; i < count; i++) { var p = particlePool.length > 0 ? particlePool.pop() : new Particle(); var angle = Math.random() * Math.PI * 2; var speed = 7 + Math.random() * 6; p.init(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed, 20 + Math.floor(Math.random() * 10)); game.addChild(p); particles.push(p); } } // Rainbow trail particles function spawnRainbowParticles(x, y, count) { var rainbow = [0xff0000, 0xff7f00, 0xffff00, 0x00ff00, 0x0000ff, 0x4b0082, 0x9400d3]; for (var i = 0; i < count; i++) { var p = particlePool.length > 0 ? particlePool.pop() : new Particle(); var angle = Math.PI + (Math.random() - 0.5) * 0.7; var speed = 2 + Math.random() * 2; var color = rainbow[(LK.ticks + i) % rainbow.length]; p.init(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed, 18 + Math.floor(Math.random() * 6)); p.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: color }); game.addChild(p); particles.push(p); } } // Confetti explosion function spawnConfetti(x, y, count) { var confettiColors = [0xffe066, 0xff5e5b, 0x3ecf4c, 0x5bc0eb, 0xfde74c, 0x9bc53d, 0xe55934, 0xfa7921]; for (var i = 0; i < count; i++) { var p = particlePool.length > 0 ? particlePool.pop() : new Particle(); var angle = Math.random() * Math.PI * 2; var speed = 7 + Math.random() * 7; var color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; p.init(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed, 24 + Math.floor(Math.random() * 10)); p.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, color: color }); game.addChild(p); particles.push(p); } } // Start game on first tap function startGame() { if (gameStarted) return; tapTxt.visible = false; gameStarted = true; gameOver = false; score = 0; scoreTxt.setText(score); // Remove all pipes for (var i = 0; i < pipes.length; i++) { pipes[i].destroy(); } pipes = []; // Spawn initial pipes lastPipeX = 1700; for (var i = 0; i < 4; i++) { spawnPipePair(lastPipeX + i * PIPE_INTERVAL); } } // Handle tap/flap function handleFlap() { if (gameOver) return; if (!gameStarted) { startGame(); } bird.flap(); // Do a flip! tween(bird, { rotation: bird.rotation + Math.PI * 2 }, { duration: 350, easing: tween.cubicInOut }); } // Touch/click events game.down = function (x, y, obj) { if (gameOver) return; handleFlap(); }; // Main update loop game.update = function () { // Bird update bird.update(); // Particle update for (var i = particles.length - 1; i >= 0; i--) { var p = particles[i]; p.update(); if (p.life <= 0) { p.destroy(); particlePool.push(p); particles.splice(i, 1); } } // Rainbow/sparkle trail: spawn every 2 frames while flying if (gameStarted && LK.ticks % 2 === 0) { spawnRainbowParticles(bird.x - 50, bird.y, 1); } // If not started, bird hovers if (!gameStarted) { bird.y += Math.sin(LK.ticks / 20) * 1.2; return; } // Pipes update for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; pipe.update(); // Remove pipes off screen if (pipe.x < -200) { pipe.destroy(); pipes.splice(i, 1); continue; } // Score: only for bottom pipes if (!pipe.passed && !pipe.isTop && pipe.x + 90 < bird.x) { pipe.passed = true; score++; scoreTxt.setText(score); LK.getSound('score').play(); // Confetti explosion every 10 points if (score > 0 && score % 10 === 0) { spawnConfetti(bird.x, bird.y - 60, 32); } // Update high score if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('BEST: ' + highScore); } } } // Spawn new pipes if (pipes.length > 0) { var lastPipe = pipes[pipes.length - 1]; if (lastPipe.x < 2048 - PIPE_INTERVAL) { lastPipeX += PIPE_INTERVAL; spawnPipePair(lastPipeX); } } // Collision: ground if (bird.y + bird.radius > GROUND_Y) { if (!gameOver) { bird.y = GROUND_Y - bird.radius; bird.die(); endGame(); } } // Collision: pipes for (var i = 0; i < pipes.length; i++) { var pipe = pipes[i]; var sprite = pipe.getSprite(); // Simple AABB collision var bx = bird.x, by = bird.y, br = bird.radius * 0.82; var px = pipe.x - sprite.width * 0.5, py = pipe.y, pw = sprite.width, ph = sprite.height; if (bx + br > px && bx - br < px + pw && by + br > py && by - br < py + ph) { // Silly bounce: reverse vertical velocity, play boing sound, and do a quick scale animation if (bird.vy > 0) { bird.vy = -Math.abs(bird.vy) * 0.7 - 8 - Math.random() * 6; } else { bird.vy = Math.abs(bird.vy) * 0.7 + 8 + Math.random() * 6; } LK.getSound('hit').play(); tween(bird, { scaleX: 1.3, scaleY: 0.7 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(bird, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); // Rainbow trail burst spawnRainbowParticles(bird.x, bird.y, 10); // No game over! } } }; // End game function endGame() { gameOver = true; tapTxt.setText('GAME OVER\nTAP TO RESTART'); tapTxt.visible = true; LK.showGameOver(); // Reset after popup LK.setTimeout(function () { resetGame(); }, 800); } // On game over popup close, reset game LK.on('gameover', function () { resetGame(); }); // On you win (not used in endless, but for completeness) LK.on('youwin', function () { resetGame(); }); // On resize, keep tap text centered (handled by LK.gui, but for safety) LK.on('resize', function () { tapTxt.anchor.set(0.5, 0.5); }); // Initial reset resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Bird (player) class
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdSprite = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = birdSprite.width * 0.5;
self.vy = 0; // vertical velocity
self.gravity = 0.22; // super low gravity for feather-like fall
self.flapStrength = -16; // gentle flap
self.alive = true;
self.rotationMax = Math.PI / 4; // 45deg
self.rotationMin = -Math.PI / 3; // -60deg
// Flap (jump)
self.flap = function () {
if (!self.alive) return;
self.vy = self.flapStrength;
LK.getSound('flap').play();
// Animate a quick upward tilt
tween(self, {
rotation: self.rotationMin
}, {
duration: 80,
easing: tween.cubicOut
});
// Particle burst
spawnParticles(self.x - 40, self.y, 8);
};
// Die
self.die = function () {
self.alive = false;
// Play hit sound
LK.getSound('hit').play();
// Flash effect
LK.effects.flashScreen(0xff0000, 400);
// Quick spin
tween(self, {
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.cubicIn
});
};
// Update per frame
self.update = function () {
if (!self.alive) return;
self.vy += self.gravity;
self.y += self.vy;
// Rotate bird based on velocity
var targetRot = Math.max(self.rotationMin, Math.min(self.rotationMax, self.vy * 0.025));
self.rotation += (targetRot - self.rotation) * 0.25;
};
return self;
});
// Ground class
var Ground = Container.expand(function () {
var self = Container.call(this);
var groundSprite = self.attachAsset('ground', {
anchorX: 0,
anchorY: 0
});
self.getSprite = function () {
return groundSprite;
};
return self;
});
// Particle class (for flapping effect)
var Particle = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.life = 0;
self.init = function (x, y, vx, vy, life) {
self.x = x;
self.y = y;
self.vx = vx;
self.vy = vy;
self.life = life;
sprite.alpha = 1;
sprite.scaleX = sprite.scaleY = 1;
};
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.vy += 0.7; // gravity
self.life--;
sprite.alpha = Math.max(0, self.life / 20);
sprite.scaleX = sprite.scaleY = 0.7 + 0.3 * (self.life / 20);
};
return self;
});
// Pipe (obstacle) class
var Pipe = Container.expand(function () {
var self = Container.call(this);
// Top or bottom pipe
self.isTop = false;
self.passed = false; // Has the bird passed this pipe?
// Pipe body
var pipeSprite = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0
});
// Add googly eyes and smile
var eyeL = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xffffff,
width: 32,
height: 32
});
var eyeR = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xffffff,
width: 32,
height: 32
});
var pupilL = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x222222,
width: 12,
height: 12
});
var pupilR = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x222222,
width: 12,
height: 12
});
var smile = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xffe066,
width: 28,
height: 8
});
// Position eyes and smile
eyeL.x = -32;
eyeL.y = 38;
eyeR.x = 32;
eyeR.y = 38;
pupilL.x = -32;
pupilL.y = 38;
pupilR.x = 32;
pupilR.y = 38;
smile.x = 0;
smile.y = 68;
smile.scaleX = 1.2;
smile.scaleY = 0.7;
// Animate pupils for fun
self.update = function () {
self.x -= pipeSpeed;
var t = LK.ticks * 0.13;
pupilL.x = eyeL.x + Math.sin(t) * 4;
pupilL.y = eyeL.y + Math.cos(t) * 2;
pupilR.x = eyeR.x + Math.cos(t) * 4;
pupilR.y = eyeR.y + Math.sin(t) * 2;
smile.scaleX = 1.2 + Math.sin(t) * 0.15;
smile.scaleY = 0.7 + Math.cos(t) * 0.07;
};
self.setHeight = function (h) {
pipeSprite.height = h;
};
// For collision, use the pipeSprite
self.getSprite = function () {
return pipeSprite;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
LK.playMusic('happy_uke', {
loop: true,
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
// Sound: Hit
// Sound: Score
// Sound: Flap
// Particle asset: white ellipse
// Ground asset: brown box
// Obstacle (pipe) asset: green box
// Bird (player) asset: yellow ellipse
// Game constants
var BIRD_START_X = 600;
var BIRD_START_Y = 1100;
var PIPE_GAP = 900; // HUGE gap for super easy mode
var PIPE_MIN = 350;
var PIPE_MAX = 1700;
var PIPE_INTERVAL = 1700; // much more distance between pipes
var pipeSpeed = 3.2; // very slow pipe speed
var GROUND_Y = 2732 - 120;
var PARTICLE_POOL_SIZE = 32;
// Game state
var bird;
var pipes = [];
var ground;
var score = 0;
var highScore = storage.highScore || 0;
var gameStarted = false;
var gameOver = false;
var lastPipeX = 0;
var dragNode = null;
var particles = [];
var particlePool = [];
var scoreTxt, highScoreTxt, tapTxt;
// GUI: Score display
scoreTxt = new Text2('0', {
size: 140,
fill: 0xFFF700,
font: "Impact"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// GUI: High score display
highScoreTxt = new Text2('BEST: ' + highScore, {
size: 60,
fill: 0xFFFFFF,
font: "Impact"
});
highScoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(highScoreTxt);
// GUI: Tap to start
tapTxt = new Text2('TAP TO FLAP', {
size: 100,
fill: 0xFFFFFF,
font: "Impact"
});
tapTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tapTxt);
// Spawn ground
ground = new Ground();
ground.x = 0;
ground.y = GROUND_Y;
game.addChild(ground);
// Spawn bird
bird = new Bird();
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
game.addChild(bird);
// Particle pool
for (var i = 0; i < PARTICLE_POOL_SIZE; i++) {
var p = new Particle();
particlePool.push(p);
}
// Reset game state
function resetGame() {
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
bird.vy = 0;
bird.rotation = 0;
bird.alive = true;
// Reset score
score = 0;
scoreTxt.setText(score);
// Remove tap text if present
tapTxt.visible = true;
// Reset state
gameStarted = false;
gameOver = false;
lastPipeX = 1700;
// Remove all particles
for (var i = 0; i < particles.length; i++) {
particles[i].destroy();
}
particles = [];
}
// Spawn a pair of pipes (top and bottom)
function spawnPipePair(x) {
var gapY = PIPE_MIN + Math.floor(Math.random() * (PIPE_MAX - PIPE_MIN));
// Top pipe
var topPipe = new Pipe();
topPipe.isTop = true;
topPipe.x = x;
topPipe.y = gapY - PIPE_GAP - 800; // pipe height is 800
topPipe.setHeight(800);
game.addChild(topPipe);
pipes.push(topPipe);
// Bottom pipe
var bottomPipe = new Pipe();
bottomPipe.isTop = false;
bottomPipe.x = x;
bottomPipe.y = gapY;
bottomPipe.setHeight(800);
game.addChild(bottomPipe);
pipes.push(bottomPipe);
}
// Particle burst at (x, y)
function spawnParticles(x, y, count) {
for (var i = 0; i < count; i++) {
var p = particlePool.length > 0 ? particlePool.pop() : new Particle();
var angle = Math.random() * Math.PI * 2;
var speed = 7 + Math.random() * 6;
p.init(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed, 20 + Math.floor(Math.random() * 10));
game.addChild(p);
particles.push(p);
}
}
// Rainbow trail particles
function spawnRainbowParticles(x, y, count) {
var rainbow = [0xff0000, 0xff7f00, 0xffff00, 0x00ff00, 0x0000ff, 0x4b0082, 0x9400d3];
for (var i = 0; i < count; i++) {
var p = particlePool.length > 0 ? particlePool.pop() : new Particle();
var angle = Math.PI + (Math.random() - 0.5) * 0.7;
var speed = 2 + Math.random() * 2;
var color = rainbow[(LK.ticks + i) % rainbow.length];
p.init(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed, 18 + Math.floor(Math.random() * 6));
p.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: color
});
game.addChild(p);
particles.push(p);
}
}
// Confetti explosion
function spawnConfetti(x, y, count) {
var confettiColors = [0xffe066, 0xff5e5b, 0x3ecf4c, 0x5bc0eb, 0xfde74c, 0x9bc53d, 0xe55934, 0xfa7921];
for (var i = 0; i < count; i++) {
var p = particlePool.length > 0 ? particlePool.pop() : new Particle();
var angle = Math.random() * Math.PI * 2;
var speed = 7 + Math.random() * 7;
var color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
p.init(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed, 24 + Math.floor(Math.random() * 10));
p.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
color: color
});
game.addChild(p);
particles.push(p);
}
}
// Start game on first tap
function startGame() {
if (gameStarted) return;
tapTxt.visible = false;
gameStarted = true;
gameOver = false;
score = 0;
scoreTxt.setText(score);
// Remove all pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Spawn initial pipes
lastPipeX = 1700;
for (var i = 0; i < 4; i++) {
spawnPipePair(lastPipeX + i * PIPE_INTERVAL);
}
}
// Handle tap/flap
function handleFlap() {
if (gameOver) return;
if (!gameStarted) {
startGame();
}
bird.flap();
// Do a flip!
tween(bird, {
rotation: bird.rotation + Math.PI * 2
}, {
duration: 350,
easing: tween.cubicInOut
});
}
// Touch/click events
game.down = function (x, y, obj) {
if (gameOver) return;
handleFlap();
};
// Main update loop
game.update = function () {
// Bird update
bird.update();
// Particle update
for (var i = particles.length - 1; i >= 0; i--) {
var p = particles[i];
p.update();
if (p.life <= 0) {
p.destroy();
particlePool.push(p);
particles.splice(i, 1);
}
}
// Rainbow/sparkle trail: spawn every 2 frames while flying
if (gameStarted && LK.ticks % 2 === 0) {
spawnRainbowParticles(bird.x - 50, bird.y, 1);
}
// If not started, bird hovers
if (!gameStarted) {
bird.y += Math.sin(LK.ticks / 20) * 1.2;
return;
}
// Pipes update
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove pipes off screen
if (pipe.x < -200) {
pipe.destroy();
pipes.splice(i, 1);
continue;
}
// Score: only for bottom pipes
if (!pipe.passed && !pipe.isTop && pipe.x + 90 < bird.x) {
pipe.passed = true;
score++;
scoreTxt.setText(score);
LK.getSound('score').play();
// Confetti explosion every 10 points
if (score > 0 && score % 10 === 0) {
spawnConfetti(bird.x, bird.y - 60, 32);
}
// Update high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('BEST: ' + highScore);
}
}
}
// Spawn new pipes
if (pipes.length > 0) {
var lastPipe = pipes[pipes.length - 1];
if (lastPipe.x < 2048 - PIPE_INTERVAL) {
lastPipeX += PIPE_INTERVAL;
spawnPipePair(lastPipeX);
}
}
// Collision: ground
if (bird.y + bird.radius > GROUND_Y) {
if (!gameOver) {
bird.y = GROUND_Y - bird.radius;
bird.die();
endGame();
}
}
// Collision: pipes
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
var sprite = pipe.getSprite();
// Simple AABB collision
var bx = bird.x,
by = bird.y,
br = bird.radius * 0.82;
var px = pipe.x - sprite.width * 0.5,
py = pipe.y,
pw = sprite.width,
ph = sprite.height;
if (bx + br > px && bx - br < px + pw && by + br > py && by - br < py + ph) {
// Silly bounce: reverse vertical velocity, play boing sound, and do a quick scale animation
if (bird.vy > 0) {
bird.vy = -Math.abs(bird.vy) * 0.7 - 8 - Math.random() * 6;
} else {
bird.vy = Math.abs(bird.vy) * 0.7 + 8 + Math.random() * 6;
}
LK.getSound('hit').play();
tween(bird, {
scaleX: 1.3,
scaleY: 0.7
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(bird, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
// Rainbow trail burst
spawnRainbowParticles(bird.x, bird.y, 10);
// No game over!
}
}
};
// End game
function endGame() {
gameOver = true;
tapTxt.setText('GAME OVER\nTAP TO RESTART');
tapTxt.visible = true;
LK.showGameOver();
// Reset after popup
LK.setTimeout(function () {
resetGame();
}, 800);
}
// On game over popup close, reset game
LK.on('gameover', function () {
resetGame();
});
// On you win (not used in endless, but for completeness)
LK.on('youwin', function () {
resetGame();
});
// On resize, keep tap text centered (handled by LK.gui, but for safety)
LK.on('resize', function () {
tapTxt.anchor.set(0.5, 0.5);
});
// Initial reset
resetGame();