User prompt
make the top pipes touch the ceiling
User prompt
make them a little higher
User prompt
make the pipes spawn only in the moddle
User prompt
make the space between the pipes a lot wider
User prompt
make the only top pipes upside down
User prompt
make the pipes THICCER
User prompt
make the speed of the pipes slower
User prompt
Increase the vertical gap between the top and bottom pipes for easier gameplay
User prompt
make the space between the pipes wider
User prompt
reduce the strength of the gravity
User prompt
fix the hitboxes
User prompt
make the bird jump lower
User prompt
reduce the gravity
Code edit (1 edits merged)
Please save this source code
User prompt
Flappy Bird: Tap to Fly
Initial prompt
Flappy Bird
/**** * 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();