/****
* 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.velocityY = 0;
self.gravity = 1.2; // Gravity per frame
self.flapStrength = -28; // Negative for upward movement
// Bird size for collision
self.radius = birdAsset.width * 0.45;
// Flap method
self.flap = function () {
self.velocityY = self.flapStrength;
// Animate a quick upward tilt
tween(self, {
rotation: -0.3
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Return to downward tilt
tween(self, {
rotation: 0.4
}, {
duration: 300,
easing: tween.cubicIn
});
}
});
};
// Update method
self.update = function () {
self.velocityY += self.gravity;
self.y += self.velocityY;
// Clamp rotation based on velocity
if (self.velocityY > 0) {
self.rotation = 0.4;
} else if (self.velocityY < 0) {
self.rotation = -0.3;
}
};
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe config
self.pipeWidth = 220;
self.gapHeight = 520;
self.speed = 7;
// Randomize gap position
var minGapY = 400;
var maxGapY = 2732 - 400 - self.gapHeight;
self.gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY));
// Top pipe
var topPipe = self.attachAsset('pipe', {
anchorX: 0,
anchorY: 1,
width: self.pipeWidth,
height: self.gapY,
color: 0x3bb54a,
shape: 'box',
x: 0,
y: self.gapY
});
// Bottom pipe
var bottomPipe = self.attachAsset('pipe', {
anchorX: 0,
anchorY: 0,
width: self.pipeWidth,
height: 2732 - (self.gapY + self.gapHeight),
color: 0x3bb54a,
shape: 'box',
x: 0,
y: self.gapY + self.gapHeight
});
// For collision
self.topPipe = topPipe;
self.bottomPipe = bottomPipe;
// Scoring flag
self.passed = false;
// Update method
self.update = function () {
self.x -= self.speed;
};
// Collision check with bird
self.collidesWith = function (bird) {
// Bird bounding box
var bx = bird.x;
var by = bird.y;
var br = bird.radius;
// Top pipe rect
var tx = self.x;
var ty = 0;
var tw = self.pipeWidth;
var th = self.gapY;
// Bottom pipe rect
var bx2 = self.x;
var by2 = self.gapY + self.gapHeight;
var bw2 = self.pipeWidth;
var bh2 = 2732 - by2;
// Circle-rectangle collision
function circleRectCollide(cx, cy, cr, rx, ry, rw, rh) {
var closestX = Math.max(rx, Math.min(cx, rx + rw));
var closestY = Math.max(ry, Math.min(cy, ry + rh));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < cr * cr;
}
if (circleRectCollide(bx, by, br, tx, ty, tw, th)) return true;
if (circleRectCollide(bx, by, br, bx2, by2, bw2, bh2)) return true;
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
var BIRD_START_X = 600;
var BIRD_START_Y = 1200;
var PIPE_INTERVAL = 90; // Frames between pipes
var GROUND_HEIGHT = 180;
// Create bird asset (ellipse)
// Create pipe asset (box)
// Ground asset (for collision)
// Score text
var scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Bird instance
var bird = new Bird();
game.addChild(bird);
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
// Ground instance
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - GROUND_HEIGHT
});
game.addChild(ground);
// Pipes array
var pipes = [];
// Game state
var gameStarted = false;
var gameOver = false;
var frameCount = 0;
// For tap/flap
game.down = function (x, y, obj) {
if (gameOver) return;
if (!gameStarted) {
gameStarted = true;
}
bird.flap();
};
// Main update loop
game.update = function () {
if (gameOver) return;
if (gameStarted) {
bird.update();
// Add new pipes
if (frameCount % PIPE_INTERVAL === 0) {
var pipePair = new PipePair();
pipePair.x = 2048;
game.addChild(pipePair);
pipes.push(pipePair);
}
// Update pipes and check for collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Collision
if (pipe.collidesWith(bird)) {
endGame();
return;
}
// Score: when bird passes the pipe
if (!pipe.passed && pipe.x + pipe.pipeWidth < bird.x) {
pipe.passed = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
}
// Remove off-screen pipes
if (pipe.x + pipe.pipeWidth < 0) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Ground collision
if (bird.y + bird.radius > 2732 - GROUND_HEIGHT) {
bird.y = 2732 - GROUND_HEIGHT - bird.radius;
endGame();
return;
}
// Ceiling clamp
if (bird.y - bird.radius < 0) {
bird.y = bird.radius;
bird.velocityY = 0;
}
frameCount++;
} else {
// Idle bird bobbing
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 30;
}
};
// End game function
function endGame() {
if (gameOver) return;
gameOver = true;
// Flash red
LK.effects.flashScreen(0xff0000, 600);
// Show game over popup
LK.showGameOver();
}
// Reset on game restart
game.on('destroy', function () {
// Clean up
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
gameStarted = false;
gameOver = false;
frameCount = 0;
LK.setScore(0);
scoreTxt.setText('0');
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
bird.velocityY = 0;
bird.rotation = 0;
}); /****
* 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.velocityY = 0;
self.gravity = 1.2; // Gravity per frame
self.flapStrength = -28; // Negative for upward movement
// Bird size for collision
self.radius = birdAsset.width * 0.45;
// Flap method
self.flap = function () {
self.velocityY = self.flapStrength;
// Animate a quick upward tilt
tween(self, {
rotation: -0.3
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Return to downward tilt
tween(self, {
rotation: 0.4
}, {
duration: 300,
easing: tween.cubicIn
});
}
});
};
// Update method
self.update = function () {
self.velocityY += self.gravity;
self.y += self.velocityY;
// Clamp rotation based on velocity
if (self.velocityY > 0) {
self.rotation = 0.4;
} else if (self.velocityY < 0) {
self.rotation = -0.3;
}
};
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe config
self.pipeWidth = 220;
self.gapHeight = 520;
self.speed = 7;
// Randomize gap position
var minGapY = 400;
var maxGapY = 2732 - 400 - self.gapHeight;
self.gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY));
// Top pipe
var topPipe = self.attachAsset('pipe', {
anchorX: 0,
anchorY: 1,
width: self.pipeWidth,
height: self.gapY,
color: 0x3bb54a,
shape: 'box',
x: 0,
y: self.gapY
});
// Bottom pipe
var bottomPipe = self.attachAsset('pipe', {
anchorX: 0,
anchorY: 0,
width: self.pipeWidth,
height: 2732 - (self.gapY + self.gapHeight),
color: 0x3bb54a,
shape: 'box',
x: 0,
y: self.gapY + self.gapHeight
});
// For collision
self.topPipe = topPipe;
self.bottomPipe = bottomPipe;
// Scoring flag
self.passed = false;
// Update method
self.update = function () {
self.x -= self.speed;
};
// Collision check with bird
self.collidesWith = function (bird) {
// Bird bounding box
var bx = bird.x;
var by = bird.y;
var br = bird.radius;
// Top pipe rect
var tx = self.x;
var ty = 0;
var tw = self.pipeWidth;
var th = self.gapY;
// Bottom pipe rect
var bx2 = self.x;
var by2 = self.gapY + self.gapHeight;
var bw2 = self.pipeWidth;
var bh2 = 2732 - by2;
// Circle-rectangle collision
function circleRectCollide(cx, cy, cr, rx, ry, rw, rh) {
var closestX = Math.max(rx, Math.min(cx, rx + rw));
var closestY = Math.max(ry, Math.min(cy, ry + rh));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < cr * cr;
}
if (circleRectCollide(bx, by, br, tx, ty, tw, th)) return true;
if (circleRectCollide(bx, by, br, bx2, by2, bw2, bh2)) return true;
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
var BIRD_START_X = 600;
var BIRD_START_Y = 1200;
var PIPE_INTERVAL = 90; // Frames between pipes
var GROUND_HEIGHT = 180;
// Create bird asset (ellipse)
// Create pipe asset (box)
// Ground asset (for collision)
// Score text
var scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Bird instance
var bird = new Bird();
game.addChild(bird);
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
// Ground instance
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - GROUND_HEIGHT
});
game.addChild(ground);
// Pipes array
var pipes = [];
// Game state
var gameStarted = false;
var gameOver = false;
var frameCount = 0;
// For tap/flap
game.down = function (x, y, obj) {
if (gameOver) return;
if (!gameStarted) {
gameStarted = true;
}
bird.flap();
};
// Main update loop
game.update = function () {
if (gameOver) return;
if (gameStarted) {
bird.update();
// Add new pipes
if (frameCount % PIPE_INTERVAL === 0) {
var pipePair = new PipePair();
pipePair.x = 2048;
game.addChild(pipePair);
pipes.push(pipePair);
}
// Update pipes and check for collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Collision
if (pipe.collidesWith(bird)) {
endGame();
return;
}
// Score: when bird passes the pipe
if (!pipe.passed && pipe.x + pipe.pipeWidth < bird.x) {
pipe.passed = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
}
// Remove off-screen pipes
if (pipe.x + pipe.pipeWidth < 0) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Ground collision
if (bird.y + bird.radius > 2732 - GROUND_HEIGHT) {
bird.y = 2732 - GROUND_HEIGHT - bird.radius;
endGame();
return;
}
// Ceiling clamp
if (bird.y - bird.radius < 0) {
bird.y = bird.radius;
bird.velocityY = 0;
}
frameCount++;
} else {
// Idle bird bobbing
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 30;
}
};
// End game function
function endGame() {
if (gameOver) return;
gameOver = true;
// Flash red
LK.effects.flashScreen(0xff0000, 600);
// Show game over popup
LK.showGameOver();
}
// Reset on game restart
game.on('destroy', function () {
// Clean up
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
gameStarted = false;
gameOver = false;
frameCount = 0;
LK.setScore(0);
scoreTxt.setText('0');
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
bird.velocityY = 0;
bird.rotation = 0;
});