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