/**** * 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;
}
};