/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdSprite = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.25;
self.jumpPower = -8;
self.rotation = 0;
self.dead = false;
self.flap = function () {
if (self.dead) return;
self.velocity = self.jumpPower;
LK.getSound('flap').play();
tween(self, {
rotation: -0.5
}, {
duration: 100,
easing: tween.easeOut
});
};
self.update = function () {
if (self.dead) return;
self.velocity += self.gravity;
self.y += self.velocity;
// Rotate bird based on velocity
if (self.velocity > 0) {
var targetRotation = Math.min(Math.PI / 2, self.velocity * 0.05);
tween.stop(self, {
rotation: true
});
tween(self, {
rotation: targetRotation
}, {
duration: 100,
easing: tween.linear
});
}
};
self.die = function () {
if (self.dead) return;
self.dead = true;
LK.getSound('hit').play();
tween.stop(self);
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
self.coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.collected = false;
self.update = function () {
self.x -= self.speed;
};
self.checkCollection = function (bird) {
if (self.collected || bird.dead) return false;
// Simple circular collision detection
var birdBounds = {
x: bird.x,
y: bird.y,
radius: 25 // Half of bird width
};
var coinBounds = {
x: self.x,
y: self.y,
radius: 25 // Half of coin width
};
var dx = birdBounds.x - coinBounds.x;
var dy = birdBounds.y - coinBounds.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < birdBounds.radius + coinBounds.radius) {
self.collected = true;
return true;
}
return false;
};
return self;
});
var Pipe = Container.expand(function () {
var self = Container.call(this);
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1.0
});
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0.0
});
self.speed = 2.5;
self.scored = false;
self.gapHeight = 600;
self.setGapPosition = function (yPos) {
// Make top pipe extend from top of screen to gap
self.topPipe.y = yPos - self.gapHeight / 2;
self.topPipe.height = yPos - self.gapHeight / 2;
// Make bottom pipe extend from gap to ground
self.bottomPipe.y = yPos + self.gapHeight / 2;
self.bottomPipe.height = 2732 - (yPos + self.gapHeight / 2) - 100; // Account for ground height
};
self.update = function () {
self.x -= self.speed;
};
self.checkCollision = function (bird) {
if (bird.dead) return false;
// Convert positions to global space for collision check
var birdBounds = {
x: bird.x - 15,
y: bird.y - 12.5,
width: 30,
height: 25
};
var topPipeBounds = {
x: self.x - 75,
y: self.topPipe.y - self.topPipe.height,
width: 150,
height: self.topPipe.height
};
var bottomPipeBounds = {
x: self.x - 75,
y: self.bottomPipe.y,
width: 150,
height: self.bottomPipe.height
};
// Check if bird collides with either pipe
if (birdBounds.x + birdBounds.width > topPipeBounds.x && birdBounds.x < topPipeBounds.x + topPipeBounds.width && birdBounds.y < topPipeBounds.y + topPipeBounds.height && birdBounds.y + birdBounds.height > topPipeBounds.y) {
return true;
}
if (birdBounds.x + birdBounds.width > bottomPipeBounds.x && birdBounds.x < bottomPipeBounds.x + bottomPipeBounds.width && birdBounds.y < bottomPipeBounds.y + bottomPipeBounds.height && birdBounds.y + birdBounds.height > bottomPipeBounds.y) {
return true;
}
return false;
};
self.checkPassed = function (bird) {
if (self.scored || bird.dead) return false;
if (self.x + 50 < bird.x - 25) {
self.scored = true;
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bird;
var pipes = [];
var coins = [];
var ground;
var background;
var gameStarted = false;
var gameOver = false;
var score = 0;
var highScore = storage.highScore || 0;
var pipeSpawnTimer = 0;
var pipeSpawnInterval = 180; // frames (3 seconds at 60fps)
// Setup UI
// Create a box for the score
var scoreBox = LK.getAsset('ground', {
width: 220,
height: 90,
color: 0x222222,
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 0
});
scoreBox.alpha = 0.7;
LK.gui.top.addChild(scoreBox);
scoreBox.y = 40;
scoreBox.x = 0;
// Score text
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 50;
scoreTxt.x = 0;
var instructionsTxt = new Text2('Tap to flap!', {
size: 70,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionsTxt);
// Create a box for the high score
var highScoreBox = LK.getAsset('ground', {
width: 420,
height: 90,
color: 0x222222,
anchorX: 1,
anchorY: 0,
x: 0,
y: 0
});
highScoreBox.alpha = 0.7;
LK.gui.topRight.addChild(highScoreBox);
highScoreBox.x = -10;
highScoreBox.y = 10;
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -30;
highScoreTxt.y = 30;
// Position score box and score text next to high score box
scoreBox.x = -highScoreBox.width - 30;
scoreBox.y = 40;
scoreTxt.x = scoreBox.x;
scoreTxt.y = scoreBox.y + 10;
// Create background
background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
// Create ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - 100
});
game.addChild(ground);
// Create bird
bird = new Bird();
bird.x = 400;
bird.y = 2732 / 2;
game.addChild(bird);
// Define floating animation but don't use it
function floatBird() {
tween(bird, {
y: bird.y - 50
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bird, {
y: bird.y + 50
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: floatBird
});
}
});
}
// Don't call floatBird() to prevent hovering animation
// Function to start the game
function startGame() {
if (gameStarted || gameOver) return;
gameStarted = true;
LK.gui.center.removeChild(instructionsTxt);
bird.flap();
// Play background music
LK.playMusic('gameMusic');
}
// Function to reset the game
function resetGame() {
// Clear pipes
for (var i = 0; i < pipes.length; i++) {
game.removeChild(pipes[i]);
}
pipes = [];
// Clear coins
for (var i = 0; i < coins.length; i++) {
game.removeChild(coins[i]);
}
coins = [];
// Reset bird
game.removeChild(bird);
bird = new Bird();
bird.x = 400;
bird.y = 2732 / 2;
game.addChild(bird);
// Reset game state
gameStarted = false;
gameOver = false;
score = 0;
pipeSpawnTimer = 0;
scoreTxt.setText("0");
// Show instructions again
LK.gui.center.addChild(instructionsTxt);
}
// Function to spawn a new pipe
function spawnPipe() {
var pipe = new Pipe();
pipe.x = 2048 + 100; // Start off-screen to the right
// Random gap position between 20% and 80% of screen height
var minY = 2732 * 0.2;
var maxY = 2732 * 0.8 - 100; // Account for ground
var gapY = minY + Math.random() * (maxY - minY);
pipe.setGapPosition(gapY);
pipes.push(pipe);
game.addChild(pipe);
// Add a coin in the middle of the gap
var coin = new Coin();
coin.x = pipe.x; // Same x position as pipe
coin.y = gapY; // In the middle of the gap
coin.lastX = coin.x; // Initialize lastX for tracking
coins.push(coin);
game.addChild(coin);
}
// Input events
game.down = function (x, y, obj) {
if (!gameStarted) {
startGame();
} else if (!gameOver) {
bird.flap();
} else {
resetGame();
}
};
// Game update logic
game.update = function () {
if (!gameStarted) return;
// Update bird
bird.update();
// Check if bird hits the ground or flies too high
if (bird.y > 2732 - 100 - 17.5 || bird.y < 0) {
if (!gameOver) {
gameOver = true;
bird.die();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
LK.showGameOver();
}
// Keep bird above ground
if (bird.y > 2732 - 100 - 17.5) {
bird.y = 2732 - 100 - 17.5;
bird.velocity = 0;
}
return;
}
// Spawn pipes
if (gameStarted && !gameOver) {
pipeSpawnTimer++;
if (pipeSpawnTimer >= pipeSpawnInterval) {
spawnPipe();
pipeSpawnTimer = 0;
}
}
// Update pipes and check collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove pipes that have gone off screen
if (pipe.x < -100) {
game.removeChild(pipe);
pipes.splice(i, 1);
continue;
}
// Check if bird passed pipe for scoring
if (pipe.checkPassed(bird)) {
score++;
scoreTxt.setText(score.toString());
LK.setScore(score);
LK.getSound('score').play();
}
// Check for collision with pipes
if (pipe.checkCollision(bird)) {
if (!gameOver) {
gameOver = true;
bird.die();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
LK.showGameOver();
}
}
}
// Update coins and check collections
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
// Find the pipe this coin belongs to (the one closest in x, and ahead of the coin)
var closestPipe = null;
var minDist = Infinity;
for (var j = 0; j < pipes.length; j++) {
var pipe = pipes[j];
// Coin should be between the pipes, so match by x proximity
var dist = Math.abs(pipe.x - coin.x);
if (dist < minDist && pipe.x >= coin.x - 200 && pipe.x <= coin.x + 200) {
minDist = dist;
closestPipe = pipe;
}
}
// If found, keep coin between the pipes
if (closestPipe) {
coin.x = closestPipe.x;
// Place coin in the center of the gap
coin.y = closestPipe.topPipe.y + closestPipe.gapHeight / 2;
}
coin.update();
// Remove coins that have gone off screen
if (coin.x < -50) {
game.removeChild(coin);
coins.splice(i, 1);
continue;
}
// Check if bird collected the coin
if (coin.checkCollection(bird)) {
// Add points for collecting the coin
score += 5;
scoreTxt.setText(score.toString());
LK.setScore(score);
LK.getSound('coin').play();
// Visually remove the coin
game.removeChild(coin);
coins.splice(i, 1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdSprite = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.25;
self.jumpPower = -8;
self.rotation = 0;
self.dead = false;
self.flap = function () {
if (self.dead) return;
self.velocity = self.jumpPower;
LK.getSound('flap').play();
tween(self, {
rotation: -0.5
}, {
duration: 100,
easing: tween.easeOut
});
};
self.update = function () {
if (self.dead) return;
self.velocity += self.gravity;
self.y += self.velocity;
// Rotate bird based on velocity
if (self.velocity > 0) {
var targetRotation = Math.min(Math.PI / 2, self.velocity * 0.05);
tween.stop(self, {
rotation: true
});
tween(self, {
rotation: targetRotation
}, {
duration: 100,
easing: tween.linear
});
}
};
self.die = function () {
if (self.dead) return;
self.dead = true;
LK.getSound('hit').play();
tween.stop(self);
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
self.coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.collected = false;
self.update = function () {
self.x -= self.speed;
};
self.checkCollection = function (bird) {
if (self.collected || bird.dead) return false;
// Simple circular collision detection
var birdBounds = {
x: bird.x,
y: bird.y,
radius: 25 // Half of bird width
};
var coinBounds = {
x: self.x,
y: self.y,
radius: 25 // Half of coin width
};
var dx = birdBounds.x - coinBounds.x;
var dy = birdBounds.y - coinBounds.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < birdBounds.radius + coinBounds.radius) {
self.collected = true;
return true;
}
return false;
};
return self;
});
var Pipe = Container.expand(function () {
var self = Container.call(this);
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1.0
});
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0.0
});
self.speed = 2.5;
self.scored = false;
self.gapHeight = 600;
self.setGapPosition = function (yPos) {
// Make top pipe extend from top of screen to gap
self.topPipe.y = yPos - self.gapHeight / 2;
self.topPipe.height = yPos - self.gapHeight / 2;
// Make bottom pipe extend from gap to ground
self.bottomPipe.y = yPos + self.gapHeight / 2;
self.bottomPipe.height = 2732 - (yPos + self.gapHeight / 2) - 100; // Account for ground height
};
self.update = function () {
self.x -= self.speed;
};
self.checkCollision = function (bird) {
if (bird.dead) return false;
// Convert positions to global space for collision check
var birdBounds = {
x: bird.x - 15,
y: bird.y - 12.5,
width: 30,
height: 25
};
var topPipeBounds = {
x: self.x - 75,
y: self.topPipe.y - self.topPipe.height,
width: 150,
height: self.topPipe.height
};
var bottomPipeBounds = {
x: self.x - 75,
y: self.bottomPipe.y,
width: 150,
height: self.bottomPipe.height
};
// Check if bird collides with either pipe
if (birdBounds.x + birdBounds.width > topPipeBounds.x && birdBounds.x < topPipeBounds.x + topPipeBounds.width && birdBounds.y < topPipeBounds.y + topPipeBounds.height && birdBounds.y + birdBounds.height > topPipeBounds.y) {
return true;
}
if (birdBounds.x + birdBounds.width > bottomPipeBounds.x && birdBounds.x < bottomPipeBounds.x + bottomPipeBounds.width && birdBounds.y < bottomPipeBounds.y + bottomPipeBounds.height && birdBounds.y + birdBounds.height > bottomPipeBounds.y) {
return true;
}
return false;
};
self.checkPassed = function (bird) {
if (self.scored || bird.dead) return false;
if (self.x + 50 < bird.x - 25) {
self.scored = true;
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bird;
var pipes = [];
var coins = [];
var ground;
var background;
var gameStarted = false;
var gameOver = false;
var score = 0;
var highScore = storage.highScore || 0;
var pipeSpawnTimer = 0;
var pipeSpawnInterval = 180; // frames (3 seconds at 60fps)
// Setup UI
// Create a box for the score
var scoreBox = LK.getAsset('ground', {
width: 220,
height: 90,
color: 0x222222,
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 0
});
scoreBox.alpha = 0.7;
LK.gui.top.addChild(scoreBox);
scoreBox.y = 40;
scoreBox.x = 0;
// Score text
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 50;
scoreTxt.x = 0;
var instructionsTxt = new Text2('Tap to flap!', {
size: 70,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionsTxt);
// Create a box for the high score
var highScoreBox = LK.getAsset('ground', {
width: 420,
height: 90,
color: 0x222222,
anchorX: 1,
anchorY: 0,
x: 0,
y: 0
});
highScoreBox.alpha = 0.7;
LK.gui.topRight.addChild(highScoreBox);
highScoreBox.x = -10;
highScoreBox.y = 10;
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -30;
highScoreTxt.y = 30;
// Position score box and score text next to high score box
scoreBox.x = -highScoreBox.width - 30;
scoreBox.y = 40;
scoreTxt.x = scoreBox.x;
scoreTxt.y = scoreBox.y + 10;
// Create background
background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
// Create ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - 100
});
game.addChild(ground);
// Create bird
bird = new Bird();
bird.x = 400;
bird.y = 2732 / 2;
game.addChild(bird);
// Define floating animation but don't use it
function floatBird() {
tween(bird, {
y: bird.y - 50
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bird, {
y: bird.y + 50
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: floatBird
});
}
});
}
// Don't call floatBird() to prevent hovering animation
// Function to start the game
function startGame() {
if (gameStarted || gameOver) return;
gameStarted = true;
LK.gui.center.removeChild(instructionsTxt);
bird.flap();
// Play background music
LK.playMusic('gameMusic');
}
// Function to reset the game
function resetGame() {
// Clear pipes
for (var i = 0; i < pipes.length; i++) {
game.removeChild(pipes[i]);
}
pipes = [];
// Clear coins
for (var i = 0; i < coins.length; i++) {
game.removeChild(coins[i]);
}
coins = [];
// Reset bird
game.removeChild(bird);
bird = new Bird();
bird.x = 400;
bird.y = 2732 / 2;
game.addChild(bird);
// Reset game state
gameStarted = false;
gameOver = false;
score = 0;
pipeSpawnTimer = 0;
scoreTxt.setText("0");
// Show instructions again
LK.gui.center.addChild(instructionsTxt);
}
// Function to spawn a new pipe
function spawnPipe() {
var pipe = new Pipe();
pipe.x = 2048 + 100; // Start off-screen to the right
// Random gap position between 20% and 80% of screen height
var minY = 2732 * 0.2;
var maxY = 2732 * 0.8 - 100; // Account for ground
var gapY = minY + Math.random() * (maxY - minY);
pipe.setGapPosition(gapY);
pipes.push(pipe);
game.addChild(pipe);
// Add a coin in the middle of the gap
var coin = new Coin();
coin.x = pipe.x; // Same x position as pipe
coin.y = gapY; // In the middle of the gap
coin.lastX = coin.x; // Initialize lastX for tracking
coins.push(coin);
game.addChild(coin);
}
// Input events
game.down = function (x, y, obj) {
if (!gameStarted) {
startGame();
} else if (!gameOver) {
bird.flap();
} else {
resetGame();
}
};
// Game update logic
game.update = function () {
if (!gameStarted) return;
// Update bird
bird.update();
// Check if bird hits the ground or flies too high
if (bird.y > 2732 - 100 - 17.5 || bird.y < 0) {
if (!gameOver) {
gameOver = true;
bird.die();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
LK.showGameOver();
}
// Keep bird above ground
if (bird.y > 2732 - 100 - 17.5) {
bird.y = 2732 - 100 - 17.5;
bird.velocity = 0;
}
return;
}
// Spawn pipes
if (gameStarted && !gameOver) {
pipeSpawnTimer++;
if (pipeSpawnTimer >= pipeSpawnInterval) {
spawnPipe();
pipeSpawnTimer = 0;
}
}
// Update pipes and check collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove pipes that have gone off screen
if (pipe.x < -100) {
game.removeChild(pipe);
pipes.splice(i, 1);
continue;
}
// Check if bird passed pipe for scoring
if (pipe.checkPassed(bird)) {
score++;
scoreTxt.setText(score.toString());
LK.setScore(score);
LK.getSound('score').play();
}
// Check for collision with pipes
if (pipe.checkCollision(bird)) {
if (!gameOver) {
gameOver = true;
bird.die();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
LK.showGameOver();
}
}
}
// Update coins and check collections
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
// Find the pipe this coin belongs to (the one closest in x, and ahead of the coin)
var closestPipe = null;
var minDist = Infinity;
for (var j = 0; j < pipes.length; j++) {
var pipe = pipes[j];
// Coin should be between the pipes, so match by x proximity
var dist = Math.abs(pipe.x - coin.x);
if (dist < minDist && pipe.x >= coin.x - 200 && pipe.x <= coin.x + 200) {
minDist = dist;
closestPipe = pipe;
}
}
// If found, keep coin between the pipes
if (closestPipe) {
coin.x = closestPipe.x;
// Place coin in the center of the gap
coin.y = closestPipe.topPipe.y + closestPipe.gapHeight / 2;
}
coin.update();
// Remove coins that have gone off screen
if (coin.x < -50) {
game.removeChild(coin);
coins.splice(i, 1);
continue;
}
// Check if bird collected the coin
if (coin.checkCollection(bird)) {
// Add points for collecting the coin
score += 5;
scoreTxt.setText(score.toString());
LK.setScore(score);
LK.getSound('coin').play();
// Visually remove the coin
game.removeChild(coin);
coins.splice(i, 1);
}
}
};