/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach bird asset
var birdAsset = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial properties
self.vy = 0; // vertical velocity
self.gravity = 0.5; // gravity per frame (further reduced for even slower falling)
self.flapStrength = -18; // negative for upward movement (lower jump)
// Bird update: apply gravity, move, clamp position
self.update = function () {
if (!gameStarted) return; // Stop bird movement before game starts
self.vy += self.gravity;
self.y += self.vy;
// Clamp to top of screen (use ellipse top)
if (self.y < self.height / 2 * 0.82) {
self.y = self.height / 2 * 0.82;
self.vy = 0;
}
};
// Flap: set upward velocity
self.flap = function () {
self.vy = self.flapStrength;
};
return self;
});
// Cloud class for background decoration
var Cloud = Container.expand(function () {
var self = Container.call(this);
// Create cloud using white ellipse
var cloudAsset = self.attachAsset('cloud', {
anchorX: 0.5,
anchorY: 0.5
});
// Set cloud movement speed (slower than pipes for background effect)
self.speed = 2;
// Cloud update: move left
self.update = function () {
if (!gameStarted) return; // Stop cloud movement before game starts
self.x -= self.speed;
};
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe config
self.pipeWidth = 400;
self.gapHeight = 1300; // Increased gap for easier gameplay
self.speed = 7;
// Create top pipe (green box)
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1,
width: self.pipeWidth,
height: 0 // will be set in setGap
});
self.topPipe.rotation = Math.PI;
// Create bottom pipe (green box)
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0,
width: self.pipeWidth,
height: 0 // will be set in setGap
});
// Set initial positions (will be set by spawn)
self.topPipe.x = 0;
self.bottomPipe.x = 0;
// Used to track if score was already given for this pipe
self.passed = false;
// Set pipes' vertical positions and heights based on gapY
self.setGap = function (gapY) {
// gapY is the vertical center of the gap
var gapTop = gapY - self.gapHeight / 2;
var gapBottom = gapY + self.gapHeight / 2;
// Top pipe: stretch from ceiling (y=0) to top of gap, but make it visually longer
self.topPipe.y = 0;
self.topPipe.height = gapTop + 300; // Make the top pipe longer by 300px
// Bottom pipe: stretch from bottom of gap to ground
self.bottomPipe.y = gapBottom;
self.bottomPipe.height = 2732 - gapBottom;
};
// Move pipes left
self.update = function () {
if (!gameStarted) return; // Stop pipe movement before game starts
self.x -= self.speed;
};
// Helper: get bounding rect for collision
self.getTopRect = function () {
// Hitbox is smaller than visible top pipe for easier gameplay
var hitboxWidth = self.pipeWidth * 0.7; // 70% width
var hitboxHeight = self.topPipe.height * 0.6; // 60% height
return new Rectangle(self.x - hitboxWidth / 2, 0, hitboxWidth, hitboxHeight);
};
self.getBottomRect = function () {
// Hitbox matches visible bottom pipe
var hitboxWidth = self.pipeWidth;
return new Rectangle(self.x - hitboxWidth / 2, self.bottomPipe.y, hitboxWidth, self.bottomPipe.height);
};
return self;
});
// Sun class for background decoration
var Sun = Container.expand(function () {
var self = Container.call(this);
// Create sun using golden yellow ellipse
var sunAsset = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Pipe collision detection is already working in the game update loop
// The bird dies when touching top or bottom pipes through ellipse-rectangle collision
// This functionality is complete and functional
// --- Game Variables ---
var bird;
var pipes = [];
var ground;
var score = 0;
var scoreTxt;
var gameStarted = false;
var gameOver = false;
var pipeTimer = null;
var lastPipeX = 0;
var nextPipeDist = 0;
var bestScore = storage.bestScore || 0;
var clouds = [];
var cloudTimer = null;
var sun;
// --- Difficulty Settings ---
// Use a single default difficulty (Normal) with fixed values
var currentDifficulty = {
name: "Normal",
gapHeight: 1200,
pipeSpeed: 7,
pipeInterval: 1100
};
// --- GUI Score Display ---
scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Start Instructions ---
var instructionTxt = new Text2('TAP TO START', {
size: 120,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionTxt);
// --- Pixelated Flappy Bird Title ---
var titleTxt = new Text2('FLAPPY BIRD', {
size: 100,
fill: 0xFFFF00
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.y = -200;
titleTxt.visible = false;
LK.gui.center.addChild(titleTxt);
// --- Pixelated Text Above Flap Instructions ---
var pixelTxt = new Text2('◆ ◇ ◆ ◇ ◆', {
size: 60,
fill: 0xFFD700
});
pixelTxt.anchor.set(0.5, 0);
pixelTxt.y = 140;
pixelTxt.visible = false;
LK.gui.center.addChild(pixelTxt);
// --- Flap Instructions ---
var flapTxt = new Text2('TAP TO FLAP', {
size: 80,
fill: 0xFFFFFF
});
flapTxt.anchor.set(0.5, 0);
flapTxt.y = 200;
flapTxt.visible = false;
LK.gui.center.addChild(flapTxt);
// --- Best Score Display (top right) ---
var bestScoreTxt = new Text2('', {
size: 60,
fill: 0xFFF7B2
});
bestScoreTxt.anchor.set(1, 0);
bestScoreTxt.x = -60;
bestScoreTxt.y = 30;
LK.gui.topRight.addChild(bestScoreTxt);
// --- Sun ---
sun = new Sun();
sun.x = 1700; // Position in upper right area
sun.y = 400; // High in the sky
game.addChild(sun);
// --- Ground ---
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732,
width: 2048,
height: 120
});
game.addChild(ground);
// --- Bird ---
bird = new Bird();
bird.x = 500;
bird.y = 2732 / 2;
game.addChild(bird);
// --- Reset Game State ---
function resetGame() {
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.x = 500;
bird.y = 2732 / 2;
bird.vy = 0;
// Reset score
score = 0;
scoreTxt.setText(score);
// Reset state
gameStarted = false;
gameOver = false;
// Stop background music only, not death music
// LK.stopMusic() removed to allow death music to continue
// Show best score from storage
bestScore = storage.bestScore || 0;
bestScoreTxt.setText('Best: ' + bestScore);
// Remove pipe timer if any
if (pipeTimer) {
LK.clearInterval(pipeTimer);
pipeTimer = null;
}
// Remove clouds
for (var j = 0; j < clouds.length; j++) {
clouds[j].destroy();
}
clouds = [];
// Remove cloud timer if any
if (cloudTimer) {
LK.clearInterval(cloudTimer);
cloudTimer = null;
}
// Start cloud spawning
spawnCloud();
if (cloudTimer) LK.clearInterval(cloudTimer);
cloudTimer = LK.setInterval(spawnCloud, 3000); // Spawn cloud every 3 seconds
// Show instruction text and title
instructionTxt.visible = true;
titleTxt.visible = true;
// Hide flap instruction and pixelated text
flapTxt.visible = false;
pixelTxt.visible = false;
// Show difficulty UI again
if (typeof leftBtn === "undefined") {
leftBtn = {
visible: true
};
} else {
leftBtn.visible = true;
}
if (typeof rightBtn === "undefined") {
rightBtn = {
visible: true
};
} else {
rightBtn.visible = true;
}
}
// --- Start Game ---
function startGame() {
if (gameStarted) return;
gameStarted = true;
gameOver = false;
score = 0;
scoreTxt.setText(score);
// Hide instruction text and title
instructionTxt.visible = false;
titleTxt.visible = false;
// Show flap instruction and pixelated text
flapTxt.visible = true;
pixelTxt.visible = true;
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.x = 500;
bird.y = 2732 / 2;
bird.vy = 0;
// Start background music
LK.playMusic('music');
// Start pipe spawning
spawnPipe();
if (pipeTimer) LK.clearInterval(pipeTimer);
pipeTimer = LK.setInterval(spawnPipe, currentDifficulty.pipeInterval);
// Lock difficulty UI after game starts
leftBtn.visible = false;
rightBtn.visible = false;
}
// --- End Game ---
function endGame() {
if (gameOver) return;
gameOver = true;
gameStarted = false;
// Stop pipe spawning
if (pipeTimer) {
LK.clearInterval(pipeTimer);
pipeTimer = null;
}
// Stop background music
LK.stopMusic();
// Play death sound
LK.getSound('death').play();
// Flash screen
LK.effects.flashScreen(0xff0000, 600);
// Update best score only if current score is higher
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
}
bestScoreTxt.setText('Best: ' + bestScore);
// Show game over popup (handled by LK)
LK.showGameOver();
}
// --- Pipe Spawning ---
function spawnPipe() {
// Gap vertical position: slightly above the vertical center of the screen
var gapY = 2732 / 2 - 250;
var pipePair = new PipePair();
pipePair.x = 2048 + pipePair.pipeWidth; // Spawn just off right edge
// Set difficulty-based gap and speed
pipePair.gapHeight = currentDifficulty.gapHeight;
pipePair.speed = currentDifficulty.pipeSpeed;
pipePair.setGap(gapY);
game.addChild(pipePair);
pipes.push(pipePair);
}
// --- Cloud Spawning ---
function spawnCloud() {
var cloud = new Cloud();
cloud.x = 2048 + 200; // Spawn just off right edge
cloud.y = Math.random() * 800 + 200; // Random height in upper portion of screen
cloud.alpha = 0.7; // Make clouds semi-transparent
// Random scale for variety
var scale = 0.8 + Math.random() * 0.6; // Scale between 0.8 and 1.4
cloud.scaleX = scale;
cloud.scaleY = scale;
game.addChild(cloud);
clouds.push(cloud);
}
// --- Collision Detection ---
function rectsIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
// --- Game Update Loop ---
game.update = function () {
// Update clouds only when game has started
if (gameStarted) {
for (var c = clouds.length - 1; c >= 0; c--) {
var cloud = clouds[c];
cloud.update();
// Remove clouds that are off screen
if (cloud.x < -400) {
cloud.destroy();
clouds.splice(c, 1);
}
}
}
if (!gameStarted) return;
// Bird update
bird.update();
// Ground collision (use ellipse bottom)
// Only trigger endGame when bird hits the ground this frame (not if already on ground)
if (bird.lastY === undefined) bird.lastY = bird.y;
if (bird.lastY + bird.height / 2 * 0.82 < 2732 - 120 && bird.y + bird.height / 2 * 0.82 >= 2732 - 120) {
bird.y = 2732 - 120 - bird.height / 2 * 0.82;
endGame();
return;
}
// Ceiling collision (use ellipse top)
// Only trigger endGame when bird hits the ceiling this frame (not if already at ceiling)
if (bird.lastY - bird.height / 2 * 0.82 > 0 && bird.y - bird.height / 2 * 0.82 <= 0) {
bird.y = bird.height / 2 * 0.82;
endGame();
return;
}
bird.lastY = bird.y;
// Pipe update and collision
var _loop = function _loop() {
pipe = pipes[i];
pipe.update();
// Remove pipes off screen
if (pipe.x < -pipe.pipeWidth) {
pipe.destroy();
pipes.splice(i, 1);
return 0; // continue
}
// Collision with pipes
// Bird ellipse center and radii
birdCx = bird.x;
birdCy = bird.y;
birdRx = bird.width / 2 * 0.82; // slightly smaller for visual fit
birdRy = bird.height / 2 * 0.82; // Ellipse-rectangle collision helper
function ellipseRectCollides(cx, cy, rx, ry, rx1, ry1, rw, rh) {
// Find closest point on rectangle to ellipse center
var closestX = Math.max(rx1, Math.min(cx, rx1 + rw));
var closestY = Math.max(ry1, Math.min(cy, ry1 + rh));
// Compute normalized distance
var dx = (closestX - cx) / rx;
var dy = (closestY - cy) / ry;
return dx * dx + dy * dy <= 1;
}
// Top pipe
topRect = pipe.getTopRect();
// Fix: Use correct height for top pipe collision (from y=0 to topPipe.height)
if (ellipseRectCollides(birdCx, birdCy, birdRx, birdRy, topRect.x, 0, topRect.width, pipe.topPipe.height)) {
endGame();
return {
v: void 0
};
}
// Bottom pipe
bottomRect = pipe.getBottomRect();
if (ellipseRectCollides(birdCx, birdCy, birdRx, birdRy, bottomRect.x, bottomRect.y, bottomRect.width, bottomRect.height)) {
endGame();
return {
v: void 0
};
}
// Score: if bird passed pipe center and not yet scored
if (!pipe.passed && pipe.x + pipe.pipeWidth / 2 < bird.x - bird.width / 2) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score);
}
},
pipe,
birdCx,
birdCy,
birdRx,
birdRy,
topRect,
bottomRect,
_ret;
for (var i = pipes.length - 1; i >= 0; i--) {
_ret = _loop();
if (_ret === 0) continue;
if (_ret) return _ret.v;
}
};
// --- Tap/Touch Controls ---
game.down = function (x, y, obj) {
if (gameOver) {
resetGame();
return;
}
if (!gameStarted) {
startGame();
}
bird.flap();
// Hide flap instruction, pixelated text, and title after first flap
if (flapTxt.visible) {
flapTxt.visible = false;
pixelTxt.visible = false;
titleTxt.visible = false;
}
};
// --- Reset on Game Over ---
LK.on('gameover', function () {
resetGame();
});
// --- Initial State ---
resetGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach bird asset
var birdAsset = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial properties
self.vy = 0; // vertical velocity
self.gravity = 0.5; // gravity per frame (further reduced for even slower falling)
self.flapStrength = -18; // negative for upward movement (lower jump)
// Bird update: apply gravity, move, clamp position
self.update = function () {
if (!gameStarted) return; // Stop bird movement before game starts
self.vy += self.gravity;
self.y += self.vy;
// Clamp to top of screen (use ellipse top)
if (self.y < self.height / 2 * 0.82) {
self.y = self.height / 2 * 0.82;
self.vy = 0;
}
};
// Flap: set upward velocity
self.flap = function () {
self.vy = self.flapStrength;
};
return self;
});
// Cloud class for background decoration
var Cloud = Container.expand(function () {
var self = Container.call(this);
// Create cloud using white ellipse
var cloudAsset = self.attachAsset('cloud', {
anchorX: 0.5,
anchorY: 0.5
});
// Set cloud movement speed (slower than pipes for background effect)
self.speed = 2;
// Cloud update: move left
self.update = function () {
if (!gameStarted) return; // Stop cloud movement before game starts
self.x -= self.speed;
};
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe config
self.pipeWidth = 400;
self.gapHeight = 1300; // Increased gap for easier gameplay
self.speed = 7;
// Create top pipe (green box)
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1,
width: self.pipeWidth,
height: 0 // will be set in setGap
});
self.topPipe.rotation = Math.PI;
// Create bottom pipe (green box)
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0,
width: self.pipeWidth,
height: 0 // will be set in setGap
});
// Set initial positions (will be set by spawn)
self.topPipe.x = 0;
self.bottomPipe.x = 0;
// Used to track if score was already given for this pipe
self.passed = false;
// Set pipes' vertical positions and heights based on gapY
self.setGap = function (gapY) {
// gapY is the vertical center of the gap
var gapTop = gapY - self.gapHeight / 2;
var gapBottom = gapY + self.gapHeight / 2;
// Top pipe: stretch from ceiling (y=0) to top of gap, but make it visually longer
self.topPipe.y = 0;
self.topPipe.height = gapTop + 300; // Make the top pipe longer by 300px
// Bottom pipe: stretch from bottom of gap to ground
self.bottomPipe.y = gapBottom;
self.bottomPipe.height = 2732 - gapBottom;
};
// Move pipes left
self.update = function () {
if (!gameStarted) return; // Stop pipe movement before game starts
self.x -= self.speed;
};
// Helper: get bounding rect for collision
self.getTopRect = function () {
// Hitbox is smaller than visible top pipe for easier gameplay
var hitboxWidth = self.pipeWidth * 0.7; // 70% width
var hitboxHeight = self.topPipe.height * 0.6; // 60% height
return new Rectangle(self.x - hitboxWidth / 2, 0, hitboxWidth, hitboxHeight);
};
self.getBottomRect = function () {
// Hitbox matches visible bottom pipe
var hitboxWidth = self.pipeWidth;
return new Rectangle(self.x - hitboxWidth / 2, self.bottomPipe.y, hitboxWidth, self.bottomPipe.height);
};
return self;
});
// Sun class for background decoration
var Sun = Container.expand(function () {
var self = Container.call(this);
// Create sun using golden yellow ellipse
var sunAsset = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Pipe collision detection is already working in the game update loop
// The bird dies when touching top or bottom pipes through ellipse-rectangle collision
// This functionality is complete and functional
// --- Game Variables ---
var bird;
var pipes = [];
var ground;
var score = 0;
var scoreTxt;
var gameStarted = false;
var gameOver = false;
var pipeTimer = null;
var lastPipeX = 0;
var nextPipeDist = 0;
var bestScore = storage.bestScore || 0;
var clouds = [];
var cloudTimer = null;
var sun;
// --- Difficulty Settings ---
// Use a single default difficulty (Normal) with fixed values
var currentDifficulty = {
name: "Normal",
gapHeight: 1200,
pipeSpeed: 7,
pipeInterval: 1100
};
// --- GUI Score Display ---
scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Start Instructions ---
var instructionTxt = new Text2('TAP TO START', {
size: 120,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionTxt);
// --- Pixelated Flappy Bird Title ---
var titleTxt = new Text2('FLAPPY BIRD', {
size: 100,
fill: 0xFFFF00
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.y = -200;
titleTxt.visible = false;
LK.gui.center.addChild(titleTxt);
// --- Pixelated Text Above Flap Instructions ---
var pixelTxt = new Text2('◆ ◇ ◆ ◇ ◆', {
size: 60,
fill: 0xFFD700
});
pixelTxt.anchor.set(0.5, 0);
pixelTxt.y = 140;
pixelTxt.visible = false;
LK.gui.center.addChild(pixelTxt);
// --- Flap Instructions ---
var flapTxt = new Text2('TAP TO FLAP', {
size: 80,
fill: 0xFFFFFF
});
flapTxt.anchor.set(0.5, 0);
flapTxt.y = 200;
flapTxt.visible = false;
LK.gui.center.addChild(flapTxt);
// --- Best Score Display (top right) ---
var bestScoreTxt = new Text2('', {
size: 60,
fill: 0xFFF7B2
});
bestScoreTxt.anchor.set(1, 0);
bestScoreTxt.x = -60;
bestScoreTxt.y = 30;
LK.gui.topRight.addChild(bestScoreTxt);
// --- Sun ---
sun = new Sun();
sun.x = 1700; // Position in upper right area
sun.y = 400; // High in the sky
game.addChild(sun);
// --- Ground ---
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732,
width: 2048,
height: 120
});
game.addChild(ground);
// --- Bird ---
bird = new Bird();
bird.x = 500;
bird.y = 2732 / 2;
game.addChild(bird);
// --- Reset Game State ---
function resetGame() {
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.x = 500;
bird.y = 2732 / 2;
bird.vy = 0;
// Reset score
score = 0;
scoreTxt.setText(score);
// Reset state
gameStarted = false;
gameOver = false;
// Stop background music only, not death music
// LK.stopMusic() removed to allow death music to continue
// Show best score from storage
bestScore = storage.bestScore || 0;
bestScoreTxt.setText('Best: ' + bestScore);
// Remove pipe timer if any
if (pipeTimer) {
LK.clearInterval(pipeTimer);
pipeTimer = null;
}
// Remove clouds
for (var j = 0; j < clouds.length; j++) {
clouds[j].destroy();
}
clouds = [];
// Remove cloud timer if any
if (cloudTimer) {
LK.clearInterval(cloudTimer);
cloudTimer = null;
}
// Start cloud spawning
spawnCloud();
if (cloudTimer) LK.clearInterval(cloudTimer);
cloudTimer = LK.setInterval(spawnCloud, 3000); // Spawn cloud every 3 seconds
// Show instruction text and title
instructionTxt.visible = true;
titleTxt.visible = true;
// Hide flap instruction and pixelated text
flapTxt.visible = false;
pixelTxt.visible = false;
// Show difficulty UI again
if (typeof leftBtn === "undefined") {
leftBtn = {
visible: true
};
} else {
leftBtn.visible = true;
}
if (typeof rightBtn === "undefined") {
rightBtn = {
visible: true
};
} else {
rightBtn.visible = true;
}
}
// --- Start Game ---
function startGame() {
if (gameStarted) return;
gameStarted = true;
gameOver = false;
score = 0;
scoreTxt.setText(score);
// Hide instruction text and title
instructionTxt.visible = false;
titleTxt.visible = false;
// Show flap instruction and pixelated text
flapTxt.visible = true;
pixelTxt.visible = true;
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.x = 500;
bird.y = 2732 / 2;
bird.vy = 0;
// Start background music
LK.playMusic('music');
// Start pipe spawning
spawnPipe();
if (pipeTimer) LK.clearInterval(pipeTimer);
pipeTimer = LK.setInterval(spawnPipe, currentDifficulty.pipeInterval);
// Lock difficulty UI after game starts
leftBtn.visible = false;
rightBtn.visible = false;
}
// --- End Game ---
function endGame() {
if (gameOver) return;
gameOver = true;
gameStarted = false;
// Stop pipe spawning
if (pipeTimer) {
LK.clearInterval(pipeTimer);
pipeTimer = null;
}
// Stop background music
LK.stopMusic();
// Play death sound
LK.getSound('death').play();
// Flash screen
LK.effects.flashScreen(0xff0000, 600);
// Update best score only if current score is higher
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
}
bestScoreTxt.setText('Best: ' + bestScore);
// Show game over popup (handled by LK)
LK.showGameOver();
}
// --- Pipe Spawning ---
function spawnPipe() {
// Gap vertical position: slightly above the vertical center of the screen
var gapY = 2732 / 2 - 250;
var pipePair = new PipePair();
pipePair.x = 2048 + pipePair.pipeWidth; // Spawn just off right edge
// Set difficulty-based gap and speed
pipePair.gapHeight = currentDifficulty.gapHeight;
pipePair.speed = currentDifficulty.pipeSpeed;
pipePair.setGap(gapY);
game.addChild(pipePair);
pipes.push(pipePair);
}
// --- Cloud Spawning ---
function spawnCloud() {
var cloud = new Cloud();
cloud.x = 2048 + 200; // Spawn just off right edge
cloud.y = Math.random() * 800 + 200; // Random height in upper portion of screen
cloud.alpha = 0.7; // Make clouds semi-transparent
// Random scale for variety
var scale = 0.8 + Math.random() * 0.6; // Scale between 0.8 and 1.4
cloud.scaleX = scale;
cloud.scaleY = scale;
game.addChild(cloud);
clouds.push(cloud);
}
// --- Collision Detection ---
function rectsIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
// --- Game Update Loop ---
game.update = function () {
// Update clouds only when game has started
if (gameStarted) {
for (var c = clouds.length - 1; c >= 0; c--) {
var cloud = clouds[c];
cloud.update();
// Remove clouds that are off screen
if (cloud.x < -400) {
cloud.destroy();
clouds.splice(c, 1);
}
}
}
if (!gameStarted) return;
// Bird update
bird.update();
// Ground collision (use ellipse bottom)
// Only trigger endGame when bird hits the ground this frame (not if already on ground)
if (bird.lastY === undefined) bird.lastY = bird.y;
if (bird.lastY + bird.height / 2 * 0.82 < 2732 - 120 && bird.y + bird.height / 2 * 0.82 >= 2732 - 120) {
bird.y = 2732 - 120 - bird.height / 2 * 0.82;
endGame();
return;
}
// Ceiling collision (use ellipse top)
// Only trigger endGame when bird hits the ceiling this frame (not if already at ceiling)
if (bird.lastY - bird.height / 2 * 0.82 > 0 && bird.y - bird.height / 2 * 0.82 <= 0) {
bird.y = bird.height / 2 * 0.82;
endGame();
return;
}
bird.lastY = bird.y;
// Pipe update and collision
var _loop = function _loop() {
pipe = pipes[i];
pipe.update();
// Remove pipes off screen
if (pipe.x < -pipe.pipeWidth) {
pipe.destroy();
pipes.splice(i, 1);
return 0; // continue
}
// Collision with pipes
// Bird ellipse center and radii
birdCx = bird.x;
birdCy = bird.y;
birdRx = bird.width / 2 * 0.82; // slightly smaller for visual fit
birdRy = bird.height / 2 * 0.82; // Ellipse-rectangle collision helper
function ellipseRectCollides(cx, cy, rx, ry, rx1, ry1, rw, rh) {
// Find closest point on rectangle to ellipse center
var closestX = Math.max(rx1, Math.min(cx, rx1 + rw));
var closestY = Math.max(ry1, Math.min(cy, ry1 + rh));
// Compute normalized distance
var dx = (closestX - cx) / rx;
var dy = (closestY - cy) / ry;
return dx * dx + dy * dy <= 1;
}
// Top pipe
topRect = pipe.getTopRect();
// Fix: Use correct height for top pipe collision (from y=0 to topPipe.height)
if (ellipseRectCollides(birdCx, birdCy, birdRx, birdRy, topRect.x, 0, topRect.width, pipe.topPipe.height)) {
endGame();
return {
v: void 0
};
}
// Bottom pipe
bottomRect = pipe.getBottomRect();
if (ellipseRectCollides(birdCx, birdCy, birdRx, birdRy, bottomRect.x, bottomRect.y, bottomRect.width, bottomRect.height)) {
endGame();
return {
v: void 0
};
}
// Score: if bird passed pipe center and not yet scored
if (!pipe.passed && pipe.x + pipe.pipeWidth / 2 < bird.x - bird.width / 2) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score);
}
},
pipe,
birdCx,
birdCy,
birdRx,
birdRy,
topRect,
bottomRect,
_ret;
for (var i = pipes.length - 1; i >= 0; i--) {
_ret = _loop();
if (_ret === 0) continue;
if (_ret) return _ret.v;
}
};
// --- Tap/Touch Controls ---
game.down = function (x, y, obj) {
if (gameOver) {
resetGame();
return;
}
if (!gameStarted) {
startGame();
}
bird.flap();
// Hide flap instruction, pixelated text, and title after first flap
if (flapTxt.visible) {
flapTxt.visible = false;
pixelTxt.visible = false;
titleTxt.visible = false;
}
};
// --- Reset on Game Over ---
LK.on('gameover', function () {
resetGame();
});
// --- Initial State ---
resetGame();