/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Bird = Container.expand(function () { var self = Container.call(this); var birdGraphic = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); self.velocity = 0; self.gravity = 0.5; self.jumpForce = -20; self.flying = false; self.onWire = true; self.currentNest = null; self.isJumpAllowed = true; self.jump = function () { // Only jump if allowed (when on a nest) if (!self.isJumpAllowed) return; // All jumps handled the same way - no special first jump anymore if (self.onWire) { self.onWire = false; // Bird leaves wire } // Normal jump physics for all jumps self.velocity = self.jumpForce; self.flying = true; // Mark game as started on first jump if (!game.gameStarted) { game.gameStarted = true; // Store safe landing height to prevent immediate game over game.safeJumpHeight = self.y + 300; // Room for falling before game over // Start continuous scrolling game.isScrolling = true; } // Disable jumping until landing on a nest again self.isJumpAllowed = false; // Create stars effect self.createStars(); // Play flying sound LK.getSound('fly').play(); }; self.createStars = function () { for (var i = 0; i < 5; i++) { var star = LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: self.x + (Math.random() * 60 - 30), y: self.y + (Math.random() * 60 - 30), alpha: 1 }); game.addChild(star); // Animate the star tween(star, { alpha: 0, scaleX: 0.2, scaleY: 0.2, x: star.x + (Math.random() * 40 - 20), y: star.y + (Math.random() * 40 - 20) }, { duration: 500, onFinish: function onFinish() { game.removeChild(star); } }); } }; self.update = function () { if (!self.onWire) { // Always apply gravity when bird is flying (both first jump and subsequent jumps) if (self.flying) { self.velocity += self.gravity; self.y += self.velocity; // Check if falling too fast if (self.velocity > 20) { self.velocity = 20; } } } else if (self.currentNest) { // Bird follows the nest self.x = self.currentNest.x; if (self.currentNest.y !== undefined) { self.y = self.currentNest.y - 30; } } else if (self.onWire) { // Safety check: if bird is somehow on wire but has no nest // Find closest nest or place in the center of screen // Don't immediately set flying to true to prevent immediate fall if (self.currentNest === null) { // Try to find the nearest nest var nearestNest = null; var minDistance = Infinity; for (var i = 0; i < nests.length; i++) { var distance = Math.abs(nests[i].x - self.x); if (distance < minDistance) { minDistance = distance; nearestNest = nests[i]; } } if (nearestNest) { self.currentNest = nearestNest; nearestNest.occupied = true; } else { self.onWire = false; self.flying = true; self.velocity = 0; } } } }; self.down = function (x, y, obj) { self.jump(); }; return self; }); var Nest = Container.expand(function () { var self = Container.call(this); var nestGraphic = self.attachAsset('nest', { anchorX: 0.5, anchorY: 0.5 }); self.occupied = false; self.lastX = undefined; self.speed = 4; // Speed for horizontal movement self.direction = 1; // Direction for horizontal movement // Randomize initial direction self.randomizeDirection = function () { self.direction = Math.random() > 0.5 ? 1 : -1; }; // Update method to handle movement and collisions self.update = function () { // Track last position for reference if (self.lastX === undefined) self.lastX = self.x; // Move nest horizontally self.x += self.speed * self.direction; // Check for wall collisions and reverse direction var wallWidth = 30; // Width of the wall assets var nestWidth = 100; // Width of nest if (self.x <= wallWidth + nestWidth / 2) { self.direction = 1; // Move right when hitting left wall self.x = wallWidth + nestWidth / 2; } else if (self.x >= 2048 - wallWidth - nestWidth / 2) { self.direction = -1; // Move left when hitting right wall self.x = 2048 - wallWidth - nestWidth / 2; } // Update last position self.lastX = self.x; }; // Initialize random direction self.randomizeDirection(); return self; }); var PowerLine = Container.expand(function () { var self = Container.call(this); var lineGraphic = self.attachAsset('powerLine', { anchorX: 0, anchorY: 0.5 }); // Power lines are static - no update method needed return self; }); var Star = Container.expand(function () { var self = Container.call(this); var starGraphic = self.attachAsset('star', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ var score = 0; var highScore = storage.highScore || 0; var powerLines = []; var nests = []; var bird; var cameraY = 0; var gameOver = false; var lineSpacing = 300; var maxLines = 10; var nextLineY = 2500; var ascentCount = 0; var lastNestLevel = null; var removedBottomLines = 0; // Initialize score display var scoreTxt = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0, 0); LK.gui.topRight.addChild(scoreTxt); scoreTxt.x = -scoreTxt.width - 20; scoreTxt.y = 20; // Initialize high score display var highScoreTxt = new Text2('Best: ' + highScore, { size: 40, fill: 0xFFFFFF }); highScoreTxt.anchor.set(0, 0); LK.gui.topRight.addChild(highScoreTxt); highScoreTxt.x = -highScoreTxt.width - 20; highScoreTxt.y = 100; // Create background var background = LK.getAsset('background', { anchorX: 0, anchorY: 0 }); game.addChild(background); // Initialize bird function initBird() { bird = new Bird(); bird.x = 2048 / 2; bird.y = 2500; game.addChild(bird); // Bird will be positioned on a nest in initGame } // Create a power line with nests function createPowerLine(y) { var powerLine = new PowerLine(); powerLine.y = y; powerLine.initialY = y; // Store initial Y position // Set power line to span the whole width, respecting wall boundaries powerLine.x = 30; // Starting from left wall position game.addChild(powerLine); powerLines.push(powerLine); // Calculate nest properties once - more efficient var nestCount = Math.floor(Math.random() * 3) + 1; // Ensure at least one nest on the bottommost power line if (y === 2500) { nestCount = Math.max(nestCount, 2); // Guarantee at least two nests on bottom line for better gameplay } var nestWidth = 100; var wallWidth = 30; // Width of the wall assets var availableWidth = 2048 - nestWidth - wallWidth * 2; // Account for walls // Pre-calculate nest positions for better performance var nestPositions = []; for (var i = 0; i < nestCount; i++) { // If this is the bottom line, ensure at least one nest is near the center var nestX = y === 2500 && i === 0 ? 2048 / 2 // Center the first nest on bottom line : wallWidth + nestWidth / 2 + Math.random() * (availableWidth - nestWidth); nestPositions.push({ x: nestX, y: y - 5 }); } // Add nests in a small timeout to prevent UI blocking LK.setTimeout(function () { // Place nests randomly along the power line for (var i = 0; i < nestCount; i++) { var nest = new Nest(); // Random position along the power line nest.x = nestPositions[i].x; nest.y = nestPositions[i].y; // Slightly above the line nest.initialY = y - 5; // Store initial Y position game.addChild(nest); nests.push(nest); } }, 0); // Even a 0ms timeout helps by deferring to next event loop return powerLine; } // Initialize power lines function initPowerLines() { // Clear existing lines and nests - optimize by checking length first if (powerLines.length > 0) { for (var i = 0; i < powerLines.length; i++) { game.removeChild(powerLines[i]); } } if (nests.length > 0) { for (var i = 0; i < nests.length; i++) { game.removeChild(nests[i]); } } powerLines = []; nests = []; // Create initial power lines - create in batches for better performance nextLineY = 2500; // Create lines in smaller batches to prevent blocking UI var linesBatch = 3; var currentLine = 0; function createLineBatch() { var batchEnd = Math.min(currentLine + linesBatch, maxLines); for (var i = currentLine; i < batchEnd; i++) { createPowerLine(nextLineY); nextLineY -= lineSpacing; } currentLine = batchEnd; if (currentLine < maxLines) { LK.setTimeout(createLineBatch, 1); } } createLineBatch(); } // Initialize the game function initGame() { score = 0; gameOver = false; game.gameStarted = false; // Flag to track if game has started (first jump) game.isFirstJump = false; // No longer needed as we use tween for first jump game.birdHighestPosition = null; // Track bird's highest (lowest y value) position game.safeJumpHeight = null; // Will be set on first jump ascentCount = 0; lastNestLevel = null; removedBottomLines = 0; updateScore(0); // Set canvas size at the beginning only cameraY = 0; // Reset camera position game.cameraOffsetY = 0; // Smoothed camera offset for rendering game.cameraTargetY = 0; // Target camera position game.cameraSmoothness = 0.1; // How quickly camera follows (0-1) game.scrollSpeed = 1; // Base scroll speed game.jumpCount = 0; // Count of successful jumps game.isScrolling = false; // Whether continuous scrolling is active // Defer heavy initialization with a small timeout to allow the UI to render first LK.setTimeout(function () { initPowerLines(); initBird(); // Create walls at screen edges createWalls(); // Give time for nests to be created, then place bird LK.setTimeout(function () { // Find a nest on the bottommost line and place bird on it var bottomNest = null; var bottomY = 0; // First identify the bottommost line by finding the nest with largest Y value for (var i = 0; i < nests.length; i++) { if (nests[i].y > bottomY) { bottomY = nests[i].y; bottomNest = nests[i]; } } // If we found a bottom nest, place bird on it if (bottomNest) { bird.currentNest = bottomNest; bird.x = bottomNest.x; bird.y = bottomNest.y - 30; bird.isJumpAllowed = true; bird.onWire = true; bottomNest.occupied = true; } else { // Ensure bird is properly placed on the bottommost power line if no nest was found var bottomLine = null; var maxY = -Infinity; for (var i = 0; i < powerLines.length; i++) { if (powerLines[i].initialY > maxY) { maxY = powerLines[i].initialY; bottomLine = powerLines[i]; } } if (bottomLine) { // Create a nest on the bottom line var nest = new Nest(); nest.x = 2048 / 2; // Center of screen nest.y = bottomLine.y - 5; // Slightly above the line nest.initialY = bottomLine.initialY - 5; game.addChild(nest); nests.push(nest); // Place bird on this nest bird.currentNest = nest; bird.x = nest.x; bird.y = nest.y - 30; bird.isJumpAllowed = true; bird.onWire = true; nest.occupied = true; } } // Play background music LK.playMusic('gameMusic'); }, 100); // A bit more delay to ensure nests are created }, 50); // Short delay to let the UI render first } // Update score display function updateScore(newScore) { score = newScore; scoreTxt.setText('Score: ' + score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('Best: ' + highScore); } } // Check if bird is on a nest function checkNestCollision() { if (bird.velocity > 0) { // Only check when bird is falling for (var i = 0; i < nests.length; i++) { var nest = nests[i]; if (!nest.occupied && bird.intersects(nest)) { bird.velocity = 0; bird.onWire = true; bird.currentNest = nest; nest.occupied = true; // Re-enable jumping now that bird has landed bird.isJumpAllowed = true; // Update score based on height var newScore = Math.floor((2732 - nest.y) / 100); updateScore(newScore); // Check for successful ascent to a higher wire var currentLevel = Math.floor(nest.initialY / lineSpacing); if (lastNestLevel !== null && currentLevel < lastNestLevel) { ascentCount++; // Increment jump count and increase difficulty every 10 jumps game.jumpCount++; if (game.jumpCount % 10 === 0) { // Increase scroll speed game.scrollSpeed += 0.2; // Flash the screen to indicate difficulty increase LK.effects.flashScreen(0x00ff00, 300); } // Every 3 successful ascents, remove the bottommost wire if (ascentCount % 3 === 0) { removeBottomWire(); } } lastNestLevel = currentLevel; // Play landing sound LK.getSound('land').play(); return true; } } } return false; } // Move camera based on continuous scrolling after first jump function updateCamera() { // Only handle bird position on wire if (bird.onWire && bird.currentNest) { bird.y = bird.currentNest.y - 30; } // Implement continuous scrolling after the game has started if (game.isScrolling) { // Continuously increase camera target position based on scrollSpeed game.cameraTargetY += game.scrollSpeed; // Smoothly move the camera using linear interpolation (lerp) game.cameraOffsetY += (game.cameraTargetY - game.cameraOffsetY) * game.cameraSmoothness; // Apply the camera offset to all game objects that need to move with the camera for (var i = 0; i < powerLines.length; i++) { powerLines[i].y = powerLines[i].initialY + game.cameraOffsetY; } for (var i = 0; i < nests.length; i++) { nests[i].y = nests[i].initialY + game.cameraOffsetY; } // Update background and walls position if they exist var children = game.children; for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.initialY !== undefined && child !== bird) { child.y = child.initialY + game.cameraOffsetY; } } // Update cameraY for game logic (line generation, etc.) cameraY = game.cameraOffsetY; } } // Generate new lines if needed function checkGenerateLines() { if (nextLineY > cameraY - 2732) { createPowerLine(nextLineY); nextLineY -= lineSpacing; } } // Create walls at screen edges function createWalls() { // Prepare wall properties before asset loading var wallProps = { anchorX: 0, anchorY: 0 }; // Using setTimeout to create a small delay before accessing assets LK.setTimeout(function () { var leftWall = LK.getAsset('wall', { anchorX: 0, anchorY: 0 }); leftWall.x = 0; leftWall.y = 0; leftWall.initialY = 0; // Store initial Y position game.addChild(leftWall); var rightWall = LK.getAsset('wall', { anchorX: 0, anchorY: 0 }); rightWall.x = 2048 - rightWall.width; rightWall.y = 0; rightWall.initialY = 0; // Store initial Y position game.addChild(rightWall); }, 5); // Small delay to prevent blocking UI } // Function to remove the bottommost wire and its nests function removeBottomWire() { // Find the bottommost wire (highest y value) var bottomLineIndex = -1; var bottomY = -Infinity; for (var i = 0; i < powerLines.length; i++) { if (powerLines[i].initialY > bottomY) { bottomY = powerLines[i].initialY; bottomLineIndex = i; } } if (bottomLineIndex >= 0) { // Remove the wire game.removeChild(powerLines[bottomLineIndex]); var removedLine = powerLines.splice(bottomLineIndex, 1)[0]; // Remove all nests on this wire for (var i = nests.length - 1; i >= 0; i--) { // Check if the nest is on the removed wire (approximately same initialY) if (Math.abs(nests[i].initialY - (removedLine.initialY - 5)) < 10) { // If bird is on this nest, it will fall if (bird.currentNest === nests[i]) { bird.currentNest = null; bird.onWire = false; bird.isJumpAllowed = false; bird.velocity = 1; // Start falling } game.removeChild(nests[i]); nests.splice(i, 1); } } removedBottomLines++; } } // Remove off-screen power lines and nests function cleanupObjects() { for (var i = powerLines.length - 1; i >= 0; i--) { if (powerLines[i].y > cameraY + 3000) { game.removeChild(powerLines[i]); powerLines.splice(i, 1); } } for (var i = nests.length - 1; i >= 0; i--) { if (nests[i].y > cameraY + 3000) { game.removeChild(nests[i]); nests.splice(i, 1); } } } // Handle game over function handleGameOver() { // Don't check for game over if game hasn't started yet if (!game.gameStarted) return; // Game over condition - bird falls off bottom of screen // With continuous scrolling, we need to check if bird is too far below the camera view var screenBottom = 2732; var visibleBottom = screenBottom + game.cameraOffsetY; var isInGracePeriod = game.gameStarted && LK.ticks < 120; if (!gameOver && !isInGracePeriod && bird.y > visibleBottom) { gameOver = true; LK.getSound('fall').play(); LK.showGameOver(); } } // Main game loop game.update = function () { if (gameOver) return; // Update bird if it exists if (bird) { // Double-check bird is defined before calling update if (typeof bird.update === 'function') { bird.update(); } } // Power lines are static, no need to update them // Update nests for (var i = 0; i < nests.length; i++) { nests[i].update(); } // Check if bird landed on a nest and bird exists if (bird) { checkNestCollision(); // Update camera position updateCamera(); } // Generate new lines if needed checkGenerateLines(); // Remove off-screen objects cleanupObjects(); // Check game over condition if (bird) { handleGameOver(); } }; // Bird jump on screen tap/click game.down = function (x, y, obj) { if (!gameOver && bird && typeof bird.jump === 'function') { bird.jump(); } }; // Show loading indicator var loadingText = new Text2('Loading...', { size: 80, fill: 0xFFFFFF }); loadingText.anchor.set(0.5, 0.5); loadingText.x = 2048 / 2; loadingText.y = 2732 / 2; game.addChild(loadingText); // Create a preloader to load game assets efficiently function startGameWithPreloader() { // Use tween to animate loading text while assets are prepared tween(loadingText, { alpha: 0.5 }, { duration: 500, easing: tween.easeInOutQuad, loop: true, yoyo: true }); // Defer game initialization to prevent startup lag LK.setTimeout(function () { // Remove loading indicator tween(loadingText, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { game.removeChild(loadingText); // Initialize game initGame(); } }); }, 200); // Give enough time for engine to initialize } // Start game with preloader startGameWithPreloader();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphic = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 0.5;
self.jumpForce = -20;
self.flying = false;
self.onWire = true;
self.currentNest = null;
self.isJumpAllowed = true;
self.jump = function () {
// Only jump if allowed (when on a nest)
if (!self.isJumpAllowed) return;
// All jumps handled the same way - no special first jump anymore
if (self.onWire) {
self.onWire = false; // Bird leaves wire
}
// Normal jump physics for all jumps
self.velocity = self.jumpForce;
self.flying = true;
// Mark game as started on first jump
if (!game.gameStarted) {
game.gameStarted = true;
// Store safe landing height to prevent immediate game over
game.safeJumpHeight = self.y + 300; // Room for falling before game over
// Start continuous scrolling
game.isScrolling = true;
}
// Disable jumping until landing on a nest again
self.isJumpAllowed = false;
// Create stars effect
self.createStars();
// Play flying sound
LK.getSound('fly').play();
};
self.createStars = function () {
for (var i = 0; i < 5; i++) {
var star = LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + (Math.random() * 60 - 30),
y: self.y + (Math.random() * 60 - 30),
alpha: 1
});
game.addChild(star);
// Animate the star
tween(star, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
x: star.x + (Math.random() * 40 - 20),
y: star.y + (Math.random() * 40 - 20)
}, {
duration: 500,
onFinish: function onFinish() {
game.removeChild(star);
}
});
}
};
self.update = function () {
if (!self.onWire) {
// Always apply gravity when bird is flying (both first jump and subsequent jumps)
if (self.flying) {
self.velocity += self.gravity;
self.y += self.velocity;
// Check if falling too fast
if (self.velocity > 20) {
self.velocity = 20;
}
}
} else if (self.currentNest) {
// Bird follows the nest
self.x = self.currentNest.x;
if (self.currentNest.y !== undefined) {
self.y = self.currentNest.y - 30;
}
} else if (self.onWire) {
// Safety check: if bird is somehow on wire but has no nest
// Find closest nest or place in the center of screen
// Don't immediately set flying to true to prevent immediate fall
if (self.currentNest === null) {
// Try to find the nearest nest
var nearestNest = null;
var minDistance = Infinity;
for (var i = 0; i < nests.length; i++) {
var distance = Math.abs(nests[i].x - self.x);
if (distance < minDistance) {
minDistance = distance;
nearestNest = nests[i];
}
}
if (nearestNest) {
self.currentNest = nearestNest;
nearestNest.occupied = true;
} else {
self.onWire = false;
self.flying = true;
self.velocity = 0;
}
}
}
};
self.down = function (x, y, obj) {
self.jump();
};
return self;
});
var Nest = Container.expand(function () {
var self = Container.call(this);
var nestGraphic = self.attachAsset('nest', {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = false;
self.lastX = undefined;
self.speed = 4; // Speed for horizontal movement
self.direction = 1; // Direction for horizontal movement
// Randomize initial direction
self.randomizeDirection = function () {
self.direction = Math.random() > 0.5 ? 1 : -1;
};
// Update method to handle movement and collisions
self.update = function () {
// Track last position for reference
if (self.lastX === undefined) self.lastX = self.x;
// Move nest horizontally
self.x += self.speed * self.direction;
// Check for wall collisions and reverse direction
var wallWidth = 30; // Width of the wall assets
var nestWidth = 100; // Width of nest
if (self.x <= wallWidth + nestWidth / 2) {
self.direction = 1; // Move right when hitting left wall
self.x = wallWidth + nestWidth / 2;
} else if (self.x >= 2048 - wallWidth - nestWidth / 2) {
self.direction = -1; // Move left when hitting right wall
self.x = 2048 - wallWidth - nestWidth / 2;
}
// Update last position
self.lastX = self.x;
};
// Initialize random direction
self.randomizeDirection();
return self;
});
var PowerLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphic = self.attachAsset('powerLine', {
anchorX: 0,
anchorY: 0.5
});
// Power lines are static - no update method needed
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var starGraphic = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
var score = 0;
var highScore = storage.highScore || 0;
var powerLines = [];
var nests = [];
var bird;
var cameraY = 0;
var gameOver = false;
var lineSpacing = 300;
var maxLines = 10;
var nextLineY = 2500;
var ascentCount = 0;
var lastNestLevel = null;
var removedBottomLines = 0;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -scoreTxt.width - 20;
scoreTxt.y = 20;
// Initialize high score display
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 40,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 100;
// Create background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Initialize bird
function initBird() {
bird = new Bird();
bird.x = 2048 / 2;
bird.y = 2500;
game.addChild(bird);
// Bird will be positioned on a nest in initGame
}
// Create a power line with nests
function createPowerLine(y) {
var powerLine = new PowerLine();
powerLine.y = y;
powerLine.initialY = y; // Store initial Y position
// Set power line to span the whole width, respecting wall boundaries
powerLine.x = 30; // Starting from left wall position
game.addChild(powerLine);
powerLines.push(powerLine);
// Calculate nest properties once - more efficient
var nestCount = Math.floor(Math.random() * 3) + 1;
// Ensure at least one nest on the bottommost power line
if (y === 2500) {
nestCount = Math.max(nestCount, 2); // Guarantee at least two nests on bottom line for better gameplay
}
var nestWidth = 100;
var wallWidth = 30; // Width of the wall assets
var availableWidth = 2048 - nestWidth - wallWidth * 2; // Account for walls
// Pre-calculate nest positions for better performance
var nestPositions = [];
for (var i = 0; i < nestCount; i++) {
// If this is the bottom line, ensure at least one nest is near the center
var nestX = y === 2500 && i === 0 ? 2048 / 2 // Center the first nest on bottom line
: wallWidth + nestWidth / 2 + Math.random() * (availableWidth - nestWidth);
nestPositions.push({
x: nestX,
y: y - 5
});
}
// Add nests in a small timeout to prevent UI blocking
LK.setTimeout(function () {
// Place nests randomly along the power line
for (var i = 0; i < nestCount; i++) {
var nest = new Nest();
// Random position along the power line
nest.x = nestPositions[i].x;
nest.y = nestPositions[i].y; // Slightly above the line
nest.initialY = y - 5; // Store initial Y position
game.addChild(nest);
nests.push(nest);
}
}, 0); // Even a 0ms timeout helps by deferring to next event loop
return powerLine;
}
// Initialize power lines
function initPowerLines() {
// Clear existing lines and nests - optimize by checking length first
if (powerLines.length > 0) {
for (var i = 0; i < powerLines.length; i++) {
game.removeChild(powerLines[i]);
}
}
if (nests.length > 0) {
for (var i = 0; i < nests.length; i++) {
game.removeChild(nests[i]);
}
}
powerLines = [];
nests = [];
// Create initial power lines - create in batches for better performance
nextLineY = 2500;
// Create lines in smaller batches to prevent blocking UI
var linesBatch = 3;
var currentLine = 0;
function createLineBatch() {
var batchEnd = Math.min(currentLine + linesBatch, maxLines);
for (var i = currentLine; i < batchEnd; i++) {
createPowerLine(nextLineY);
nextLineY -= lineSpacing;
}
currentLine = batchEnd;
if (currentLine < maxLines) {
LK.setTimeout(createLineBatch, 1);
}
}
createLineBatch();
}
// Initialize the game
function initGame() {
score = 0;
gameOver = false;
game.gameStarted = false; // Flag to track if game has started (first jump)
game.isFirstJump = false; // No longer needed as we use tween for first jump
game.birdHighestPosition = null; // Track bird's highest (lowest y value) position
game.safeJumpHeight = null; // Will be set on first jump
ascentCount = 0;
lastNestLevel = null;
removedBottomLines = 0;
updateScore(0);
// Set canvas size at the beginning only
cameraY = 0; // Reset camera position
game.cameraOffsetY = 0; // Smoothed camera offset for rendering
game.cameraTargetY = 0; // Target camera position
game.cameraSmoothness = 0.1; // How quickly camera follows (0-1)
game.scrollSpeed = 1; // Base scroll speed
game.jumpCount = 0; // Count of successful jumps
game.isScrolling = false; // Whether continuous scrolling is active
// Defer heavy initialization with a small timeout to allow the UI to render first
LK.setTimeout(function () {
initPowerLines();
initBird();
// Create walls at screen edges
createWalls();
// Give time for nests to be created, then place bird
LK.setTimeout(function () {
// Find a nest on the bottommost line and place bird on it
var bottomNest = null;
var bottomY = 0;
// First identify the bottommost line by finding the nest with largest Y value
for (var i = 0; i < nests.length; i++) {
if (nests[i].y > bottomY) {
bottomY = nests[i].y;
bottomNest = nests[i];
}
}
// If we found a bottom nest, place bird on it
if (bottomNest) {
bird.currentNest = bottomNest;
bird.x = bottomNest.x;
bird.y = bottomNest.y - 30;
bird.isJumpAllowed = true;
bird.onWire = true;
bottomNest.occupied = true;
} else {
// Ensure bird is properly placed on the bottommost power line if no nest was found
var bottomLine = null;
var maxY = -Infinity;
for (var i = 0; i < powerLines.length; i++) {
if (powerLines[i].initialY > maxY) {
maxY = powerLines[i].initialY;
bottomLine = powerLines[i];
}
}
if (bottomLine) {
// Create a nest on the bottom line
var nest = new Nest();
nest.x = 2048 / 2; // Center of screen
nest.y = bottomLine.y - 5; // Slightly above the line
nest.initialY = bottomLine.initialY - 5;
game.addChild(nest);
nests.push(nest);
// Place bird on this nest
bird.currentNest = nest;
bird.x = nest.x;
bird.y = nest.y - 30;
bird.isJumpAllowed = true;
bird.onWire = true;
nest.occupied = true;
}
}
// Play background music
LK.playMusic('gameMusic');
}, 100); // A bit more delay to ensure nests are created
}, 50); // Short delay to let the UI render first
}
// Update score display
function updateScore(newScore) {
score = newScore;
scoreTxt.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Check if bird is on a nest
function checkNestCollision() {
if (bird.velocity > 0) {
// Only check when bird is falling
for (var i = 0; i < nests.length; i++) {
var nest = nests[i];
if (!nest.occupied && bird.intersects(nest)) {
bird.velocity = 0;
bird.onWire = true;
bird.currentNest = nest;
nest.occupied = true;
// Re-enable jumping now that bird has landed
bird.isJumpAllowed = true;
// Update score based on height
var newScore = Math.floor((2732 - nest.y) / 100);
updateScore(newScore);
// Check for successful ascent to a higher wire
var currentLevel = Math.floor(nest.initialY / lineSpacing);
if (lastNestLevel !== null && currentLevel < lastNestLevel) {
ascentCount++;
// Increment jump count and increase difficulty every 10 jumps
game.jumpCount++;
if (game.jumpCount % 10 === 0) {
// Increase scroll speed
game.scrollSpeed += 0.2;
// Flash the screen to indicate difficulty increase
LK.effects.flashScreen(0x00ff00, 300);
}
// Every 3 successful ascents, remove the bottommost wire
if (ascentCount % 3 === 0) {
removeBottomWire();
}
}
lastNestLevel = currentLevel;
// Play landing sound
LK.getSound('land').play();
return true;
}
}
}
return false;
}
// Move camera based on continuous scrolling after first jump
function updateCamera() {
// Only handle bird position on wire
if (bird.onWire && bird.currentNest) {
bird.y = bird.currentNest.y - 30;
}
// Implement continuous scrolling after the game has started
if (game.isScrolling) {
// Continuously increase camera target position based on scrollSpeed
game.cameraTargetY += game.scrollSpeed;
// Smoothly move the camera using linear interpolation (lerp)
game.cameraOffsetY += (game.cameraTargetY - game.cameraOffsetY) * game.cameraSmoothness;
// Apply the camera offset to all game objects that need to move with the camera
for (var i = 0; i < powerLines.length; i++) {
powerLines[i].y = powerLines[i].initialY + game.cameraOffsetY;
}
for (var i = 0; i < nests.length; i++) {
nests[i].y = nests[i].initialY + game.cameraOffsetY;
}
// Update background and walls position if they exist
var children = game.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.initialY !== undefined && child !== bird) {
child.y = child.initialY + game.cameraOffsetY;
}
}
// Update cameraY for game logic (line generation, etc.)
cameraY = game.cameraOffsetY;
}
}
// Generate new lines if needed
function checkGenerateLines() {
if (nextLineY > cameraY - 2732) {
createPowerLine(nextLineY);
nextLineY -= lineSpacing;
}
}
// Create walls at screen edges
function createWalls() {
// Prepare wall properties before asset loading
var wallProps = {
anchorX: 0,
anchorY: 0
};
// Using setTimeout to create a small delay before accessing assets
LK.setTimeout(function () {
var leftWall = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0
});
leftWall.x = 0;
leftWall.y = 0;
leftWall.initialY = 0; // Store initial Y position
game.addChild(leftWall);
var rightWall = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0
});
rightWall.x = 2048 - rightWall.width;
rightWall.y = 0;
rightWall.initialY = 0; // Store initial Y position
game.addChild(rightWall);
}, 5); // Small delay to prevent blocking UI
}
// Function to remove the bottommost wire and its nests
function removeBottomWire() {
// Find the bottommost wire (highest y value)
var bottomLineIndex = -1;
var bottomY = -Infinity;
for (var i = 0; i < powerLines.length; i++) {
if (powerLines[i].initialY > bottomY) {
bottomY = powerLines[i].initialY;
bottomLineIndex = i;
}
}
if (bottomLineIndex >= 0) {
// Remove the wire
game.removeChild(powerLines[bottomLineIndex]);
var removedLine = powerLines.splice(bottomLineIndex, 1)[0];
// Remove all nests on this wire
for (var i = nests.length - 1; i >= 0; i--) {
// Check if the nest is on the removed wire (approximately same initialY)
if (Math.abs(nests[i].initialY - (removedLine.initialY - 5)) < 10) {
// If bird is on this nest, it will fall
if (bird.currentNest === nests[i]) {
bird.currentNest = null;
bird.onWire = false;
bird.isJumpAllowed = false;
bird.velocity = 1; // Start falling
}
game.removeChild(nests[i]);
nests.splice(i, 1);
}
}
removedBottomLines++;
}
}
// Remove off-screen power lines and nests
function cleanupObjects() {
for (var i = powerLines.length - 1; i >= 0; i--) {
if (powerLines[i].y > cameraY + 3000) {
game.removeChild(powerLines[i]);
powerLines.splice(i, 1);
}
}
for (var i = nests.length - 1; i >= 0; i--) {
if (nests[i].y > cameraY + 3000) {
game.removeChild(nests[i]);
nests.splice(i, 1);
}
}
}
// Handle game over
function handleGameOver() {
// Don't check for game over if game hasn't started yet
if (!game.gameStarted) return;
// Game over condition - bird falls off bottom of screen
// With continuous scrolling, we need to check if bird is too far below the camera view
var screenBottom = 2732;
var visibleBottom = screenBottom + game.cameraOffsetY;
var isInGracePeriod = game.gameStarted && LK.ticks < 120;
if (!gameOver && !isInGracePeriod && bird.y > visibleBottom) {
gameOver = true;
LK.getSound('fall').play();
LK.showGameOver();
}
}
// Main game loop
game.update = function () {
if (gameOver) return;
// Update bird if it exists
if (bird) {
// Double-check bird is defined before calling update
if (typeof bird.update === 'function') {
bird.update();
}
}
// Power lines are static, no need to update them
// Update nests
for (var i = 0; i < nests.length; i++) {
nests[i].update();
}
// Check if bird landed on a nest and bird exists
if (bird) {
checkNestCollision();
// Update camera position
updateCamera();
}
// Generate new lines if needed
checkGenerateLines();
// Remove off-screen objects
cleanupObjects();
// Check game over condition
if (bird) {
handleGameOver();
}
};
// Bird jump on screen tap/click
game.down = function (x, y, obj) {
if (!gameOver && bird && typeof bird.jump === 'function') {
bird.jump();
}
};
// Show loading indicator
var loadingText = new Text2('Loading...', {
size: 80,
fill: 0xFFFFFF
});
loadingText.anchor.set(0.5, 0.5);
loadingText.x = 2048 / 2;
loadingText.y = 2732 / 2;
game.addChild(loadingText);
// Create a preloader to load game assets efficiently
function startGameWithPreloader() {
// Use tween to animate loading text while assets are prepared
tween(loadingText, {
alpha: 0.5
}, {
duration: 500,
easing: tween.easeInOutQuad,
loop: true,
yoyo: true
});
// Defer game initialization to prevent startup lag
LK.setTimeout(function () {
// Remove loading indicator
tween(loadingText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(loadingText);
// Initialize game
initGame();
}
});
}, 200); // Give enough time for engine to initialize
}
// Start game with preloader
startGameWithPreloader();