/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bird = Container.expand(function (characterIndex) {
var self = Container.call(this);
var assetName = characterAssets[characterIndex - 1] || 'bird';
var birdGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.6;
self.flapPower = -16;
self.maxVelocity = 15;
self.minVelocity = -15;
self.flap = function () {
self.velocity = self.flapPower;
LK.getSound('flap').play();
// Add slight rotation animation
tween(birdGraphics, {
rotation: -0.3
}, {
duration: 100
});
tween(birdGraphics, {
rotation: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(birdGraphics, {
rotation: 0
}, {
duration: 300
});
}
});
};
self.update = function () {
// Apply gravity
self.velocity += self.gravity;
// Limit velocity
if (self.velocity > self.maxVelocity) self.velocity = self.maxVelocity;
if (self.velocity < self.minVelocity) self.velocity = self.minVelocity;
// Update position
self.y += self.velocity;
// Rotate bird based on velocity
var targetRotation = Math.max(-0.5, Math.min(0.8, self.velocity * 0.05));
birdGraphics.rotation = targetRotation;
};
return self;
});
var Pipe = Container.expand(function () {
var self = Container.call(this);
self.speed = -4;
self.scored = false;
// Create top pipe body
self.topPipeBody = self.attachAsset('pipeBody', {
anchorX: 0.5,
anchorY: 1
});
// Create top pipe cap
self.topPipeCap = self.attachAsset('pipeTop', {
anchorX: 0.5,
anchorY: 1
});
// Create bottom pipe body
self.bottomPipeBody = self.attachAsset('pipeBody', {
anchorX: 0.5,
anchorY: 0
});
// Create bottom pipe cap
self.bottomPipeCap = self.attachAsset('pipeTop', {
anchorX: 0.5,
anchorY: 0
});
self.setGap = function (centerY, gapSize) {
self.topPipeBody.y = centerY - gapSize / 2;
self.topPipeCap.y = centerY - gapSize / 2;
self.bottomPipeBody.y = centerY + gapSize / 2;
self.bottomPipeCap.y = centerY + gapSize / 2;
};
self.update = function () {
self.x += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bird;
var pipes = [];
var ground;
var gameStarted = false;
var gameOver = false;
var gameState = 'menu'; // 'menu', 'playing', 'gameOver'
var pipeSpawnTimer = 0;
var pipeSpawnInterval = 150; // frames - increased for more spacing
var gapSize = 500; // Much larger gap size for easier gameplay
var baseGapSize = 500;
var basePipeInterval = 150;
var scrollSpeed = 4;
// Character selection
var currentCharacter = storage.selectedCharacter || 1;
var characterAssets = ['bird', 'bird2', 'bird3', 'bird4'];
var totalCharacters = 4;
// Increased game dimensions
var GAME_WIDTH = 1800; // Increased from 1536
var GAME_HEIGHT = 2400; // Increased from 2048
// UI Elements
var scoreTxt = new Text2('0', {
size: 70,
// Slightly smaller for mobile
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Menu elements
var titleTxt = new Text2('FLAPPY WINGS', {
size: 80,
fill: 0xFFD700
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.x = GAME_WIDTH / 2;
titleTxt.y = -100; // Start above screen
titleTxt.alpha = 0; // Start invisible
game.addChild(titleTxt);
// Animate title entrance
tween(titleTxt, {
y: GAME_HEIGHT / 2 - 300,
alpha: 1
}, {
duration: 1000,
easing: tween.bounceOut
});
var playButtonTxt = new Text2('TAP TO PLAY', {
size: 50,
fill: 0xFFFFFF
});
playButtonTxt.anchor.set(0.5, 0.5);
playButtonTxt.x = -200; // Start off screen left
playButtonTxt.y = GAME_HEIGHT / 2 - 100;
playButtonTxt.alpha = 0; // Start invisible
game.addChild(playButtonTxt);
// Animate play button entrance with delay
LK.setTimeout(function () {
tween(playButtonTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Start pulsing animation after entrance
function pulsePlayButton() {
tween(playButtonTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButtonTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: pulsePlayButton
});
}
});
}
pulsePlayButton();
}
});
}, 500);
var highScoreTxt = new Text2('Best: 0', {
size: 40,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0.5, 0.5);
highScoreTxt.x = GAME_WIDTH + 200; // Start off screen right
highScoreTxt.y = GAME_HEIGHT / 2 + 50;
highScoreTxt.alpha = 0; // Start invisible
game.addChild(highScoreTxt);
// Animate high score entrance with delay
LK.setTimeout(function () {
tween(highScoreTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 800);
// Animate character button entrance with delay
LK.setTimeout(function () {
tween(characterButtonTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
tween(characterDisplayTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 1000);
var characterButtonTxt = new Text2('CHARACTER', {
size: 45,
fill: 0xFFD700
});
characterButtonTxt.anchor.set(0.5, 0.5);
characterButtonTxt.x = GAME_WIDTH / 2;
characterButtonTxt.y = GAME_HEIGHT / 2 + 100;
characterButtonTxt.alpha = 0;
game.addChild(characterButtonTxt);
// Character display
var characterDisplayTxt = new Text2('1/4', {
size: 35,
fill: 0xFFFFFF
});
characterDisplayTxt.anchor.set(0.5, 0.5);
characterDisplayTxt.x = GAME_WIDTH / 2;
characterDisplayTxt.y = GAME_HEIGHT / 2 + 150;
characterDisplayTxt.alpha = 0;
game.addChild(characterDisplayTxt);
var instructionTxt = new Text2('TAP TO FLAP', {
size: 45,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
instructionTxt.x = GAME_WIDTH / 2;
instructionTxt.y = GAME_HEIGHT / 2 + 150;
instructionTxt.visible = false;
game.addChild(instructionTxt);
// Game over elements
var gameOverTxt = new Text2('GAME OVER', {
size: 70,
fill: 0xFF0000
});
gameOverTxt.anchor.set(0.5, 0.5);
gameOverTxt.x = GAME_WIDTH / 2;
gameOverTxt.y = GAME_HEIGHT / 2 - 100;
gameOverTxt.visible = false;
game.addChild(gameOverTxt);
var restartTxt = new Text2('TAP TO RESTART', {
size: 45,
fill: 0xFFFFFF
});
restartTxt.anchor.set(0.5, 0.5);
restartTxt.x = GAME_WIDTH / 2;
restartTxt.y = GAME_HEIGHT / 2 + 50;
restartTxt.visible = false;
game.addChild(restartTxt);
// Initialize bird
bird = game.addChild(new Bird(currentCharacter));
bird.x = GAME_WIDTH / 3.5;
bird.y = GAME_HEIGHT / 2;
bird.visible = false; // Hide until game starts
// Create layered ground
var dirt = game.addChild(LK.getAsset('dirt', {
anchorX: 0,
anchorY: 1
}));
dirt.x = 0;
dirt.y = GAME_HEIGHT;
dirt.width = GAME_WIDTH;
ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 1
}));
ground.x = 0;
ground.y = GAME_HEIGHT - 140;
ground.width = GAME_WIDTH; // Scale to larger width
// Add grass details
var grassTufts = [];
for (var g = 0; g < GAME_WIDTH / 60; g++) {
var grass = game.addChild(LK.getAsset('grass', {
anchorX: 0.5,
anchorY: 1
}));
grass.x = g * 60 + Math.random() * 40;
grass.y = GAME_HEIGHT - 140;
grass.height = 15 + Math.random() * 10;
grassTufts.push(grass);
}
// Function to spawn pipe
function spawnPipe() {
var pipe = new Pipe();
// Better gap positioning - ensure minimum distance from top and bottom
var minY = gapSize / 2 + 150; // Minimum distance from top
var maxY = GAME_HEIGHT - ground.height - gapSize / 2 - 150; // Maximum distance from bottom
var centerY = minY + Math.random() * (maxY - minY);
pipe.setGap(centerY, gapSize);
pipe.x = GAME_WIDTH + 100;
pipes.push(pipe);
game.addChild(pipe);
}
// Function to start game
function startGame() {
if (gameState === 'menu') {
gameState = 'playing';
gameStarted = true;
// Reset bird velocity to prevent death
bird.velocity = 0;
bird.y = GAME_HEIGHT / 2;
// Hide menu elements
titleTxt.visible = false;
playButtonTxt.visible = false;
highScoreTxt.visible = false;
characterButtonTxt.visible = false;
characterDisplayTxt.visible = false;
// Show game elements
bird.visible = true;
instructionTxt.visible = true;
scoreTxt.visible = true;
// Hide instruction after a delay
LK.setTimeout(function () {
instructionTxt.visible = false;
}, 2000);
}
}
// Function to reset game
function resetGame() {
// Clean up pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.destroy();
bird = game.addChild(new Bird(currentCharacter));
bird.x = GAME_WIDTH / 4;
bird.y = GAME_HEIGHT / 2;
bird.velocity = 0;
bird.visible = false;
// Update high score
var currentScore = LK.getScore();
var bestScore = storage.highScore || 0;
if (currentScore > bestScore) {
storage.highScore = currentScore;
bestScore = currentScore;
}
// Reset game state
gameState = 'menu';
gameStarted = false;
gameOver = false;
pipeSpawnTimer = 0;
LK.setScore(0);
scoreTxt.setText('0');
// Show menu elements with animations
titleTxt.visible = true;
playButtonTxt.visible = true;
highScoreTxt.visible = true;
characterButtonTxt.visible = true;
characterDisplayTxt.visible = true;
highScoreTxt.setText('Best: ' + bestScore);
characterDisplayTxt.setText(currentCharacter + '/' + totalCharacters);
// Reset positions for re-entrance
titleTxt.y = -100;
titleTxt.alpha = 0;
playButtonTxt.x = -200;
playButtonTxt.alpha = 0;
highScoreTxt.x = GAME_WIDTH + 200;
highScoreTxt.alpha = 0;
characterButtonTxt.alpha = 0;
characterDisplayTxt.alpha = 0;
// Re-animate entrance
tween(titleTxt, {
y: GAME_HEIGHT / 2 - 300,
alpha: 1
}, {
duration: 1000,
easing: tween.bounceOut
});
LK.setTimeout(function () {
tween(playButtonTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 300);
LK.setTimeout(function () {
tween(highScoreTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 600);
LK.setTimeout(function () {
tween(characterButtonTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
tween(characterDisplayTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 800);
instructionTxt.visible = false;
gameOverTxt.visible = false;
restartTxt.visible = false;
scoreTxt.visible = false;
}
// Touch/click handlers
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if clicked on character button
var characterButtonBounds = {
x: characterButtonTxt.x - 100,
y: characterButtonTxt.y - 30,
width: 200,
height: 60
};
var characterDisplayBounds = {
x: characterDisplayTxt.x - 50,
y: characterDisplayTxt.y - 30,
width: 100,
height: 60
};
// Check if clicked on play button
var playButtonBounds = {
x: playButtonTxt.x - 120,
y: playButtonTxt.y - 30,
width: 240,
height: 60
};
if (x >= characterButtonBounds.x && x <= characterButtonBounds.x + characterButtonBounds.width && y >= characterButtonBounds.y && y <= characterButtonBounds.y + characterButtonBounds.height || x >= characterDisplayBounds.x && x <= characterDisplayBounds.x + characterDisplayBounds.width && y >= characterDisplayBounds.y && y <= characterDisplayBounds.y + characterDisplayBounds.height) {
// Change character
currentCharacter = currentCharacter % totalCharacters + 1;
storage.selectedCharacter = currentCharacter;
characterDisplayTxt.setText(currentCharacter + '/' + totalCharacters);
// Recreate bird with new character
bird.destroy();
bird = game.addChild(new Bird(currentCharacter));
bird.x = GAME_WIDTH / 4;
bird.y = GAME_HEIGHT / 2;
bird.visible = false;
// Add selection animation
tween(characterDisplayTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
onFinish: function onFinish() {
tween(characterDisplayTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150
});
}
});
} else if (x >= playButtonBounds.x && x <= playButtonBounds.x + playButtonBounds.width && y >= playButtonBounds.y && y <= playButtonBounds.y + playButtonBounds.height) {
// Start game only if play button is clicked
startGame();
return; // Exit immediately to prevent flap
}
} else if (gameState === 'playing' && !gameOver) {
bird.flap();
} else if (gameState === 'gameOver') {
resetGame();
}
};
// Main game loop
game.update = function () {
if (!gameStarted || gameOver) return;
// Spawn pipes
pipeSpawnTimer++;
if (pipeSpawnTimer >= pipeSpawnInterval) {
spawnPipe();
pipeSpawnTimer = 0;
}
// Update pipes
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
// Check for scoring - use pipe center for more accurate scoring
if (!pipe.scored && pipe.x < bird.x - 60) {
pipe.scored = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('score').play();
// Dynamic difficulty - make game harder as score increases
var currentScore = LK.getScore();
if (currentScore > 0) {
// Gradually decrease gap size (minimum 450 for much easier gameplay)
gapSize = Math.max(450, baseGapSize - currentScore * 2);
// Gradually decrease spawn interval (minimum 120 frames for much better spacing)
pipeSpawnInterval = Math.max(120, basePipeInterval - currentScore * 1);
}
}
// Check collision with bird - use more accurate bird bounds
var birdBounds = {
x: bird.x - 40,
y: bird.y - 30,
width: 80,
height: 60
};
var topPipeBounds = {
x: pipe.x - 55,
y: 0,
width: 110,
height: pipe.topPipeBody.y - 10
};
var bottomPipeBounds = {
x: pipe.x - 55,
y: pipe.bottomPipeBody.y + 10,
width: 110,
height: GAME_HEIGHT - pipe.bottomPipeBody.y - 10
};
// Check collision
if (birdBounds.x < topPipeBounds.x + topPipeBounds.width && birdBounds.x + birdBounds.width > topPipeBounds.x && birdBounds.y < topPipeBounds.y + topPipeBounds.height && birdBounds.y + birdBounds.height > topPipeBounds.y || birdBounds.x < bottomPipeBounds.x + bottomPipeBounds.width && birdBounds.x + birdBounds.width > bottomPipeBounds.x && birdBounds.y < bottomPipeBounds.y + bottomPipeBounds.height && birdBounds.y + birdBounds.height > bottomPipeBounds.y) {
gameOver = true;
gameState = 'gameOver';
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Show game over UI
gameOverTxt.visible = true;
restartTxt.visible = true;
scoreTxt.visible = false;
return;
}
// Remove off-screen pipes
if (pipe.x < -200) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Check ground collision - updated for new ground position with better bounds
if (bird.y + 30 >= ground.y) {
gameOver = true;
gameState = 'gameOver';
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Show game over UI
gameOverTxt.visible = true;
restartTxt.visible = true;
scoreTxt.visible = false;
return;
}
// Check ceiling collision with better bounds
if (bird.y - 30 <= 0) {
gameOver = true;
gameState = 'gameOver';
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Show game over UI
gameOverTxt.visible = true;
restartTxt.visible = true;
scoreTxt.visible = false;
return;
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bird = Container.expand(function (characterIndex) {
var self = Container.call(this);
var assetName = characterAssets[characterIndex - 1] || 'bird';
var birdGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.6;
self.flapPower = -16;
self.maxVelocity = 15;
self.minVelocity = -15;
self.flap = function () {
self.velocity = self.flapPower;
LK.getSound('flap').play();
// Add slight rotation animation
tween(birdGraphics, {
rotation: -0.3
}, {
duration: 100
});
tween(birdGraphics, {
rotation: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(birdGraphics, {
rotation: 0
}, {
duration: 300
});
}
});
};
self.update = function () {
// Apply gravity
self.velocity += self.gravity;
// Limit velocity
if (self.velocity > self.maxVelocity) self.velocity = self.maxVelocity;
if (self.velocity < self.minVelocity) self.velocity = self.minVelocity;
// Update position
self.y += self.velocity;
// Rotate bird based on velocity
var targetRotation = Math.max(-0.5, Math.min(0.8, self.velocity * 0.05));
birdGraphics.rotation = targetRotation;
};
return self;
});
var Pipe = Container.expand(function () {
var self = Container.call(this);
self.speed = -4;
self.scored = false;
// Create top pipe body
self.topPipeBody = self.attachAsset('pipeBody', {
anchorX: 0.5,
anchorY: 1
});
// Create top pipe cap
self.topPipeCap = self.attachAsset('pipeTop', {
anchorX: 0.5,
anchorY: 1
});
// Create bottom pipe body
self.bottomPipeBody = self.attachAsset('pipeBody', {
anchorX: 0.5,
anchorY: 0
});
// Create bottom pipe cap
self.bottomPipeCap = self.attachAsset('pipeTop', {
anchorX: 0.5,
anchorY: 0
});
self.setGap = function (centerY, gapSize) {
self.topPipeBody.y = centerY - gapSize / 2;
self.topPipeCap.y = centerY - gapSize / 2;
self.bottomPipeBody.y = centerY + gapSize / 2;
self.bottomPipeCap.y = centerY + gapSize / 2;
};
self.update = function () {
self.x += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bird;
var pipes = [];
var ground;
var gameStarted = false;
var gameOver = false;
var gameState = 'menu'; // 'menu', 'playing', 'gameOver'
var pipeSpawnTimer = 0;
var pipeSpawnInterval = 150; // frames - increased for more spacing
var gapSize = 500; // Much larger gap size for easier gameplay
var baseGapSize = 500;
var basePipeInterval = 150;
var scrollSpeed = 4;
// Character selection
var currentCharacter = storage.selectedCharacter || 1;
var characterAssets = ['bird', 'bird2', 'bird3', 'bird4'];
var totalCharacters = 4;
// Increased game dimensions
var GAME_WIDTH = 1800; // Increased from 1536
var GAME_HEIGHT = 2400; // Increased from 2048
// UI Elements
var scoreTxt = new Text2('0', {
size: 70,
// Slightly smaller for mobile
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Menu elements
var titleTxt = new Text2('FLAPPY WINGS', {
size: 80,
fill: 0xFFD700
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.x = GAME_WIDTH / 2;
titleTxt.y = -100; // Start above screen
titleTxt.alpha = 0; // Start invisible
game.addChild(titleTxt);
// Animate title entrance
tween(titleTxt, {
y: GAME_HEIGHT / 2 - 300,
alpha: 1
}, {
duration: 1000,
easing: tween.bounceOut
});
var playButtonTxt = new Text2('TAP TO PLAY', {
size: 50,
fill: 0xFFFFFF
});
playButtonTxt.anchor.set(0.5, 0.5);
playButtonTxt.x = -200; // Start off screen left
playButtonTxt.y = GAME_HEIGHT / 2 - 100;
playButtonTxt.alpha = 0; // Start invisible
game.addChild(playButtonTxt);
// Animate play button entrance with delay
LK.setTimeout(function () {
tween(playButtonTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Start pulsing animation after entrance
function pulsePlayButton() {
tween(playButtonTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButtonTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: pulsePlayButton
});
}
});
}
pulsePlayButton();
}
});
}, 500);
var highScoreTxt = new Text2('Best: 0', {
size: 40,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0.5, 0.5);
highScoreTxt.x = GAME_WIDTH + 200; // Start off screen right
highScoreTxt.y = GAME_HEIGHT / 2 + 50;
highScoreTxt.alpha = 0; // Start invisible
game.addChild(highScoreTxt);
// Animate high score entrance with delay
LK.setTimeout(function () {
tween(highScoreTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 800);
// Animate character button entrance with delay
LK.setTimeout(function () {
tween(characterButtonTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
tween(characterDisplayTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 1000);
var characterButtonTxt = new Text2('CHARACTER', {
size: 45,
fill: 0xFFD700
});
characterButtonTxt.anchor.set(0.5, 0.5);
characterButtonTxt.x = GAME_WIDTH / 2;
characterButtonTxt.y = GAME_HEIGHT / 2 + 100;
characterButtonTxt.alpha = 0;
game.addChild(characterButtonTxt);
// Character display
var characterDisplayTxt = new Text2('1/4', {
size: 35,
fill: 0xFFFFFF
});
characterDisplayTxt.anchor.set(0.5, 0.5);
characterDisplayTxt.x = GAME_WIDTH / 2;
characterDisplayTxt.y = GAME_HEIGHT / 2 + 150;
characterDisplayTxt.alpha = 0;
game.addChild(characterDisplayTxt);
var instructionTxt = new Text2('TAP TO FLAP', {
size: 45,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
instructionTxt.x = GAME_WIDTH / 2;
instructionTxt.y = GAME_HEIGHT / 2 + 150;
instructionTxt.visible = false;
game.addChild(instructionTxt);
// Game over elements
var gameOverTxt = new Text2('GAME OVER', {
size: 70,
fill: 0xFF0000
});
gameOverTxt.anchor.set(0.5, 0.5);
gameOverTxt.x = GAME_WIDTH / 2;
gameOverTxt.y = GAME_HEIGHT / 2 - 100;
gameOverTxt.visible = false;
game.addChild(gameOverTxt);
var restartTxt = new Text2('TAP TO RESTART', {
size: 45,
fill: 0xFFFFFF
});
restartTxt.anchor.set(0.5, 0.5);
restartTxt.x = GAME_WIDTH / 2;
restartTxt.y = GAME_HEIGHT / 2 + 50;
restartTxt.visible = false;
game.addChild(restartTxt);
// Initialize bird
bird = game.addChild(new Bird(currentCharacter));
bird.x = GAME_WIDTH / 3.5;
bird.y = GAME_HEIGHT / 2;
bird.visible = false; // Hide until game starts
// Create layered ground
var dirt = game.addChild(LK.getAsset('dirt', {
anchorX: 0,
anchorY: 1
}));
dirt.x = 0;
dirt.y = GAME_HEIGHT;
dirt.width = GAME_WIDTH;
ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 1
}));
ground.x = 0;
ground.y = GAME_HEIGHT - 140;
ground.width = GAME_WIDTH; // Scale to larger width
// Add grass details
var grassTufts = [];
for (var g = 0; g < GAME_WIDTH / 60; g++) {
var grass = game.addChild(LK.getAsset('grass', {
anchorX: 0.5,
anchorY: 1
}));
grass.x = g * 60 + Math.random() * 40;
grass.y = GAME_HEIGHT - 140;
grass.height = 15 + Math.random() * 10;
grassTufts.push(grass);
}
// Function to spawn pipe
function spawnPipe() {
var pipe = new Pipe();
// Better gap positioning - ensure minimum distance from top and bottom
var minY = gapSize / 2 + 150; // Minimum distance from top
var maxY = GAME_HEIGHT - ground.height - gapSize / 2 - 150; // Maximum distance from bottom
var centerY = minY + Math.random() * (maxY - minY);
pipe.setGap(centerY, gapSize);
pipe.x = GAME_WIDTH + 100;
pipes.push(pipe);
game.addChild(pipe);
}
// Function to start game
function startGame() {
if (gameState === 'menu') {
gameState = 'playing';
gameStarted = true;
// Reset bird velocity to prevent death
bird.velocity = 0;
bird.y = GAME_HEIGHT / 2;
// Hide menu elements
titleTxt.visible = false;
playButtonTxt.visible = false;
highScoreTxt.visible = false;
characterButtonTxt.visible = false;
characterDisplayTxt.visible = false;
// Show game elements
bird.visible = true;
instructionTxt.visible = true;
scoreTxt.visible = true;
// Hide instruction after a delay
LK.setTimeout(function () {
instructionTxt.visible = false;
}, 2000);
}
}
// Function to reset game
function resetGame() {
// Clean up pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset bird
bird.destroy();
bird = game.addChild(new Bird(currentCharacter));
bird.x = GAME_WIDTH / 4;
bird.y = GAME_HEIGHT / 2;
bird.velocity = 0;
bird.visible = false;
// Update high score
var currentScore = LK.getScore();
var bestScore = storage.highScore || 0;
if (currentScore > bestScore) {
storage.highScore = currentScore;
bestScore = currentScore;
}
// Reset game state
gameState = 'menu';
gameStarted = false;
gameOver = false;
pipeSpawnTimer = 0;
LK.setScore(0);
scoreTxt.setText('0');
// Show menu elements with animations
titleTxt.visible = true;
playButtonTxt.visible = true;
highScoreTxt.visible = true;
characterButtonTxt.visible = true;
characterDisplayTxt.visible = true;
highScoreTxt.setText('Best: ' + bestScore);
characterDisplayTxt.setText(currentCharacter + '/' + totalCharacters);
// Reset positions for re-entrance
titleTxt.y = -100;
titleTxt.alpha = 0;
playButtonTxt.x = -200;
playButtonTxt.alpha = 0;
highScoreTxt.x = GAME_WIDTH + 200;
highScoreTxt.alpha = 0;
characterButtonTxt.alpha = 0;
characterDisplayTxt.alpha = 0;
// Re-animate entrance
tween(titleTxt, {
y: GAME_HEIGHT / 2 - 300,
alpha: 1
}, {
duration: 1000,
easing: tween.bounceOut
});
LK.setTimeout(function () {
tween(playButtonTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 300);
LK.setTimeout(function () {
tween(highScoreTxt, {
x: GAME_WIDTH / 2,
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 600);
LK.setTimeout(function () {
tween(characterButtonTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
tween(characterDisplayTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.elasticOut
});
}, 800);
instructionTxt.visible = false;
gameOverTxt.visible = false;
restartTxt.visible = false;
scoreTxt.visible = false;
}
// Touch/click handlers
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if clicked on character button
var characterButtonBounds = {
x: characterButtonTxt.x - 100,
y: characterButtonTxt.y - 30,
width: 200,
height: 60
};
var characterDisplayBounds = {
x: characterDisplayTxt.x - 50,
y: characterDisplayTxt.y - 30,
width: 100,
height: 60
};
// Check if clicked on play button
var playButtonBounds = {
x: playButtonTxt.x - 120,
y: playButtonTxt.y - 30,
width: 240,
height: 60
};
if (x >= characterButtonBounds.x && x <= characterButtonBounds.x + characterButtonBounds.width && y >= characterButtonBounds.y && y <= characterButtonBounds.y + characterButtonBounds.height || x >= characterDisplayBounds.x && x <= characterDisplayBounds.x + characterDisplayBounds.width && y >= characterDisplayBounds.y && y <= characterDisplayBounds.y + characterDisplayBounds.height) {
// Change character
currentCharacter = currentCharacter % totalCharacters + 1;
storage.selectedCharacter = currentCharacter;
characterDisplayTxt.setText(currentCharacter + '/' + totalCharacters);
// Recreate bird with new character
bird.destroy();
bird = game.addChild(new Bird(currentCharacter));
bird.x = GAME_WIDTH / 4;
bird.y = GAME_HEIGHT / 2;
bird.visible = false;
// Add selection animation
tween(characterDisplayTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
onFinish: function onFinish() {
tween(characterDisplayTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150
});
}
});
} else if (x >= playButtonBounds.x && x <= playButtonBounds.x + playButtonBounds.width && y >= playButtonBounds.y && y <= playButtonBounds.y + playButtonBounds.height) {
// Start game only if play button is clicked
startGame();
return; // Exit immediately to prevent flap
}
} else if (gameState === 'playing' && !gameOver) {
bird.flap();
} else if (gameState === 'gameOver') {
resetGame();
}
};
// Main game loop
game.update = function () {
if (!gameStarted || gameOver) return;
// Spawn pipes
pipeSpawnTimer++;
if (pipeSpawnTimer >= pipeSpawnInterval) {
spawnPipe();
pipeSpawnTimer = 0;
}
// Update pipes
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
// Check for scoring - use pipe center for more accurate scoring
if (!pipe.scored && pipe.x < bird.x - 60) {
pipe.scored = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('score').play();
// Dynamic difficulty - make game harder as score increases
var currentScore = LK.getScore();
if (currentScore > 0) {
// Gradually decrease gap size (minimum 450 for much easier gameplay)
gapSize = Math.max(450, baseGapSize - currentScore * 2);
// Gradually decrease spawn interval (minimum 120 frames for much better spacing)
pipeSpawnInterval = Math.max(120, basePipeInterval - currentScore * 1);
}
}
// Check collision with bird - use more accurate bird bounds
var birdBounds = {
x: bird.x - 40,
y: bird.y - 30,
width: 80,
height: 60
};
var topPipeBounds = {
x: pipe.x - 55,
y: 0,
width: 110,
height: pipe.topPipeBody.y - 10
};
var bottomPipeBounds = {
x: pipe.x - 55,
y: pipe.bottomPipeBody.y + 10,
width: 110,
height: GAME_HEIGHT - pipe.bottomPipeBody.y - 10
};
// Check collision
if (birdBounds.x < topPipeBounds.x + topPipeBounds.width && birdBounds.x + birdBounds.width > topPipeBounds.x && birdBounds.y < topPipeBounds.y + topPipeBounds.height && birdBounds.y + birdBounds.height > topPipeBounds.y || birdBounds.x < bottomPipeBounds.x + bottomPipeBounds.width && birdBounds.x + birdBounds.width > bottomPipeBounds.x && birdBounds.y < bottomPipeBounds.y + bottomPipeBounds.height && birdBounds.y + birdBounds.height > bottomPipeBounds.y) {
gameOver = true;
gameState = 'gameOver';
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Show game over UI
gameOverTxt.visible = true;
restartTxt.visible = true;
scoreTxt.visible = false;
return;
}
// Remove off-screen pipes
if (pipe.x < -200) {
pipe.destroy();
pipes.splice(i, 1);
}
}
// Check ground collision - updated for new ground position with better bounds
if (bird.y + 30 >= ground.y) {
gameOver = true;
gameState = 'gameOver';
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Show game over UI
gameOverTxt.visible = true;
restartTxt.visible = true;
scoreTxt.visible = false;
return;
}
// Check ceiling collision with better bounds
if (bird.y - 30 <= 0) {
gameOver = true;
gameState = 'gameOver';
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Show game over UI
gameOverTxt.visible = true;
restartTxt.visible = true;
scoreTxt.visible = false;
return;
}
};