/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bird = Container.expand(function () { var self = Container.call(this); var birdGraphics = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); self.velocityY = 0; self.gravity = 0.6; self.flapPower = -16; self.maxFallSpeed = 12; self.flap = function () { self.velocityY = self.flapPower; LK.getSound('flap').play(); // Rotate bird upward when flapping tween(birdGraphics, { rotation: -0.3 }, { duration: 150 }); }; self.update = function () { // Apply gravity self.velocityY += self.gravity; if (self.velocityY > self.maxFallSpeed) { self.velocityY = self.maxFallSpeed; } // Move bird self.y += self.velocityY; // Rotate bird based on velocity if (self.velocityY > 0) { var targetRotation = Math.min(self.velocityY * 0.08, 1.5); tween(birdGraphics, { rotation: targetRotation }, { duration: 200 }); } }; return self; }); var Pipe = Container.expand(function () { var self = Container.call(this); self.topPipe = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 1 }); self.bottomPipe = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 0 }); self.gapSize = 400; self.speed = -4; self.scored = false; self.setup = function (gapY) { // Ensure gap is properly centered and pipes are aligned var halfGap = self.gapSize / 2; // Position pipes to create a clear, passable gap // Top pipe extends from screen top to gap start self.topPipe.y = gapY - halfGap; // Bottom pipe extends from gap end to screen bottom self.bottomPipe.y = gapY + halfGap; // Ensure pipes are perfectly aligned horizontally self.topPipe.x = 0; self.bottomPipe.x = 0; // Ensure the gap is visually clear and bird-sized self.topPipe.height = Math.max(0, gapY - halfGap); self.bottomPipe.height = Math.max(0, 2732 - (gapY + halfGap)); }; self.update = function () { self.x += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var bird; var pipes = []; var ground; var gameStarted = false; var gameOver = false; var pipeSpawnTimer = 0; var pipeSpawnInterval = 240; // frames between pipe spawns // Create score display var scoreTxt = new Text2('0', { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Create tap to start instruction var instructionTxt = new Text2('TAP TO START', { size: 80, fill: 0xFFFFFF }); instructionTxt.anchor.set(0.5, 0.5); instructionTxt.x = 2048 / 2; instructionTxt.y = 2732 / 2 - 200; game.addChild(instructionTxt); // Initialize bird bird = game.addChild(new Bird()); bird.x = 2048 / 4; bird.y = 2732 / 2; // Create ground ground = game.addChild(LK.getAsset('ground', { anchorX: 0, anchorY: 1 })); ground.x = 0; ground.y = 2732; function startGame() { gameStarted = true; if (instructionTxt.parent) { instructionTxt.parent.removeChild(instructionTxt); } bird.flap(); } function spawnPipe() { var pipe = new Pipe(); // Calculate safe boundaries for gap placement var safeTop = 250; // Increased distance from top for easier passage var safeBottom = ground.y - 250; // Increased distance from ground for easier passage var availableHeight = safeBottom - safeTop; // Create more predictable, bird-friendly gap positioning // Focus gaps in the middle 60% of the safe area for better playability var gapY = safeTop + availableHeight * 0.2 + Math.random() * (availableHeight * 0.6); // Ensure gap is always within safe bounds with proper clearance var minGapY = safeTop + pipe.gapSize / 2; var maxGapY = safeBottom - pipe.gapSize / 2; gapY = Math.max(minGapY, Math.min(maxGapY, gapY)); // Apply additional smoothing to prevent extreme positions var screenCenter = 2732 / 2; gapY = gapY * 0.7 + screenCenter * 0.3; // Bias toward screen center pipe.setup(gapY); pipe.x = 2048 + 60; // Start position for smooth entry pipes.push(pipe); game.addChild(pipe); } function checkCollisions() { // Check ground collision if (bird.y + 30 >= ground.y) { return true; } // Check ceiling collision if (bird.y - 30 <= 0) { return true; } // Check pipe collisions for (var i = 0; i < pipes.length; i++) { var pipe = pipes[i]; var birdLeft = bird.x - 40; var birdRight = bird.x + 40; var birdTop = bird.y - 30; var birdBottom = bird.y + 30; var pipeLeft = pipe.x - 150; var pipeRight = pipe.x + 150; // Check if bird is in pipe's x range if (birdRight > pipeLeft && birdLeft < pipeRight) { // Check collision with top pipe (more precise alignment) if (birdTop <= pipe.topPipe.y) { return true; } // Check collision with bottom pipe (more precise alignment) if (birdBottom >= pipe.bottomPipe.y) { return true; } } } return false; } function updateScore() { for (var i = 0; i < pipes.length; i++) { var pipe = pipes[i]; if (!pipe.scored && bird.x > pipe.x + 150) { pipe.scored = true; LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore()); LK.getSound('score').play(); // Increase difficulty slightly if (LK.getScore() % 5 === 0 && pipeSpawnInterval > 60) { pipeSpawnInterval -= 2; } break; } } } function cleanupPipes() { for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; if (pipe.x < -200) { pipe.destroy(); pipes.splice(i, 1); } } } game.down = function (x, y, obj) { if (gameOver) { return; } if (!gameStarted) { startGame(); } else { bird.flap(); } }; game.update = function () { if (gameOver) { return; } if (gameStarted) { // Check for collisions if (checkCollisions()) { gameOver = true; LK.showGameOver(); return; } // Spawn pipes pipeSpawnTimer++; if (pipeSpawnTimer >= pipeSpawnInterval) { spawnPipe(); pipeSpawnTimer = 0; } // Update score updateScore(); // Cleanup off-screen pipes cleanupPipes(); } else { // Gentle floating animation when waiting to start bird.y += Math.sin(LK.ticks * 0.1) * 0.5; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityY = 0;
self.gravity = 0.6;
self.flapPower = -16;
self.maxFallSpeed = 12;
self.flap = function () {
self.velocityY = self.flapPower;
LK.getSound('flap').play();
// Rotate bird upward when flapping
tween(birdGraphics, {
rotation: -0.3
}, {
duration: 150
});
};
self.update = function () {
// Apply gravity
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
// Move bird
self.y += self.velocityY;
// Rotate bird based on velocity
if (self.velocityY > 0) {
var targetRotation = Math.min(self.velocityY * 0.08, 1.5);
tween(birdGraphics, {
rotation: targetRotation
}, {
duration: 200
});
}
};
return self;
});
var Pipe = Container.expand(function () {
var self = Container.call(this);
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1
});
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0
});
self.gapSize = 400;
self.speed = -4;
self.scored = false;
self.setup = function (gapY) {
// Ensure gap is properly centered and pipes are aligned
var halfGap = self.gapSize / 2;
// Position pipes to create a clear, passable gap
// Top pipe extends from screen top to gap start
self.topPipe.y = gapY - halfGap;
// Bottom pipe extends from gap end to screen bottom
self.bottomPipe.y = gapY + halfGap;
// Ensure pipes are perfectly aligned horizontally
self.topPipe.x = 0;
self.bottomPipe.x = 0;
// Ensure the gap is visually clear and bird-sized
self.topPipe.height = Math.max(0, gapY - halfGap);
self.bottomPipe.height = Math.max(0, 2732 - (gapY + halfGap));
};
self.update = function () {
self.x += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var bird;
var pipes = [];
var ground;
var gameStarted = false;
var gameOver = false;
var pipeSpawnTimer = 0;
var pipeSpawnInterval = 240; // frames between pipe spawns
// Create score display
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create tap to start instruction
var instructionTxt = new Text2('TAP TO START', {
size: 80,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
instructionTxt.x = 2048 / 2;
instructionTxt.y = 2732 / 2 - 200;
game.addChild(instructionTxt);
// Initialize bird
bird = game.addChild(new Bird());
bird.x = 2048 / 4;
bird.y = 2732 / 2;
// Create ground
ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 1
}));
ground.x = 0;
ground.y = 2732;
function startGame() {
gameStarted = true;
if (instructionTxt.parent) {
instructionTxt.parent.removeChild(instructionTxt);
}
bird.flap();
}
function spawnPipe() {
var pipe = new Pipe();
// Calculate safe boundaries for gap placement
var safeTop = 250; // Increased distance from top for easier passage
var safeBottom = ground.y - 250; // Increased distance from ground for easier passage
var availableHeight = safeBottom - safeTop;
// Create more predictable, bird-friendly gap positioning
// Focus gaps in the middle 60% of the safe area for better playability
var gapY = safeTop + availableHeight * 0.2 + Math.random() * (availableHeight * 0.6);
// Ensure gap is always within safe bounds with proper clearance
var minGapY = safeTop + pipe.gapSize / 2;
var maxGapY = safeBottom - pipe.gapSize / 2;
gapY = Math.max(minGapY, Math.min(maxGapY, gapY));
// Apply additional smoothing to prevent extreme positions
var screenCenter = 2732 / 2;
gapY = gapY * 0.7 + screenCenter * 0.3; // Bias toward screen center
pipe.setup(gapY);
pipe.x = 2048 + 60; // Start position for smooth entry
pipes.push(pipe);
game.addChild(pipe);
}
function checkCollisions() {
// Check ground collision
if (bird.y + 30 >= ground.y) {
return true;
}
// Check ceiling collision
if (bird.y - 30 <= 0) {
return true;
}
// Check pipe collisions
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
var birdLeft = bird.x - 40;
var birdRight = bird.x + 40;
var birdTop = bird.y - 30;
var birdBottom = bird.y + 30;
var pipeLeft = pipe.x - 150;
var pipeRight = pipe.x + 150;
// Check if bird is in pipe's x range
if (birdRight > pipeLeft && birdLeft < pipeRight) {
// Check collision with top pipe (more precise alignment)
if (birdTop <= pipe.topPipe.y) {
return true;
}
// Check collision with bottom pipe (more precise alignment)
if (birdBottom >= pipe.bottomPipe.y) {
return true;
}
}
}
return false;
}
function updateScore() {
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
if (!pipe.scored && bird.x > pipe.x + 150) {
pipe.scored = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('score').play();
// Increase difficulty slightly
if (LK.getScore() % 5 === 0 && pipeSpawnInterval > 60) {
pipeSpawnInterval -= 2;
}
break;
}
}
}
function cleanupPipes() {
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
if (pipe.x < -200) {
pipe.destroy();
pipes.splice(i, 1);
}
}
}
game.down = function (x, y, obj) {
if (gameOver) {
return;
}
if (!gameStarted) {
startGame();
} else {
bird.flap();
}
};
game.update = function () {
if (gameOver) {
return;
}
if (gameStarted) {
// Check for collisions
if (checkCollisions()) {
gameOver = true;
LK.showGameOver();
return;
}
// Spawn pipes
pipeSpawnTimer++;
if (pipeSpawnTimer >= pipeSpawnInterval) {
spawnPipe();
pipeSpawnTimer = 0;
}
// Update score
updateScore();
// Cleanup off-screen pipes
cleanupPipes();
} else {
// Gentle floating animation when waiting to start
bird.y += Math.sin(LK.ticks * 0.1) * 0.5;
}
};