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