/**** * 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();