/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bird 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 // Bird physics constants self.gravity = 1.2; // px per frame^2 (slower fall) self.jumpStrength = -24; // px per frame (less abrupt jump) // Bird update self.update = function () { self.vy += self.gravity; self.y += self.vy; // Clamp rotation for visual feedback var maxAngle = Math.PI / 4; var minAngle = -Math.PI / 3; var targetAngle = Math.max(minAngle, Math.min(maxAngle, self.vy / 40)); birdSprite.rotation = targetAngle; }; // Bird jump self.flap = function () { self.vy = self.jumpStrength; }; return self; }); // PipePair class var PipePair = Container.expand(function () { var self = Container.call(this); // Pipe constants self.pipeWidth = 220; self.gapHeight = 600; self.speed = 8; // px per frame (slower pipes) // Randomize gap position var minGapY = 300; var maxGapY = 2732 - 300 - self.gapHeight - 120; // leave space for ground self.gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY + 1)); // Top pipe: height is from top to gapY self.topPipe = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 1.0, x: 0, y: self.gapY, height: self.gapY // fill from top to gap }); // Bottom pipe: height is from gapY+gapHeight to bottom self.bottomPipe = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 0.0, x: 0, y: self.gapY + self.gapHeight, height: 2732 - (self.gapY + self.gapHeight) // fill from gap to bottom }); // For scoring self.passed = false; // Update self.update = function () { self.x -= self.speed; }; // Helper for collision self.getTopRect = function () { return new Rectangle(self.x - self.pipeWidth / 2, 0, self.pipeWidth, self.gapY); }; self.getBottomRect = function () { return new Rectangle(self.x - self.pipeWidth / 2, self.gapY + self.gapHeight, self.pipeWidth, 2732 - (self.gapY + self.gapHeight)); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // always black, use image for sky }); /**** * Game Code ****/ // Ground: brown box // Pipe: green box // Bird: yellow ellipse // Game constants var BIRD_START_X = 600; var BIRD_START_Y = 1366; var PIPE_INTERVAL = 110; // frames between pipes (slower, matches pipe speed) var GROUND_HEIGHT = 260; var GROUND_Y = 2732 - GROUND_HEIGHT; // ground top y // Game state var bird; var pipes = []; var ground; var score = 0; var scoreTxt; var started = false; var gameOver = false; var lastPipeTick = 0; // GUI score scoreTxt = new Text2('0', { size: 160, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Background sky (full screen, behind everything) var backgroundSky = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(backgroundSky); // Ground ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: GROUND_Y }); game.addChild(ground); // Bird bird = new Bird(); bird.x = BIRD_START_X; bird.y = BIRD_START_Y; game.addChild(bird); // Reset function 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; // Reset score score = 0; scoreTxt.setText(score); started = false; gameOver = false; lastPipeTick = LK.ticks; LK.playMusic('mqhni', { loop: true }); } // Start game on first tap game.down = function (x, y, obj) { if (gameOver) return; if (!started) { started = true; bird.flap(); LK.getSound('birdflap').play(); return; } bird.flap(); LK.getSound('birdflap').play(); }; // Main update loop game.update = function () { if (gameOver) return; if (!started) { // Idle bird bobbing bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 30; return; } // Bird physics bird.update(); // Clamp bird to top of screen if (bird.y < bird.radius) { bird.y = bird.radius; bird.vy = 0; } // Add pipes if (LK.ticks - lastPipeTick >= PIPE_INTERVAL) { var pipePair = new PipePair(); pipePair.x = 2048 + pipePair.pipeWidth / 2; game.addChild(pipePair); pipes.push(pipePair); lastPipeTick = LK.ticks; } // Update pipes, check for offscreen and scoring for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; pipe.update(); // Remove offscreen pipes if (pipe.x < -pipe.pipeWidth / 2) { pipe.destroy(); pipes.splice(i, 1); continue; } // Scoring: passed center of bird if (!pipe.passed && pipe.x + pipe.pipeWidth / 2 < bird.x - bird.radius) { pipe.passed = true; score += 1; scoreTxt.setText(score); LK.getSound('pipeskip').play(); } } // Collision detection // Bird rectangle var birdRect = new Rectangle(bird.x - bird.radius * 0.8, bird.y - bird.radius * 0.8, bird.radius * 1.6, bird.radius * 1.6); // Check collision with pipes for (var j = 0; j < pipes.length; j++) { var pipe = pipes[j]; var topRect = pipe.getTopRect(); var bottomRect = pipe.getBottomRect(); if (rectsIntersect(birdRect, topRect) || rectsIntersect(birdRect, bottomRect)) { triggerGameOver(); return; } } // Check collision with ground if (bird.y + bird.radius > GROUND_Y + GROUND_HEIGHT) { // Bird is below ground, clamp to ground, but do not die bird.y = GROUND_Y + GROUND_HEIGHT - bird.radius; bird.vy = 0; } // Check if bird goes above the sky (top of screen) if (bird.y - bird.radius < 0) { bird.y = bird.radius; triggerGameOver(); return; } }; // Rectangle intersection helper function rectsIntersect(r1, r2) { return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y); } // Game over function triggerGameOver() { if (gameOver) return; gameOver = true; LK.getSound('pipehit').play(); LK.stopMusic(); // Flash red LK.effects.flashScreen(0xff0000, 800); // Show game over popup (handled by LK) LK.showGameOver(); } // Reset on game over LK.on('gameover', function () { resetGame(); }); // Initial reset resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bird 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
// Bird physics constants
self.gravity = 1.2; // px per frame^2 (slower fall)
self.jumpStrength = -24; // px per frame (less abrupt jump)
// Bird update
self.update = function () {
self.vy += self.gravity;
self.y += self.vy;
// Clamp rotation for visual feedback
var maxAngle = Math.PI / 4;
var minAngle = -Math.PI / 3;
var targetAngle = Math.max(minAngle, Math.min(maxAngle, self.vy / 40));
birdSprite.rotation = targetAngle;
};
// Bird jump
self.flap = function () {
self.vy = self.jumpStrength;
};
return self;
});
// PipePair class
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe constants
self.pipeWidth = 220;
self.gapHeight = 600;
self.speed = 8; // px per frame (slower pipes)
// Randomize gap position
var minGapY = 300;
var maxGapY = 2732 - 300 - self.gapHeight - 120; // leave space for ground
self.gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY + 1));
// Top pipe: height is from top to gapY
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: self.gapY,
height: self.gapY // fill from top to gap
});
// Bottom pipe: height is from gapY+gapHeight to bottom
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0.0,
x: 0,
y: self.gapY + self.gapHeight,
height: 2732 - (self.gapY + self.gapHeight) // fill from gap to bottom
});
// For scoring
self.passed = false;
// Update
self.update = function () {
self.x -= self.speed;
};
// Helper for collision
self.getTopRect = function () {
return new Rectangle(self.x - self.pipeWidth / 2, 0, self.pipeWidth, self.gapY);
};
self.getBottomRect = function () {
return new Rectangle(self.x - self.pipeWidth / 2, self.gapY + self.gapHeight, self.pipeWidth, 2732 - (self.gapY + self.gapHeight));
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // always black, use image for sky
});
/****
* Game Code
****/
// Ground: brown box
// Pipe: green box
// Bird: yellow ellipse
// Game constants
var BIRD_START_X = 600;
var BIRD_START_Y = 1366;
var PIPE_INTERVAL = 110; // frames between pipes (slower, matches pipe speed)
var GROUND_HEIGHT = 260;
var GROUND_Y = 2732 - GROUND_HEIGHT; // ground top y
// Game state
var bird;
var pipes = [];
var ground;
var score = 0;
var scoreTxt;
var started = false;
var gameOver = false;
var lastPipeTick = 0;
// GUI score
scoreTxt = new Text2('0', {
size: 160,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Background sky (full screen, behind everything)
var backgroundSky = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(backgroundSky);
// Ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GROUND_Y
});
game.addChild(ground);
// Bird
bird = new Bird();
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
game.addChild(bird);
// Reset function
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;
// Reset score
score = 0;
scoreTxt.setText(score);
started = false;
gameOver = false;
lastPipeTick = LK.ticks;
LK.playMusic('mqhni', {
loop: true
});
}
// Start game on first tap
game.down = function (x, y, obj) {
if (gameOver) return;
if (!started) {
started = true;
bird.flap();
LK.getSound('birdflap').play();
return;
}
bird.flap();
LK.getSound('birdflap').play();
};
// Main update loop
game.update = function () {
if (gameOver) return;
if (!started) {
// Idle bird bobbing
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 30;
return;
}
// Bird physics
bird.update();
// Clamp bird to top of screen
if (bird.y < bird.radius) {
bird.y = bird.radius;
bird.vy = 0;
}
// Add pipes
if (LK.ticks - lastPipeTick >= PIPE_INTERVAL) {
var pipePair = new PipePair();
pipePair.x = 2048 + pipePair.pipeWidth / 2;
game.addChild(pipePair);
pipes.push(pipePair);
lastPipeTick = LK.ticks;
}
// Update pipes, check for offscreen and scoring
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove offscreen pipes
if (pipe.x < -pipe.pipeWidth / 2) {
pipe.destroy();
pipes.splice(i, 1);
continue;
}
// Scoring: passed center of bird
if (!pipe.passed && pipe.x + pipe.pipeWidth / 2 < bird.x - bird.radius) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score);
LK.getSound('pipeskip').play();
}
}
// Collision detection
// Bird rectangle
var birdRect = new Rectangle(bird.x - bird.radius * 0.8, bird.y - bird.radius * 0.8, bird.radius * 1.6, bird.radius * 1.6);
// Check collision with pipes
for (var j = 0; j < pipes.length; j++) {
var pipe = pipes[j];
var topRect = pipe.getTopRect();
var bottomRect = pipe.getBottomRect();
if (rectsIntersect(birdRect, topRect) || rectsIntersect(birdRect, bottomRect)) {
triggerGameOver();
return;
}
}
// Check collision with ground
if (bird.y + bird.radius > GROUND_Y + GROUND_HEIGHT) {
// Bird is below ground, clamp to ground, but do not die
bird.y = GROUND_Y + GROUND_HEIGHT - bird.radius;
bird.vy = 0;
}
// Check if bird goes above the sky (top of screen)
if (bird.y - bird.radius < 0) {
bird.y = bird.radius;
triggerGameOver();
return;
}
};
// Rectangle intersection helper
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
// Game over
function triggerGameOver() {
if (gameOver) return;
gameOver = true;
LK.getSound('pipehit').play();
LK.stopMusic();
// Flash red
LK.effects.flashScreen(0xff0000, 800);
// Show game over popup (handled by LK)
LK.showGameOver();
}
// Reset on game over
LK.on('gameover', function () {
resetGame();
});
// Initial reset
resetGame();