/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach a yellow ellipse as the bird
var birdAsset = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Bird physics
self.velocity = 0; // vertical speed
self.gravity = 0.7; // gravity per frame (reduced for slower fall)
self.flapStrength = -22; // negative for upward movement
// Bird size for collision
self.radius = birdAsset.width * 0.45;
// Flap method
self.flap = function () {
self.velocity = self.flapStrength;
};
// Update method (called every tick)
self.update = function () {
self.velocity += self.gravity;
self.y += self.velocity;
// Clamp rotation for visual feedback (optional)
var maxAngle = Math.PI / 4;
var minAngle = -Math.PI / 3;
var angle = self.velocity / 30 * maxAngle;
if (angle > maxAngle) angle = maxAngle;
if (angle < minAngle) angle = minAngle;
birdAsset.rotation = angle;
};
return self;
});
// PipePair class (top and bottom pipes as a pair)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe dimensions
var pipeWidth = 220;
var gapHeight = 800; // vertical gap between pipes (increased gap)
// Randomize gap position
var minY = 400;
var maxY = 2732 - 400 - gapHeight;
var gapY = minY + Math.floor(Math.random() * (maxY - minY + 1));
// Top pipe
var topPipe = self.attachAsset('pipe', {
width: pipeWidth,
height: gapY,
color: 0x4ec04e,
shape: 'box',
anchorX: 0,
anchorY: 1,
x: 0,
y: gapY
});
// Bottom pipe
var bottomPipe = self.attachAsset('pipe', {
width: pipeWidth,
height: 2732 - (gapY + gapHeight),
color: 0x4ec04e,
shape: 'box',
anchorX: 0,
anchorY: 0,
x: 0,
y: gapY + gapHeight
});
// For scoring: has the bird passed this pipe?
self.passed = false;
// Move pipes leftward
self.speed = 14;
self.update = function () {
self.x -= self.speed;
};
// For collision detection, expose gapY and gapHeight
self.gapY = gapY;
self.gapHeight = gapHeight;
self.pipeWidth = pipeWidth;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Tween plugin for smooth animations (optional, but included for future polish)
// --- Game constants ---
var BIRD_START_X = 600;
var BIRD_START_Y = 1366;
var PIPE_INTERVAL = 90; // frames between pipes
var GROUND_HEIGHT = 180;
// --- Game state ---
var bird;
var pipes = [];
var score = 0;
var scoreTxt;
var gameStarted = false;
var gameOver = false;
var lastPipeTick = 0;
// --- Asset initialization (shapes) ---
// --- Add ground (for collision) ---
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - GROUND_HEIGHT
});
game.addChild(ground);
// --- Add bird ---
bird = new Bird();
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
game.addChild(bird);
// --- Score display ---
scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Start instructions (centered) ---
var startTxt = new Text2('Tap to Flap!', {
size: 120,
fill: 0xFFFFFF
});
startTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startTxt);
// --- Helper: Reset game state ---
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.velocity = 0;
// Reset score
score = 0;
scoreTxt.setText('0');
// Reset state
gameStarted = false;
gameOver = false;
lastPipeTick = LK.ticks;
// Show start text
startTxt.visible = true;
}
// --- Helper: Check collision between bird and pipes/ground ---
function checkCollision() {
// Bird with ground
if (bird.y + bird.radius >= 2732 - GROUND_HEIGHT) {
// Clamp the bird to the ground so it can't pass through visually
bird.y = 2732 - GROUND_HEIGHT - bird.radius;
return true;
}
// Bird with ceiling
if (bird.y - bird.radius <= 0) {
return true;
}
// Bird with pipes
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
// Pipe bounds
var px = pipe.x;
var pw = pipe.pipeWidth;
var gapY = pipe.gapY;
var gapH = pipe.gapHeight;
// Bird's bounding box
var bx = bird.x;
var by = bird.y;
var br = bird.radius;
// Check horizontal overlap
if (bx + br > px && bx - br < px + pw) {
// Check if bird is NOT in the gap
if (by - br < gapY || by + br > gapY + gapH) {
return true;
}
}
}
return false;
}
// --- Helper: Update score if bird passes pipes ---
function updateScore() {
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
if (!pipe.passed && bird.x > pipe.x + pipe.pipeWidth) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score + '');
}
}
}
// --- Game tap/flap handler ---
function onFlap() {
if (gameOver) return;
if (!gameStarted) {
gameStarted = true;
startTxt.visible = false;
}
bird.flap();
}
// --- Touch/click events ---
game.down = function (x, y, obj) {
onFlap();
};
// --- Main game loop ---
game.update = function () {
if (gameOver) return;
// Only update bird and pipes if game started
if (gameStarted) {
bird.update();
// Add new pipes at intervals
if (LK.ticks - lastPipeTick >= PIPE_INTERVAL) {
var pipe = new PipePair();
pipe.x = 2048;
game.addChild(pipe);
pipes.push(pipe);
lastPipeTick = LK.ticks;
}
// Update pipes and remove off-screen ones
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
if (pipe.x + pipe.pipeWidth < 0) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Update score
updateScore();
// Check for collision
if (checkCollision()) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
}
};
// --- Reset game on game over (handled by LK) ---
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);
// Attach a yellow ellipse as the bird
var birdAsset = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Bird physics
self.velocity = 0; // vertical speed
self.gravity = 0.7; // gravity per frame (reduced for slower fall)
self.flapStrength = -22; // negative for upward movement
// Bird size for collision
self.radius = birdAsset.width * 0.45;
// Flap method
self.flap = function () {
self.velocity = self.flapStrength;
};
// Update method (called every tick)
self.update = function () {
self.velocity += self.gravity;
self.y += self.velocity;
// Clamp rotation for visual feedback (optional)
var maxAngle = Math.PI / 4;
var minAngle = -Math.PI / 3;
var angle = self.velocity / 30 * maxAngle;
if (angle > maxAngle) angle = maxAngle;
if (angle < minAngle) angle = minAngle;
birdAsset.rotation = angle;
};
return self;
});
// PipePair class (top and bottom pipes as a pair)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe dimensions
var pipeWidth = 220;
var gapHeight = 800; // vertical gap between pipes (increased gap)
// Randomize gap position
var minY = 400;
var maxY = 2732 - 400 - gapHeight;
var gapY = minY + Math.floor(Math.random() * (maxY - minY + 1));
// Top pipe
var topPipe = self.attachAsset('pipe', {
width: pipeWidth,
height: gapY,
color: 0x4ec04e,
shape: 'box',
anchorX: 0,
anchorY: 1,
x: 0,
y: gapY
});
// Bottom pipe
var bottomPipe = self.attachAsset('pipe', {
width: pipeWidth,
height: 2732 - (gapY + gapHeight),
color: 0x4ec04e,
shape: 'box',
anchorX: 0,
anchorY: 0,
x: 0,
y: gapY + gapHeight
});
// For scoring: has the bird passed this pipe?
self.passed = false;
// Move pipes leftward
self.speed = 14;
self.update = function () {
self.x -= self.speed;
};
// For collision detection, expose gapY and gapHeight
self.gapY = gapY;
self.gapHeight = gapHeight;
self.pipeWidth = pipeWidth;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Tween plugin for smooth animations (optional, but included for future polish)
// --- Game constants ---
var BIRD_START_X = 600;
var BIRD_START_Y = 1366;
var PIPE_INTERVAL = 90; // frames between pipes
var GROUND_HEIGHT = 180;
// --- Game state ---
var bird;
var pipes = [];
var score = 0;
var scoreTxt;
var gameStarted = false;
var gameOver = false;
var lastPipeTick = 0;
// --- Asset initialization (shapes) ---
// --- Add ground (for collision) ---
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - GROUND_HEIGHT
});
game.addChild(ground);
// --- Add bird ---
bird = new Bird();
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
game.addChild(bird);
// --- Score display ---
scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Start instructions (centered) ---
var startTxt = new Text2('Tap to Flap!', {
size: 120,
fill: 0xFFFFFF
});
startTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startTxt);
// --- Helper: Reset game state ---
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.velocity = 0;
// Reset score
score = 0;
scoreTxt.setText('0');
// Reset state
gameStarted = false;
gameOver = false;
lastPipeTick = LK.ticks;
// Show start text
startTxt.visible = true;
}
// --- Helper: Check collision between bird and pipes/ground ---
function checkCollision() {
// Bird with ground
if (bird.y + bird.radius >= 2732 - GROUND_HEIGHT) {
// Clamp the bird to the ground so it can't pass through visually
bird.y = 2732 - GROUND_HEIGHT - bird.radius;
return true;
}
// Bird with ceiling
if (bird.y - bird.radius <= 0) {
return true;
}
// Bird with pipes
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
// Pipe bounds
var px = pipe.x;
var pw = pipe.pipeWidth;
var gapY = pipe.gapY;
var gapH = pipe.gapHeight;
// Bird's bounding box
var bx = bird.x;
var by = bird.y;
var br = bird.radius;
// Check horizontal overlap
if (bx + br > px && bx - br < px + pw) {
// Check if bird is NOT in the gap
if (by - br < gapY || by + br > gapY + gapH) {
return true;
}
}
}
return false;
}
// --- Helper: Update score if bird passes pipes ---
function updateScore() {
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
if (!pipe.passed && bird.x > pipe.x + pipe.pipeWidth) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score + '');
}
}
}
// --- Game tap/flap handler ---
function onFlap() {
if (gameOver) return;
if (!gameStarted) {
gameStarted = true;
startTxt.visible = false;
}
bird.flap();
}
// --- Touch/click events ---
game.down = function (x, y, obj) {
onFlap();
};
// --- Main game loop ---
game.update = function () {
if (gameOver) return;
// Only update bird and pipes if game started
if (gameStarted) {
bird.update();
// Add new pipes at intervals
if (LK.ticks - lastPipeTick >= PIPE_INTERVAL) {
var pipe = new PipePair();
pipe.x = 2048;
game.addChild(pipe);
pipes.push(pipe);
lastPipeTick = LK.ticks;
}
// Update pipes and remove off-screen ones
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
if (pipe.x + pipe.pipeWidth < 0) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Update score
updateScore();
// Check for collision
if (checkCollision()) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
}
};
// --- Reset game on game over (handled by LK) ---
LK.on('gameover', function () {
resetGame();
});
// --- Initial reset ---
resetGame();