/****
* 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.vy = 0; // vertical speed
self.gravity = 0.7; // gravity per frame (further reduced for slower fall)
self.flapStrength = -18; // negative = up (further reduced for softer flap)
self.flap = function () {
self.vy = self.flapStrength;
LK.getSound('flap').play();
// Animate a quick scale for feedback
tween(birdSprite, {
scaleY: 0.7
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(birdSprite, {
scaleY: 1
}, {
duration: 120,
easing: tween.cubicOut
});
}
});
};
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 / 6;
var t = Math.max(-1, Math.min(1, self.vy / 40));
birdSprite.rotation = minAngle + (maxAngle - minAngle) * (t + 1) / 2;
};
return self;
});
// Ground class
var Ground = Container.expand(function () {
var self = Container.call(this);
var groundSprite = self.attachAsset('ground', {
anchorX: 0,
anchorY: 0
});
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe gap size
self.gap = 600;
// Pipe speed
self.speed = 9;
// Top pipe
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1
});
// Bottom pipe
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0
});
// Set pipes' positions based on gapY
self.setGapY = function (gapY) {
self.topPipe.y = gapY - self.gap / 2;
self.bottomPipe.y = gapY + self.gap / 2;
// Extend top pipe to reach ceiling
self.topPipe.height = self.topPipe.y + 750;
// Extend bottom pipe to reach ground
self.bottomPipe.height = GROUND_Y - self.bottomPipe.y + 750;
};
// For scoring
self.passed = false;
self.update = function () {
self.x -= self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Bird: yellow ellipse
// Pipe: green box
// Ground: brown box
// Sound: Flap
// Sound: Score
// Sound: Hit
var BIRD_START_X = 600;
var BIRD_START_Y = 1100;
var PIPE_INTERVAL = 90; // frames between pipes
var PIPE_MIN_Y = 500;
var PIPE_MAX_Y = 2000;
var GROUND_Y = 2732 - 120;
// Game state
var bird;
var pipes = [];
var ground;
var score = 0;
var scoreTxt;
var gameStarted = false;
var gameOver = false;
var gameInMenu = true;
var lastPipeTick = 0;
// GUI score
scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Menu UI elements
var menuTitle = new Text2('FLAPPY BIRD', {
size: 200,
fill: 0xFFFFFF
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = 1024;
menuTitle.y = 600;
game.addChild(menuTitle);
// Create a container for the play instruction and bird
var menuPlayContainer = new Container();
menuPlayContainer.x = 1024;
menuPlayContainer.y = 1200;
game.addChild(menuPlayContainer);
var menuInstructions = new Text2('OYNA KANKAM', {
size: 100,
fill: 0xFFDD00
});
menuInstructions.anchor.set(0.5, 0.5);
menuInstructions.x = 0;
menuInstructions.y = 0;
menuPlayContainer.addChild(menuInstructions);
var menuSubtitle = new Text2('Tap to flap and fly through pipes!', {
size: 80,
fill: 0xCCCCCC
});
menuSubtitle.anchor.set(0.5, 0.5);
menuSubtitle.x = 1024;
menuSubtitle.y = 1200;
game.addChild(menuSubtitle);
// Add ground
ground = new Ground();
ground.x = 0;
ground.y = GROUND_Y;
game.addChild(ground);
// Add 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);
// Reset state
gameStarted = false;
gameOver = false;
gameInMenu = true;
lastPipeTick = LK.ticks;
// Show menu elements
menuTitle.visible = true;
menuPlayContainer.visible = true;
menuSubtitle.visible = true;
scoreTxt.visible = false;
}
// Start game on first tap
game.down = function (x, y, obj) {
if (gameOver) return;
if (gameInMenu) {
// Start game from menu
gameInMenu = false;
gameStarted = true;
// Hide menu elements
menuTitle.visible = false;
menuPlayContainer.visible = false;
menuSubtitle.visible = false;
scoreTxt.visible = true;
bird.flap();
return;
}
if (!gameStarted) {
gameStarted = true;
bird.flap();
return;
}
bird.flap();
};
// Main update loop
game.update = function () {
if (gameOver) return;
if (gameInMenu) {
// Bird idle bounce in menu
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 20;
// Animate menu instructions
menuInstructions.alpha = 0.5 + 0.5 * Math.sin(LK.ticks / 30);
return;
}
if (!gameStarted) {
// Bird idle bounce
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 20;
return;
}
bird.update();
// Add pipes
if (LK.ticks - lastPipeTick > PIPE_INTERVAL) {
lastPipeTick = LK.ticks;
var gapY = PIPE_MIN_Y + Math.floor(Math.random() * (PIPE_MAX_Y - PIPE_MIN_Y));
var pipePair = new PipePair();
pipePair.x = 2048 + 200;
pipePair.setGapY(gapY);
pipes.push(pipePair);
game.addChild(pipePair);
}
// Update pipes and check for collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Collision with pipes
if (bird.x + 60 > pipe.x - 60 && bird.x - 60 < pipe.x + 60 // horizontal overlap
) {
// Top pipe
if (bird.y - 45 < pipe.topPipe.y) {
// Hit top pipe
endGame();
return;
}
// Bottom pipe
if (bird.y + 45 > pipe.bottomPipe.y) {
// Hit bottom pipe
endGame();
return;
}
}
// Score: passed pipe
if (!pipe.passed && pipe.x + 60 < bird.x - 60) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score);
LK.getSound('score').play();
}
// Remove off-screen pipes
if (pipe.x < -300) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Collision with ground
if (bird.y + 45 > GROUND_Y) {
endGame();
return;
}
// Collision with ceiling
if (bird.y - 45 < 0) {
bird.y = 45;
bird.vy = 0;
}
};
// End game
function endGame() {
if (gameOver) return;
gameOver = true;
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 600);
// Show game over popup (handled by LK)
LK.showGameOver();
}
// Reset on game over (handled by LK)
game.on('reset', 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.vy = 0; // vertical speed
self.gravity = 0.7; // gravity per frame (further reduced for slower fall)
self.flapStrength = -18; // negative = up (further reduced for softer flap)
self.flap = function () {
self.vy = self.flapStrength;
LK.getSound('flap').play();
// Animate a quick scale for feedback
tween(birdSprite, {
scaleY: 0.7
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(birdSprite, {
scaleY: 1
}, {
duration: 120,
easing: tween.cubicOut
});
}
});
};
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 / 6;
var t = Math.max(-1, Math.min(1, self.vy / 40));
birdSprite.rotation = minAngle + (maxAngle - minAngle) * (t + 1) / 2;
};
return self;
});
// Ground class
var Ground = Container.expand(function () {
var self = Container.call(this);
var groundSprite = self.attachAsset('ground', {
anchorX: 0,
anchorY: 0
});
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe gap size
self.gap = 600;
// Pipe speed
self.speed = 9;
// Top pipe
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1
});
// Bottom pipe
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0
});
// Set pipes' positions based on gapY
self.setGapY = function (gapY) {
self.topPipe.y = gapY - self.gap / 2;
self.bottomPipe.y = gapY + self.gap / 2;
// Extend top pipe to reach ceiling
self.topPipe.height = self.topPipe.y + 750;
// Extend bottom pipe to reach ground
self.bottomPipe.height = GROUND_Y - self.bottomPipe.y + 750;
};
// For scoring
self.passed = false;
self.update = function () {
self.x -= self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Bird: yellow ellipse
// Pipe: green box
// Ground: brown box
// Sound: Flap
// Sound: Score
// Sound: Hit
var BIRD_START_X = 600;
var BIRD_START_Y = 1100;
var PIPE_INTERVAL = 90; // frames between pipes
var PIPE_MIN_Y = 500;
var PIPE_MAX_Y = 2000;
var GROUND_Y = 2732 - 120;
// Game state
var bird;
var pipes = [];
var ground;
var score = 0;
var scoreTxt;
var gameStarted = false;
var gameOver = false;
var gameInMenu = true;
var lastPipeTick = 0;
// GUI score
scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Menu UI elements
var menuTitle = new Text2('FLAPPY BIRD', {
size: 200,
fill: 0xFFFFFF
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = 1024;
menuTitle.y = 600;
game.addChild(menuTitle);
// Create a container for the play instruction and bird
var menuPlayContainer = new Container();
menuPlayContainer.x = 1024;
menuPlayContainer.y = 1200;
game.addChild(menuPlayContainer);
var menuInstructions = new Text2('OYNA KANKAM', {
size: 100,
fill: 0xFFDD00
});
menuInstructions.anchor.set(0.5, 0.5);
menuInstructions.x = 0;
menuInstructions.y = 0;
menuPlayContainer.addChild(menuInstructions);
var menuSubtitle = new Text2('Tap to flap and fly through pipes!', {
size: 80,
fill: 0xCCCCCC
});
menuSubtitle.anchor.set(0.5, 0.5);
menuSubtitle.x = 1024;
menuSubtitle.y = 1200;
game.addChild(menuSubtitle);
// Add ground
ground = new Ground();
ground.x = 0;
ground.y = GROUND_Y;
game.addChild(ground);
// Add 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);
// Reset state
gameStarted = false;
gameOver = false;
gameInMenu = true;
lastPipeTick = LK.ticks;
// Show menu elements
menuTitle.visible = true;
menuPlayContainer.visible = true;
menuSubtitle.visible = true;
scoreTxt.visible = false;
}
// Start game on first tap
game.down = function (x, y, obj) {
if (gameOver) return;
if (gameInMenu) {
// Start game from menu
gameInMenu = false;
gameStarted = true;
// Hide menu elements
menuTitle.visible = false;
menuPlayContainer.visible = false;
menuSubtitle.visible = false;
scoreTxt.visible = true;
bird.flap();
return;
}
if (!gameStarted) {
gameStarted = true;
bird.flap();
return;
}
bird.flap();
};
// Main update loop
game.update = function () {
if (gameOver) return;
if (gameInMenu) {
// Bird idle bounce in menu
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 20;
// Animate menu instructions
menuInstructions.alpha = 0.5 + 0.5 * Math.sin(LK.ticks / 30);
return;
}
if (!gameStarted) {
// Bird idle bounce
bird.y = BIRD_START_Y + Math.sin(LK.ticks / 20) * 20;
return;
}
bird.update();
// Add pipes
if (LK.ticks - lastPipeTick > PIPE_INTERVAL) {
lastPipeTick = LK.ticks;
var gapY = PIPE_MIN_Y + Math.floor(Math.random() * (PIPE_MAX_Y - PIPE_MIN_Y));
var pipePair = new PipePair();
pipePair.x = 2048 + 200;
pipePair.setGapY(gapY);
pipes.push(pipePair);
game.addChild(pipePair);
}
// Update pipes and check for collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Collision with pipes
if (bird.x + 60 > pipe.x - 60 && bird.x - 60 < pipe.x + 60 // horizontal overlap
) {
// Top pipe
if (bird.y - 45 < pipe.topPipe.y) {
// Hit top pipe
endGame();
return;
}
// Bottom pipe
if (bird.y + 45 > pipe.bottomPipe.y) {
// Hit bottom pipe
endGame();
return;
}
}
// Score: passed pipe
if (!pipe.passed && pipe.x + 60 < bird.x - 60) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score);
LK.getSound('score').play();
}
// Remove off-screen pipes
if (pipe.x < -300) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Collision with ground
if (bird.y + 45 > GROUND_Y) {
endGame();
return;
}
// Collision with ceiling
if (bird.y - 45 < 0) {
bird.y = 45;
bird.vy = 0;
}
};
// End game
function endGame() {
if (gameOver) return;
gameOver = true;
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 600);
// Show game over popup (handled by LK)
LK.showGameOver();
}
// Reset on game over (handled by LK)
game.on('reset', function () {
resetGame();
});
// Initial reset
resetGame();