/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Bird class var Bird = Container.expand(function () { var self = Container.call(this); // Attach bird asset (ellipse, yellow) var birdAsset = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); // Set up bird asset properties birdAsset.width = 120; birdAsset.height = 120; birdAsset.color = 0xffe066; birdAsset.shape = 'ellipse'; // Bird skin logic self.currentSkin = typeof storage.selectedSkin === "string" ? storage.selectedSkin : "default"; self.setSkin = function (skinId) { // Remove old asset if (self.children.length > 0) { self.removeChild(self.children[0]); } // Pick asset for skin var assetId = "bird"; if (skinId === "bird2") { assetId = "bird2"; } else if (skinId === "bird3") { assetId = "bird3"; } else if (skinId === "sapsik") { assetId = "sapsik"; } var asset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); asset.width = 120; asset.height = 120; self.currentSkin = skinId; }; self.setSkin(self.currentSkin); // Bird physics self.velocityY = 0; self.gravity = 1.1; // Gravity per frame (slightly faster than before) self.flapStrength = -26; // Negative for upward movement (higher jump) // Bird update method (called every tick) self.update = function () { if (!gameStarted) { // Don't update position or velocity if game hasn't started return; } self.velocityY += self.gravity; self.y += self.velocityY; // Clamp bird to not go above the screen if (self.y < self.height / 2) { self.y = self.height / 2; self.velocityY = 0; } }; // Flap method self.flap = function () { self.velocityY = self.flapStrength; }; return self; }); // Coin class var Coin = Container.expand(function () { var self = Container.call(this); // Attach coin asset (ellipse, gold) var coinAsset = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100, color: 0xffd700, shape: 'ellipse', x: 0, y: 0 }); self.collected = false; // Coin update: move left at pipe speed self.update = function (speed) { self.x -= speed; }; return self; }); // PipePair class (top and bottom pipes as a pair) var PipePair = Container.expand(function () { var self = Container.call(this); // Pipe properties self.pipeWidth = 220; self.gapHeight = 520; // Vertical gap between pipes self.speed = 16; // Speed at which pipes move left (increased for faster gameplay) // Randomize gap position var minGapY = 400; var maxGapY = 2732 - 400 - self.gapHeight; self.gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY + 1)); // Top pipe self.topPipe = self.attachAsset('pipeTop', { anchorX: 0.5, anchorY: 1.0, width: self.pipeWidth, height: self.gapY, color: 0x4ec04e, shape: 'box', x: 0, y: self.gapY }); // Bottom pipe self.bottomPipe = self.attachAsset('pipeBottom', { anchorX: 0.5, anchorY: 0.0, width: self.pipeWidth, height: 2732 - (self.gapY + self.gapHeight), color: 0x4ec04e, shape: 'box', x: 0, y: self.gapY + self.gapHeight }); // Scoring flag (to ensure only one score per pipe pair) self.scored = false; // Add coin between pipes self.coin = new Coin(); // Place coin in the center of the gap self.coin.x = 0; self.coin.y = self.gapY + self.gapHeight / 2; self.addChild(self.coin); // Update method self.update = function () { self.x -= self.speed; // Coin does not move independently; it stays fixed between the pipes }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Not visible, pixel sky covers all }); /**** * Game Code ****/ // Game constants // Tween plugin for animations (optional, not used in MVP but included for future use) var GROUND_HEIGHT = 220; var PIPE_INTERVAL = 70; // Frames between pipes (faster spawn rate) var BIRD_START_X = 600; var BIRD_START_Y = 1200; // Game state variables var bird; var pipes = []; var score = 0; var scoreTxt; var ground; var gameStarted = false; var gameOver = false; var ticksSinceLastPipe = 0; // Add pixel sky background (full screen, behind everything) var skyBg = LK.getAsset('pixelSky', { anchorX: 0, anchorY: 0, width: 2048, height: 2732, x: 0, y: 0 }); game.addChild(skyBg); // Add ground (simple green box at bottom) ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, width: 2048, height: GROUND_HEIGHT, color: 0x3b7a2a, shape: 'box', x: 0, y: 2732 - GROUND_HEIGHT }); game.addChild(ground); // Add bird bird = new Bird(); bird.x = BIRD_START_X; bird.y = BIRD_START_Y; game.addChild(bird); // Add score text to GUI scoreTxt = new Text2('0', { size: 180, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Add start button to center of screen var startBtn = new Text2('START', { size: 220, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); startBtn.anchor.set(0.5, 0.5); startBtn.x = 2048 / 2; startBtn.y = 2732 / 2 + 300; // Move the button further down to avoid accidental game over game.addChild(startBtn); // Start button tap handler startBtn.down = function (x, y, obj) { if (!gameStarted && !gameOver) { startGame(); startBtn.visible = false; } }; // Show/hide start button based on game state function showStartButton() { if (typeof startBtn !== "undefined") startBtn.visible = true; } function hideStartButton() { if (typeof startBtn !== "undefined") startBtn.visible = false; } // Add money icon and money counter to the right side of the screen var moneyIcon = LK.getAsset('coin', { anchorX: 1, anchorY: 0, width: 100, height: 100, x: 0, y: 0 }); var moneyAmount = 0; var moneyTxt = new Text2('0', { size: 120, fill: 0xffd700 }); moneyTxt.anchor.set(1, 0); // Position icon and text at the top right, with some margin from the edge moneyIcon.x = -40; moneyIcon.y = 40; moneyTxt.x = -160; moneyTxt.y = 60; LK.gui.topRight.addChild(moneyIcon); LK.gui.topRight.addChild(moneyTxt); // Add shop button to top right (below money) var shopBtn = new Text2('SHOP', { size: 100, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); shopBtn.anchor.set(1, 0); shopBtn.x = -40; shopBtn.y = 160; LK.gui.topRight.addChild(shopBtn); // High score tracking var highScore = typeof storage.highScore === "number" ? storage.highScore : 0; var highScoreTxt = new Text2('Best: ' + highScore, { size: 100, fill: 0xffffff }); highScoreTxt.anchor.set(1, 1); // Position at bottom right with margin highScoreTxt.x = -40; highScoreTxt.y = -40; LK.gui.bottomRight.addChild(highScoreTxt); // --- Shop Menu UI and Logic --- var shopMenu = new Container(); shopMenu.visible = false; game.addChild(shopMenu); // Shop background var shopBg = LK.getAsset('ground', { anchorX: 0.5, anchorY: 0.5, width: 900, height: 1200, color: 0x222222, shape: 'box', x: 2048 / 2, y: 2732 / 2 }); shopMenu.addChild(shopBg); // Shop title var shopTitle = new Text2('SKIN SHOP', { size: 140, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); shopTitle.anchor.set(0.5, 0); shopTitle.x = 2048 / 2; shopTitle.y = 2732 / 2 - 500; shopMenu.addChild(shopTitle); // Skins data (id, label, price, assetId) var skins = [{ id: "default", label: "Classic", price: 0, asset: "bird" }, { id: "bird2", label: "Bird 2", price: 10, asset: "bird2" }, { id: "bird3", label: "Bird 3", price: 15, asset: "bird3" }, { id: "sapsik", label: "Sapsik", price: 50, asset: "sapsik" }]; // Load owned skins from storage or default to only "default" var ownedSkins = storage.ownedSkins && Array.isArray(storage.ownedSkins) ? storage.ownedSkins : ["default"]; var selectedSkin = typeof storage.selectedSkin === "string" ? storage.selectedSkin : "default"; // Helper to update skin buttons function updateSkinButtons() { for (var i = 0; i < skinButtons.length; i++) { var btn = skinButtons[i]; var skin = skins[i]; if (ownedSkins.indexOf(skin.id) !== -1) { btn.setText(skin.label + (selectedSkin === skin.id ? " (Selected)" : "")); } else { btn.setText(skin.label + " (" + skin.price + "💰)"); } } } // Create skin buttons var skinButtons = []; for (var i = 0; i < skins.length; i++) { (function (idx) { var skin = skins[idx]; var btn = new Text2(skin.label, { size: 100, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); btn.anchor.set(0.5, 0); btn.x = 2048 / 2; btn.y = 2732 / 2 - 250 + idx * 220; btn.down = function (x, y, obj) { // If already owned, select it if (ownedSkins.indexOf(skin.id) !== -1) { selectedSkin = skin.id; // Persist selectedSkin to storage storage.selectedSkin = selectedSkin; updateSkinButtons(); // Update bird skin if in game if (typeof bird !== "undefined" && bird.setSkin) bird.setSkin(selectedSkin); } else if (moneyAmount >= skin.price) { // Buy skin moneyAmount -= skin.price; storage.moneyAmount = moneyAmount; moneyTxt.setText(moneyAmount + ''); ownedSkins.push(skin.id); // Persist ownedSkins to storage storage.ownedSkins = ownedSkins; // Persist selectedSkin to storage selectedSkin = skin.id; storage.selectedSkin = selectedSkin; updateSkinButtons(); if (typeof bird !== "undefined" && bird.setSkin) bird.setSkin(selectedSkin); } }; shopMenu.addChild(btn); skinButtons.push(btn); })(i); } // Close button (cross in top right) var closeShopCross = new Text2('✕', { size: 120, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); closeShopCross.anchor.set(1, 0); closeShopCross.x = 2048 / 2 + 450 - 30; // right edge of shopBg minus margin closeShopCross.y = 2732 / 2 - 600 + 30; // top edge of shopBg plus margin closeShopCross.down = function (x, y, obj) { shopMenu.visible = false; }; shopMenu.addChild(closeShopCross); // (Old close button removed) // Shop button handler shopBtn.down = function (x, y, obj) { updateSkinButtons(); shopMenu.visible = true; }; // Helper: Reset game state function resetGame() { // Remove all pipes for (var i = 0; i < pipes.length; i++) { pipes[i].destroy(); } pipes = []; score = 0; scoreTxt.setText('0'); moneyAmount = typeof storage.moneyAmount === "number" ? storage.moneyAmount : 0; if (typeof moneyTxt !== "undefined") moneyTxt.setText(moneyAmount + ''); // Update high score from storage in case it changed externally if (typeof storage.highScore === "number") { highScore = storage.highScore; if (typeof highScoreTxt !== "undefined") { highScoreTxt.setText('Best: ' + highScore); } } // Restore games won from storage and update UI gamesWon = typeof storage.gamesWon === "number" ? storage.gamesWon : 0; if (typeof gamesWonTxt !== "undefined") { gamesWonTxt.setText('Wins: ' + gamesWon); } bird.x = BIRD_START_X; bird.y = BIRD_START_Y; bird.velocityY = 0; // Restore ownedSkins and selectedSkin from storage ownedSkins = storage.ownedSkins && Array.isArray(storage.ownedSkins) ? storage.ownedSkins : ["default"]; selectedSkin = typeof storage.selectedSkin === "string" ? storage.selectedSkin : "default"; // Restore bird skin if (typeof bird.setSkin === "function") { var skinToSet = selectedSkin; bird.setSkin(skinToSet); } gameStarted = false; gameOver = false; ticksSinceLastPipe = 0; showStartButton(); } // Helper: Start game (first flap) function startGame() { if (!gameStarted && !gameOver) { gameStarted = true; hideStartButton(); } } // Helper: End game function endGame() { gameOver = true; // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreTxt !== "undefined") { highScoreTxt.setText('Best: ' + highScore); } } LK.effects.flashScreen(0xff0000, 600); hideStartButton(); LK.showGameOver(); } // Game tap/flap handler game.down = function (x, y, obj) { if (gameOver) return; if (!gameStarted) { startGame(); } bird.flap(); }; // --- Games Won Counter UI --- var gamesWon = typeof storage.gamesWon === "number" ? storage.gamesWon : 0; var gamesWonTxt = new Text2('Wins: ' + gamesWon, { size: 100, fill: 0x00ffcc }); gamesWonTxt.anchor.set(0, 1); gamesWonTxt.x = 40; gamesWonTxt.y = -40; LK.gui.bottomLeft.addChild(gamesWonTxt); // Main game update loop game.update = function () { if (gameOver) return; // Only update bird and pipes if game started if (gameStarted) { bird.update(); // Add new pipes at interval ticksSinceLastPipe++; if (ticksSinceLastPipe >= PIPE_INTERVAL) { ticksSinceLastPipe = 0; var pipePair = new PipePair(); pipePair.x = 2048 + pipePair.pipeWidth / 2; pipePair.y = 0; pipes.push(pipePair); game.addChild(pipePair); } // Update pipes and check for collisions for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; pipe.update(); // Remove pipes that have gone off screen if (pipe.x < -pipe.pipeWidth / 2) { pipe.destroy(); pipes.splice(i, 1); continue; } // Collision detection (bird with pipes) // Use .intersects for both top and bottom pipes if (bird.intersects(pipe.topPipe) || bird.intersects(pipe.bottomPipe)) { endGame(); return; } // Coin collection if (pipe.coin && !pipe.coin.collected && bird.intersects(pipe.coin)) { pipe.coin.collected = true; pipe.coin.visible = false; score += 5; // Award 5 points for coin scoreTxt.setText(score + ''); moneyAmount += 1; moneyTxt.setText(moneyAmount + ''); // Persist moneyAmount to storage storage.moneyAmount = moneyAmount; } // Scoring: if bird passes the center of the pipe and hasn't scored yet if (!pipe.scored && pipe.x + pipe.pipeWidth / 2 < bird.x - bird.width / 2) { pipe.scored = true; score += 1; scoreTxt.setText(score + ''); } } // --- Win Condition: 500 points --- if (score >= 500 && !gameOver) { // Only trigger win once per game gameOver = true; // Increment games won and persist gamesWon = typeof storage.gamesWon === "number" ? storage.gamesWon : 0; gamesWon += 1; storage.gamesWon = gamesWon; if (typeof gamesWonTxt !== "undefined") { gamesWonTxt.setText('Wins: ' + gamesWon); } LK.effects.flashScreen(0x00ffcc, 1000); hideStartButton(); LK.showYouWin(); return; } // Collision with ground if (bird.y + bird.height / 2 >= 2732 - GROUND_HEIGHT) { bird.y = 2732 - GROUND_HEIGHT - bird.height / 2; endGame(); return; } } }; // Reset game on game over (handled by LK automatically, but for clarity) game.onGameOver = function () { resetGame(); }; // Initial reset resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach bird asset (ellipse, yellow)
var birdAsset = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Set up bird asset properties
birdAsset.width = 120;
birdAsset.height = 120;
birdAsset.color = 0xffe066;
birdAsset.shape = 'ellipse';
// Bird skin logic
self.currentSkin = typeof storage.selectedSkin === "string" ? storage.selectedSkin : "default";
self.setSkin = function (skinId) {
// Remove old asset
if (self.children.length > 0) {
self.removeChild(self.children[0]);
}
// Pick asset for skin
var assetId = "bird";
if (skinId === "bird2") {
assetId = "bird2";
} else if (skinId === "bird3") {
assetId = "bird3";
} else if (skinId === "sapsik") {
assetId = "sapsik";
}
var asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
asset.width = 120;
asset.height = 120;
self.currentSkin = skinId;
};
self.setSkin(self.currentSkin);
// Bird physics
self.velocityY = 0;
self.gravity = 1.1; // Gravity per frame (slightly faster than before)
self.flapStrength = -26; // Negative for upward movement (higher jump)
// Bird update method (called every tick)
self.update = function () {
if (!gameStarted) {
// Don't update position or velocity if game hasn't started
return;
}
self.velocityY += self.gravity;
self.y += self.velocityY;
// Clamp bird to not go above the screen
if (self.y < self.height / 2) {
self.y = self.height / 2;
self.velocityY = 0;
}
};
// Flap method
self.flap = function () {
self.velocityY = self.flapStrength;
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
// Attach coin asset (ellipse, gold)
var coinAsset = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100,
color: 0xffd700,
shape: 'ellipse',
x: 0,
y: 0
});
self.collected = false;
// Coin update: move left at pipe speed
self.update = function (speed) {
self.x -= speed;
};
return self;
});
// PipePair class (top and bottom pipes as a pair)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe properties
self.pipeWidth = 220;
self.gapHeight = 520; // Vertical gap between pipes
self.speed = 16; // Speed at which pipes move left (increased for faster gameplay)
// Randomize gap position
var minGapY = 400;
var maxGapY = 2732 - 400 - self.gapHeight;
self.gapY = minGapY + Math.floor(Math.random() * (maxGapY - minGapY + 1));
// Top pipe
self.topPipe = self.attachAsset('pipeTop', {
anchorX: 0.5,
anchorY: 1.0,
width: self.pipeWidth,
height: self.gapY,
color: 0x4ec04e,
shape: 'box',
x: 0,
y: self.gapY
});
// Bottom pipe
self.bottomPipe = self.attachAsset('pipeBottom', {
anchorX: 0.5,
anchorY: 0.0,
width: self.pipeWidth,
height: 2732 - (self.gapY + self.gapHeight),
color: 0x4ec04e,
shape: 'box',
x: 0,
y: self.gapY + self.gapHeight
});
// Scoring flag (to ensure only one score per pipe pair)
self.scored = false;
// Add coin between pipes
self.coin = new Coin();
// Place coin in the center of the gap
self.coin.x = 0;
self.coin.y = self.gapY + self.gapHeight / 2;
self.addChild(self.coin);
// Update method
self.update = function () {
self.x -= self.speed;
// Coin does not move independently; it stays fixed between the pipes
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Not visible, pixel sky covers all
});
/****
* Game Code
****/
// Game constants
// Tween plugin for animations (optional, not used in MVP but included for future use)
var GROUND_HEIGHT = 220;
var PIPE_INTERVAL = 70; // Frames between pipes (faster spawn rate)
var BIRD_START_X = 600;
var BIRD_START_Y = 1200;
// Game state variables
var bird;
var pipes = [];
var score = 0;
var scoreTxt;
var ground;
var gameStarted = false;
var gameOver = false;
var ticksSinceLastPipe = 0;
// Add pixel sky background (full screen, behind everything)
var skyBg = LK.getAsset('pixelSky', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
x: 0,
y: 0
});
game.addChild(skyBg);
// Add ground (simple green box at bottom)
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: GROUND_HEIGHT,
color: 0x3b7a2a,
shape: 'box',
x: 0,
y: 2732 - GROUND_HEIGHT
});
game.addChild(ground);
// Add bird
bird = new Bird();
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
game.addChild(bird);
// Add score text to GUI
scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Add start button to center of screen
var startBtn = new Text2('START', {
size: 220,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = 2048 / 2;
startBtn.y = 2732 / 2 + 300; // Move the button further down to avoid accidental game over
game.addChild(startBtn);
// Start button tap handler
startBtn.down = function (x, y, obj) {
if (!gameStarted && !gameOver) {
startGame();
startBtn.visible = false;
}
};
// Show/hide start button based on game state
function showStartButton() {
if (typeof startBtn !== "undefined") startBtn.visible = true;
}
function hideStartButton() {
if (typeof startBtn !== "undefined") startBtn.visible = false;
}
// Add money icon and money counter to the right side of the screen
var moneyIcon = LK.getAsset('coin', {
anchorX: 1,
anchorY: 0,
width: 100,
height: 100,
x: 0,
y: 0
});
var moneyAmount = 0;
var moneyTxt = new Text2('0', {
size: 120,
fill: 0xffd700
});
moneyTxt.anchor.set(1, 0);
// Position icon and text at the top right, with some margin from the edge
moneyIcon.x = -40;
moneyIcon.y = 40;
moneyTxt.x = -160;
moneyTxt.y = 60;
LK.gui.topRight.addChild(moneyIcon);
LK.gui.topRight.addChild(moneyTxt);
// Add shop button to top right (below money)
var shopBtn = new Text2('SHOP', {
size: 100,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
shopBtn.anchor.set(1, 0);
shopBtn.x = -40;
shopBtn.y = 160;
LK.gui.topRight.addChild(shopBtn);
// High score tracking
var highScore = typeof storage.highScore === "number" ? storage.highScore : 0;
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 100,
fill: 0xffffff
});
highScoreTxt.anchor.set(1, 1);
// Position at bottom right with margin
highScoreTxt.x = -40;
highScoreTxt.y = -40;
LK.gui.bottomRight.addChild(highScoreTxt);
// --- Shop Menu UI and Logic ---
var shopMenu = new Container();
shopMenu.visible = false;
game.addChild(shopMenu);
// Shop background
var shopBg = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0.5,
width: 900,
height: 1200,
color: 0x222222,
shape: 'box',
x: 2048 / 2,
y: 2732 / 2
});
shopMenu.addChild(shopBg);
// Shop title
var shopTitle = new Text2('SKIN SHOP', {
size: 140,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = 2048 / 2;
shopTitle.y = 2732 / 2 - 500;
shopMenu.addChild(shopTitle);
// Skins data (id, label, price, assetId)
var skins = [{
id: "default",
label: "Classic",
price: 0,
asset: "bird"
}, {
id: "bird2",
label: "Bird 2",
price: 10,
asset: "bird2"
}, {
id: "bird3",
label: "Bird 3",
price: 15,
asset: "bird3"
}, {
id: "sapsik",
label: "Sapsik",
price: 50,
asset: "sapsik"
}];
// Load owned skins from storage or default to only "default"
var ownedSkins = storage.ownedSkins && Array.isArray(storage.ownedSkins) ? storage.ownedSkins : ["default"];
var selectedSkin = typeof storage.selectedSkin === "string" ? storage.selectedSkin : "default";
// Helper to update skin buttons
function updateSkinButtons() {
for (var i = 0; i < skinButtons.length; i++) {
var btn = skinButtons[i];
var skin = skins[i];
if (ownedSkins.indexOf(skin.id) !== -1) {
btn.setText(skin.label + (selectedSkin === skin.id ? " (Selected)" : ""));
} else {
btn.setText(skin.label + " (" + skin.price + "💰)");
}
}
}
// Create skin buttons
var skinButtons = [];
for (var i = 0; i < skins.length; i++) {
(function (idx) {
var skin = skins[idx];
var btn = new Text2(skin.label, {
size: 100,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
btn.anchor.set(0.5, 0);
btn.x = 2048 / 2;
btn.y = 2732 / 2 - 250 + idx * 220;
btn.down = function (x, y, obj) {
// If already owned, select it
if (ownedSkins.indexOf(skin.id) !== -1) {
selectedSkin = skin.id;
// Persist selectedSkin to storage
storage.selectedSkin = selectedSkin;
updateSkinButtons();
// Update bird skin if in game
if (typeof bird !== "undefined" && bird.setSkin) bird.setSkin(selectedSkin);
} else if (moneyAmount >= skin.price) {
// Buy skin
moneyAmount -= skin.price;
storage.moneyAmount = moneyAmount;
moneyTxt.setText(moneyAmount + '');
ownedSkins.push(skin.id);
// Persist ownedSkins to storage
storage.ownedSkins = ownedSkins;
// Persist selectedSkin to storage
selectedSkin = skin.id;
storage.selectedSkin = selectedSkin;
updateSkinButtons();
if (typeof bird !== "undefined" && bird.setSkin) bird.setSkin(selectedSkin);
}
};
shopMenu.addChild(btn);
skinButtons.push(btn);
})(i);
}
// Close button (cross in top right)
var closeShopCross = new Text2('✕', {
size: 120,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
closeShopCross.anchor.set(1, 0);
closeShopCross.x = 2048 / 2 + 450 - 30; // right edge of shopBg minus margin
closeShopCross.y = 2732 / 2 - 600 + 30; // top edge of shopBg plus margin
closeShopCross.down = function (x, y, obj) {
shopMenu.visible = false;
};
shopMenu.addChild(closeShopCross);
// (Old close button removed)
// Shop button handler
shopBtn.down = function (x, y, obj) {
updateSkinButtons();
shopMenu.visible = true;
};
// Helper: Reset game state
function resetGame() {
// Remove all pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
score = 0;
scoreTxt.setText('0');
moneyAmount = typeof storage.moneyAmount === "number" ? storage.moneyAmount : 0;
if (typeof moneyTxt !== "undefined") moneyTxt.setText(moneyAmount + '');
// Update high score from storage in case it changed externally
if (typeof storage.highScore === "number") {
highScore = storage.highScore;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText('Best: ' + highScore);
}
}
// Restore games won from storage and update UI
gamesWon = typeof storage.gamesWon === "number" ? storage.gamesWon : 0;
if (typeof gamesWonTxt !== "undefined") {
gamesWonTxt.setText('Wins: ' + gamesWon);
}
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
bird.velocityY = 0;
// Restore ownedSkins and selectedSkin from storage
ownedSkins = storage.ownedSkins && Array.isArray(storage.ownedSkins) ? storage.ownedSkins : ["default"];
selectedSkin = typeof storage.selectedSkin === "string" ? storage.selectedSkin : "default";
// Restore bird skin
if (typeof bird.setSkin === "function") {
var skinToSet = selectedSkin;
bird.setSkin(skinToSet);
}
gameStarted = false;
gameOver = false;
ticksSinceLastPipe = 0;
showStartButton();
}
// Helper: Start game (first flap)
function startGame() {
if (!gameStarted && !gameOver) {
gameStarted = true;
hideStartButton();
}
}
// Helper: End game
function endGame() {
gameOver = true;
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText('Best: ' + highScore);
}
}
LK.effects.flashScreen(0xff0000, 600);
hideStartButton();
LK.showGameOver();
}
// Game tap/flap handler
game.down = function (x, y, obj) {
if (gameOver) return;
if (!gameStarted) {
startGame();
}
bird.flap();
};
// --- Games Won Counter UI ---
var gamesWon = typeof storage.gamesWon === "number" ? storage.gamesWon : 0;
var gamesWonTxt = new Text2('Wins: ' + gamesWon, {
size: 100,
fill: 0x00ffcc
});
gamesWonTxt.anchor.set(0, 1);
gamesWonTxt.x = 40;
gamesWonTxt.y = -40;
LK.gui.bottomLeft.addChild(gamesWonTxt);
// Main game update loop
game.update = function () {
if (gameOver) return;
// Only update bird and pipes if game started
if (gameStarted) {
bird.update();
// Add new pipes at interval
ticksSinceLastPipe++;
if (ticksSinceLastPipe >= PIPE_INTERVAL) {
ticksSinceLastPipe = 0;
var pipePair = new PipePair();
pipePair.x = 2048 + pipePair.pipeWidth / 2;
pipePair.y = 0;
pipes.push(pipePair);
game.addChild(pipePair);
}
// Update pipes and check for collisions
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove pipes that have gone off screen
if (pipe.x < -pipe.pipeWidth / 2) {
pipe.destroy();
pipes.splice(i, 1);
continue;
}
// Collision detection (bird with pipes)
// Use .intersects for both top and bottom pipes
if (bird.intersects(pipe.topPipe) || bird.intersects(pipe.bottomPipe)) {
endGame();
return;
}
// Coin collection
if (pipe.coin && !pipe.coin.collected && bird.intersects(pipe.coin)) {
pipe.coin.collected = true;
pipe.coin.visible = false;
score += 5; // Award 5 points for coin
scoreTxt.setText(score + '');
moneyAmount += 1;
moneyTxt.setText(moneyAmount + '');
// Persist moneyAmount to storage
storage.moneyAmount = moneyAmount;
}
// Scoring: if bird passes the center of the pipe and hasn't scored yet
if (!pipe.scored && pipe.x + pipe.pipeWidth / 2 < bird.x - bird.width / 2) {
pipe.scored = true;
score += 1;
scoreTxt.setText(score + '');
}
}
// --- Win Condition: 500 points ---
if (score >= 500 && !gameOver) {
// Only trigger win once per game
gameOver = true;
// Increment games won and persist
gamesWon = typeof storage.gamesWon === "number" ? storage.gamesWon : 0;
gamesWon += 1;
storage.gamesWon = gamesWon;
if (typeof gamesWonTxt !== "undefined") {
gamesWonTxt.setText('Wins: ' + gamesWon);
}
LK.effects.flashScreen(0x00ffcc, 1000);
hideStartButton();
LK.showYouWin();
return;
}
// Collision with ground
if (bird.y + bird.height / 2 >= 2732 - GROUND_HEIGHT) {
bird.y = 2732 - GROUND_HEIGHT - bird.height / 2;
endGame();
return;
}
}
};
// Reset game on game over (handled by LK automatically, but for clarity)
game.onGameOver = function () {
resetGame();
};
// Initial reset
resetGame();
a pixel art coin. In-Game asset. 2d. High contrast. pixel
a bird like flappy bird but they are not same In-Game asset. 2d. High contrast. pixel
green grass ground. In-Game asset. 2d. High contrast. pixel no shadow
a beautiful sky. pixel In-Game asset. 2d. High contrast. No shadows
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Flappy Flight" and with the description "Guide a bird through gaps in pipes by tapping to flap and avoid obstacles. Score points for each successful pass.". No text on banner! pixel
a long mario pipeTop. pixel PIPE TOP In-Game asset. 2d. High contrast. No shadows
a pixel bird head. In-Game asset. 2d. High contrast. No shadows