/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // PipePair: Represents a pair of pipes (top and bottom) with a gap for the player to pass through var PipePair = Container.expand(function () { var self = Container.call(this); // Constants for pipe dimensions var pipeWidth = 220; var minGap = 700; var maxGap = 1000; var minY = 350; var maxY = 2732 - 350 - minGap; // Randomize gap position var gapSize = minGap + Math.floor(Math.random() * (maxGap - minGap)); var gapY = minY + Math.floor(Math.random() * (maxY - minY)); // Top pipe var topPipeHeight = gapY; var topPipeAssetId = typeof theme !== "undefined" && theme === "dragon" ? "pipeTopDragon" : "pipeTop"; var topPipe = self.attachAsset(topPipeAssetId, { width: pipeWidth, height: topPipeHeight, color: 0x4ec04e, shape: 'box', anchorX: 0, anchorY: 0 }); topPipe.x = 0; topPipe.y = 0; // Bottom pipe var bottomPipeHeight = 2732 - (gapY + gapSize); var bottomPipeAssetId = typeof theme !== "undefined" && theme === "dragon" ? "pipeBottomDragon" : "pipeBottom"; var bottomPipe = self.attachAsset(bottomPipeAssetId, { width: pipeWidth, height: bottomPipeHeight, color: 0x4ec04e, shape: 'box', anchorX: 0, anchorY: 0 }); bottomPipe.x = 0; bottomPipe.y = gapY + gapSize; // Used for collision detection self.topPipe = topPipe; self.bottomPipe = bottomPipe; // Used to check if player has passed this pipe self.passed = false; // Set initial position (off the right edge) self.x = 2048; self.y = 0; // Pipe speed (will be set from outside) self.speed = -8; // Update method called every tick self.update = function () { self.x += self.speed; }; // Helper for collision with player self.intersectsPlayer = function (player) { // Check collision with top pipe if (player.intersects(topPipe)) { return true; } // Check collision with bottom pipe if (player.intersects(bottomPipe)) { return true; } return false; }; return self; }); // Add player // Override Player class to use correct asset for theme var Player = Container.expand(function () { var self = Container.call(this); // Attach correct asset for theme var square = self.attachAsset(playerAssetId, { width: 200, height: 200, color: 0xffd700, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Physics properties self.vy = 0; // vertical velocity self.gravity = 1.1; self.jumpStrength = -22; // For rotation effect (optional, can be improved later) self.maxAngle = Math.PI / 6; self.minAngle = -Math.PI / 3; // Update method called every tick self.update = function () { self.vy += self.gravity; self.y += self.vy; // Clamp rotation based on velocity var angle = self.vy / 40; if (angle > self.maxAngle) { angle = self.maxAngle; } if (angle < self.minAngle) { angle = self.minAngle; } // Always rotate the current player asset (first child) if (self.children && self.children.length > 0) { self.children[0].rotation = angle; } }; // Jump method self.jump = function () { self.vy = self.jumpStrength; }; // Reset method self.reset = function () { self.vy = 0; self.y = 2732 / 2; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ // Game constants // Tween plugin for smooth animations (not strictly needed for MVP, but included for future use) var GROUND_HEIGHT = 350; // Volume button state and asset var volumeOnButton = null; var volumeOffButton = null; var isVolumeOn = true; // Always play background music when the game starts if (!window.__FRVR_MUTED) { LK.playMusic('muzik'); } // Hide the pause button in the top left if it exists if (LK.pauseButton) { LK.pauseButton.visible = false; } var PIPE_INTERVAL = 110; // frames between pipes var PIPE_SPEED_START = -8; var PIPE_SPEED_INC = -0.2; var PIPE_SPEED_MAX = -22; // Game state variables var theme = "bird"; // "bird" or "dragon" var pipes = []; var player = null; var ground = null; // scoreTxt and bestScoreTxt removed (no longer used) var score = 0; var bestScore = 0; var ticksSinceLastPipe = 0; var pipeSpeed = PIPE_SPEED_START; var gameStarted = false; var gameOver = false; // --- Score Display (top center, only during gameplay) --- var scoreGroup = new Container(); scoreGroup.visible = false; // Only visible during gameplay // Helper to clear and set numeric assets for score function setScoreDigits(n) { // Remove previous digits (keep score frame at index 0) for (var i = scoreGroup.children.length - 1; i >= 1; i--) { scoreGroup.removeChild(scoreGroup.children[i]); } var digits = String(n).split(''); // Use the correct score frame asset for theme var scoreFrameAssetId = theme === "dragon" ? "scoreSignDragon" : "scoreSign"; var scoreFrame = scoreGroup.children[0]; if (!scoreFrame || scoreFrame.assetId !== scoreFrameAssetId) { // Remove old frame if wrong if (scoreFrame) { scoreGroup.removeChild(scoreFrame); } scoreFrame = LK.getAsset(scoreFrameAssetId, { width: 500, height: 300, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 150 }); scoreFrame.assetId = scoreFrameAssetId; scoreGroup.addChildAt(scoreFrame, 0); } // Layout digits centered on score frame var digitWidth = 120; var digitSpacing = 16; var totalDigitsWidth = digits.length * digitWidth + (digits.length - 1) * digitSpacing; var startX = scoreFrame.x - totalDigitsWidth / 2; var digitY = scoreFrame.y + 8; // Vertically centered on frame for (var i = 0; i < digits.length; i++) { var digitAsset = LK.getAsset(digits[i], { width: digitWidth, height: LK.getAsset(digits[i], { width: 100, height: 100 }).height * (digitWidth / 100), anchorX: 0, anchorY: 0.5, x: startX + i * (digitWidth + digitSpacing), y: digitY }); scoreGroup.addChild(digitAsset); } } // Add to game game.addChild(scoreGroup); // Load best score from storage at game start bestScore = 31; storage.flappyBest = 31; // Add background image (behind everything) var backgroundAssetId = theme === "dragon" ? "backgroundDragon" : "background"; var groundAssetId = theme === "dragon" ? "groundDragon" : "ground"; var playerAssetId = theme === "dragon" ? "playerDragon" : "playerSquare"; var background = LK.getAsset(backgroundAssetId, { width: 2048, height: 2732, anchorX: 0, anchorY: 0 }); background.x = 0; background.y = 0; game.addChild(background); // Add ground (visual only, for collision) ground = LK.getAsset(groundAssetId, { width: 2048, height: GROUND_HEIGHT, color: 0x8b5a2b, shape: 'box', anchorX: 0, anchorY: 0 }); ground.x = 0; ground.y = 2732 - GROUND_HEIGHT; game.addChild(ground); player = new Player(); player.x = 600; player.y = 2732 / 2; player.visible = false; // Hide player at first game.addChild(player); // Add play and cosmetics buttons (in front of background, before game starts) var playButton = LK.getAsset('playButton', { width: 1000, height: 440, anchorX: 0.5, anchorY: 0.5, x: 1024, y: 600 }); var cosmeticsButton = LK.getAsset('cosmeticsButton', { width: 1340, height: 440, anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1100 }); game.addChild(playButton); game.addChild(cosmeticsButton); // Cosmetics menu state var cosmeticsMenuActive = false; // Cosmetics menu assets (created once, shown/hidden as needed) var cosmeticsTitle = LK.getAsset('cosmeticsButton', { width: 1340, height: 440, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 440 }); cosmeticsTitle.visible = false; game.addChild(cosmeticsTitle); // Character frames var frameY = 1366; // vertical center var frameSpacing = 300; var birdFrame = LK.getAsset('birdFrame', { width: 840, height: 960, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - frameSpacing - 150, y: frameY }); birdFrame.visible = false; game.addChild(birdFrame); // Bird character sprite (centered in birdFrame) var birdSprite = LK.getAsset('playerSquare', { width: 520, // fits well inside 840x960 frame, leaves margin for decorations height: 490, // maintain aspect ratio (original 1000x945.31) anchorX: 0.5, anchorY: 0.5, x: birdFrame.x, y: birdFrame.y }); birdSprite.visible = false; game.addChild(birdSprite); var dragonFrame = LK.getAsset('dragonFrame', { width: 920, height: 1150, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + frameSpacing + 150, y: frameY }); dragonFrame.visible = false; game.addChild(dragonFrame); // Dragon character sprite (centered in dragonFrame) var dragonSprite = LK.getAsset('playerDragon', { width: 600, // fits well inside 920x1150 frame, leaves margin for decorations height: 610, // maintain aspect ratio (original 1000x1019.92) anchorX: 0.5, anchorY: 0.5, x: dragonFrame.x, y: dragonFrame.y }); dragonSprite.visible = false; game.addChild(dragonSprite); // BACK button, centered below frames, same vertical distance as title above var backBtnY = frameY + (frameY - cosmeticsTitle.y); var backButton = LK.getAsset('backButton', { width: 1000, height: 531.25, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: backBtnY }); backButton.visible = false; game.addChild(backButton); // Add volume buttons (positioned under cosmetics button, matching cosmetics button width) var cosmeticsBtnWidth = 1340; var cosmeticsBtnHeight = 440; var volumeBtnWidth = cosmeticsBtnWidth * 0.25; // make volume buttons bigger var volumeBtnHeightOn = volumeBtnWidth * 0.77; // keep aspect ratio similar to original var volumeBtnHeightOff = volumeBtnWidth * 0.98; // keep aspect ratio similar to original var volumeBtnX = 2048 / 2; var volumeBtnY = 1100 + cosmeticsBtnHeight / 2 + 60 + volumeBtnHeightOn / 2; // 60px gap below cosmetics volumeOnButton = LK.getAsset('volumeOn', { width: volumeBtnWidth, height: volumeBtnHeightOn, anchorX: 0.5, anchorY: 0.5, x: volumeBtnX, y: volumeBtnY }); volumeOffButton = LK.getAsset('volumeOff', { width: volumeBtnWidth, height: volumeBtnHeightOff, anchorX: 0.5, anchorY: 0.5, x: volumeBtnX, y: volumeBtnY }); volumeOnButton.visible = true; volumeOffButton.visible = false; game.addChild(volumeOnButton); game.addChild(volumeOffButton); // (score text removed) // (best score text removed) // --- High Score Display (main menu) --- var highScoreGroup = new Container(); // Make the high score icon and digits EVEN bigger // Position: to the left under the cosmetics button, now stack vertically var highScoreIconScale = 2.5; // scale up the icon even more // Centered horizontally with cosmetics button, below it var highScoreIconX = 725; var highScoreIconY = 1875; var highScoreIcon = LK.getAsset('highScore', { width: volumeBtnWidth * 0.7 * highScoreIconScale, height: 54.3 * (volumeBtnWidth * 0.7 * highScoreIconScale / 100), anchorX: 0.5, anchorY: 0.5, x: highScoreIconX + volumeBtnWidth * 0.7 * highScoreIconScale / 2, y: highScoreIconY }); highScoreGroup.addChild(highScoreIcon); // Helper to clear and set numeric assets for high score function setHighScoreDigits(n) { // Remove previous digits for (var i = highScoreGroup.children.length - 1; i >= 1; i--) { highScoreGroup.removeChild(highScoreGroup.children[i]); } var digits = String(n).split(''); var digitWidth = volumeBtnWidth * 0.32 * 2.2; // scale up digits even more var digitSpacing = digitWidth * 0.12; // more spacing for bigger digits // Center digits horizontally under the icon var totalDigitsWidth = digits.length * digitWidth + (digits.length - 1) * digitSpacing; var startX = highScoreIcon.x - totalDigitsWidth / 2; var digitY = highScoreIcon.y + highScoreIcon.height / 2 + digitWidth / 2 + 20; // 20px gap below icon for (var i = 0; i < digits.length; i++) { var digitAsset = LK.getAsset(digits[i], { width: digitWidth, height: LK.getAsset(digits[i], { width: 100, height: 100 }).height * (digitWidth / 100), anchorX: 0, anchorY: 0.5, x: startX + i * (digitWidth + digitSpacing), y: digitY }); highScoreGroup.addChild(digitAsset); } } // Set initial best score setHighScoreDigits(bestScore); // Add to game game.addChild(highScoreGroup); // Show only in main menu highScoreGroup.visible = !gameStarted && !gameOver; // Update high score display on game over var _oldOnGameOver = game.onGameOver; game.onGameOver = function () { // Always update bestScore from storage in case it was updated elsewhere if (typeof storage.flappyBest !== "undefined") { bestScore = storage.flappyBest; } if (score > bestScore) { bestScore = score; storage.flappyBest = bestScore; setHighScoreDigits(bestScore); } else { setHighScoreDigits(bestScore); } highScoreGroup.visible = true; if (_oldOnGameOver) { _oldOnGameOver(); } }; // Hide high score display when game starts var _oldStartGame = startGame; startGame = function startGame() { highScoreGroup.visible = false; _oldStartGame(); }; // Helper: Start the game function startGame() { // Reset state for (var i = pipes.length - 1; i >= 0; i--) { pipes[i].destroy(); pipes.splice(i, 1); } player.x = 600; player.y = 2732 / 2; player.vy = 0; player.visible = true; // Show player when game starts score = 0; setScoreDigits(score); scoreGroup.visible = true; // Show score display pipeSpeed = PIPE_SPEED_START; ticksSinceLastPipe = 0; gameStarted = true; gameOver = false; // Play music if not muted if (!window.__FRVR_MUTED) { LK.playMusic('muzik'); } } // Helper: End the game function endGame() { gameOver = true; gameStarted = false; // Swap player asset to correct dead asset for theme if (player && player.children && player.children.length > 0) { var oldAsset = player.children[0]; var deadAssetId = theme === "dragon" ? "playerDragonDead" : "playerDead"; var deadAsset = player.attachAsset(deadAssetId, { width: 200, height: 200, anchorX: 0.5, anchorY: 0.5 }); deadAsset.x = oldAsset.x; deadAsset.y = oldAsset.y; deadAsset.rotation = oldAsset.rotation; player.removeChild(oldAsset); player.visible = true; // Show dead asset after game over } // Flash screen red LK.effects.flashScreen(0xff0000, 800); // Save best score if (score > bestScore) { bestScore = score; storage.flappyBest = bestScore; } // Show game over popup (handled by LK) scoreGroup.visible = false; // Hide score display LK.showGameOver(); } // Helper: Player jump function playerJump() { if (!gameStarted) { startGame(); } if (!gameOver) { player.jump(); } } // Touch/click to jump or handle play/cosmetics button game.down = function (x, y, obj) { // Prevent jump if touch is in top left 100x100 (menu area) if (x < 100 && y < 100) { return; } // If game hasn't started, check if playButton or cosmeticsButton was pressed if (!gameStarted && !gameOver) { // Check volumeOnButton (centered hitbox) if (volumeOnButton && volumeOnButton.visible && x >= volumeOnButton.x - volumeOnButton.width / 2 && x <= volumeOnButton.x + volumeOnButton.width / 2 && y >= volumeOnButton.y - volumeOnButton.height / 2 && y <= volumeOnButton.y + volumeOnButton.height / 2) { // Mute all sounds isVolumeOn = false; volumeOnButton.visible = false; volumeOffButton.visible = true; // Set a global mute flag; all sound/music play calls should check this flag window.__FRVR_MUTED = true; LK.stopMusic(); return; } // Check volumeOffButton (centered hitbox) if (volumeOffButton && volumeOffButton.visible && x >= volumeOffButton.x - volumeOffButton.width / 2 && x <= volumeOffButton.x + volumeOffButton.width / 2 && y >= volumeOffButton.y - volumeOffButton.height / 2 && y <= volumeOffButton.y + volumeOffButton.height / 2) { // Unmute all sounds isVolumeOn = true; volumeOnButton.visible = true; volumeOffButton.visible = false; // Unset the global mute flag; all sound/music play calls should check this flag window.__FRVR_MUTED = false; LK.playMusic('muzik'); return; } // Check playButton (disable if cosmetics menu is active) if (!cosmeticsMenuActive && playButton && x >= playButton.x - playButton.width / 2 && x <= playButton.x + playButton.width / 2 && y >= playButton.y - playButton.height / 2 && y <= playButton.y + playButton.height / 2) { // Start game, hide buttons playButton.visible = false; cosmeticsButton.visible = false; if (volumeOnButton) { volumeOnButton.visible = false; } if (volumeOffButton) { volumeOffButton.visible = false; } startGame(); return; } // Check cosmeticsButton (open cosmetics menu) if (cosmeticsButton && x >= cosmeticsButton.x - cosmeticsButton.width / 2 && x <= cosmeticsButton.x + cosmeticsButton.width / 2 && y >= cosmeticsButton.y - cosmeticsButton.height / 2 && y <= cosmeticsButton.y + cosmeticsButton.height / 2) { // Hide main menu assets playButton.visible = false; cosmeticsButton.visible = false; if (volumeOnButton) { volumeOnButton.visible = false; } if (volumeOffButton) { volumeOffButton.visible = false; } highScoreGroup.visible = false; // Show cosmetics menu assets cosmeticsTitle.visible = true; birdFrame.visible = true; dragonFrame.visible = true; backButton.visible = true; birdSprite.visible = true; dragonSprite.visible = true; cosmeticsMenuActive = true; scoreGroup.visible = false; // Hide score display in cosmetics menu return; } // Handle selecting dragon character in cosmetics menu if (cosmeticsMenuActive && dragonSprite && dragonSprite.visible && x >= dragonSprite.x - dragonSprite.width / 2 && x <= dragonSprite.x + dragonSprite.width / 2 && y >= dragonSprite.y - dragonSprite.height / 2 && y <= dragonSprite.y + dragonSprite.height / 2) { theme = "dragon"; // Update background var newBackgroundAssetId = "backgroundDragon"; var newGroundAssetId = "groundDragon"; var newPlayerAssetId = "playerDragon"; // Update score frame asset for dragon theme setScoreDigits(score); // Remove old background and ground if (background && background.parent) { background.parent.removeChild(background); } if (ground && ground.parent) { ground.parent.removeChild(ground); } // Add new background and ground background = LK.getAsset(newBackgroundAssetId, { width: 2048, height: 2732, anchorX: 0, anchorY: 0 }); background.x = 0; background.y = 0; game.addChildAt(background, 0); ground = LK.getAsset(newGroundAssetId, { width: 2048, height: GROUND_HEIGHT, color: 0x8b5a2b, shape: 'box', anchorX: 0, anchorY: 0 }); ground.x = 0; ground.y = 2732 - GROUND_HEIGHT; game.addChildAt(ground, 1); // Update player asset if (player && player.children && player.children.length > 0) { var oldAsset = player.children[0]; var newAsset = player.attachAsset(newPlayerAssetId, { width: 200, height: 200, color: 0xffd700, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); newAsset.x = oldAsset.x; newAsset.y = oldAsset.y; newAsset.rotation = oldAsset.rotation; player.removeChild(oldAsset); } // Update volume button assets for dragon theme if (volumeOnButton && volumeOffButton) { // Remove old buttons from game if (volumeOnButton.parent) { volumeOnButton.parent.removeChild(volumeOnButton); } if (volumeOffButton.parent) { volumeOffButton.parent.removeChild(volumeOffButton); } // Create new dragon theme volume buttons volumeOnButton = LK.getAsset('volumeOnDragon', { width: volumeBtnWidth, height: volumeBtnHeightOn, anchorX: 0.5, anchorY: 0.5, x: volumeBtnX, y: volumeBtnY }); volumeOffButton = LK.getAsset('volumeOffDragon', { width: volumeBtnWidth, height: volumeBtnHeightOff, anchorX: 0.5, anchorY: 0.5, x: volumeBtnX, y: volumeBtnY }); // Hide volume buttons if cosmetics menu is active, otherwise set based on isVolumeOn if (cosmeticsMenuActive) { volumeOnButton.visible = false; volumeOffButton.visible = false; } else { volumeOnButton.visible = isVolumeOn; volumeOffButton.visible = !isVolumeOn; } game.addChild(volumeOnButton); game.addChild(volumeOffButton); } // Optionally, you could add a visual feedback here (e.g. highlight selection) // --- Cosmetics selection visual feedback: bounce animation for selected character --- if (cosmeticsMenuActive) { var bounceDragonUp = function bounceDragonUp() { tween(dragonSprite, { y: dragonFrame.y - bounceAmount }, { duration: bounceDuration, easing: tween.cubicOut, onFinish: bounceDragonDown }); }; var bounceDragonDown = function bounceDragonDown() { tween(dragonSprite, { y: dragonFrame.y }, { duration: bounceDuration, easing: tween.cubicIn, onFinish: bounceDragonUp }); }; // Stop any previous tweens tween.stop(birdSprite, { y: true }); tween.stop(dragonSprite, { y: true }); // Bird is not selected, dragon is selected // Bird: reset to original position, no bounce birdSprite.y = birdFrame.y; // Dragon: bounce up and down var bounceAmount = 60; var bounceDuration = 600; bounceDragonUp(); } return; } // Handle selecting bird character in cosmetics menu if (cosmeticsMenuActive && birdSprite && birdSprite.visible && x >= birdSprite.x - birdSprite.width / 2 && x <= birdSprite.x + birdSprite.width / 2 && y >= birdSprite.y - birdSprite.height / 2 && y <= birdSprite.y + birdSprite.height / 2) { theme = "bird"; // Update background var newBackgroundAssetId = "background"; var newGroundAssetId = "ground"; var newPlayerAssetId = "playerSquare"; // Update score frame asset for bird theme setScoreDigits(score); // Remove old background and ground if (background && background.parent) { background.parent.removeChild(background); } if (ground && ground.parent) { ground.parent.removeChild(ground); } // Add new background and ground background = LK.getAsset(newBackgroundAssetId, { width: 2048, height: 2732, anchorX: 0, anchorY: 0 }); background.x = 0; background.y = 0; game.addChildAt(background, 0); ground = LK.getAsset(newGroundAssetId, { width: 2048, height: GROUND_HEIGHT, color: 0x8b5a2b, shape: 'box', anchorX: 0, anchorY: 0 }); ground.x = 0; ground.y = 2732 - GROUND_HEIGHT; game.addChildAt(ground, 1); // Update player asset if (player && player.children && player.children.length > 0) { var oldAsset = player.children[0]; var newAsset = player.attachAsset(newPlayerAssetId, { width: 200, height: 200, color: 0xffd700, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); newAsset.x = oldAsset.x; newAsset.y = oldAsset.y; newAsset.rotation = oldAsset.rotation; player.removeChild(oldAsset); } // Update volume button assets for bird theme if (volumeOnButton && volumeOffButton) { // Remove old buttons from game if (volumeOnButton.parent) { volumeOnButton.parent.removeChild(volumeOnButton); } if (volumeOffButton.parent) { volumeOffButton.parent.removeChild(volumeOffButton); } // Create new bird theme volume buttons volumeOnButton = LK.getAsset('volumeOn', { width: volumeBtnWidth, height: volumeBtnHeightOn, anchorX: 0.5, anchorY: 0.5, x: volumeBtnX, y: volumeBtnY }); volumeOffButton = LK.getAsset('volumeOff', { width: volumeBtnWidth, height: volumeBtnHeightOff, anchorX: 0.5, anchorY: 0.5, x: volumeBtnX, y: volumeBtnY }); // Hide volume buttons if cosmetics menu is active, otherwise set based on isVolumeOn if (cosmeticsMenuActive) { volumeOnButton.visible = false; volumeOffButton.visible = false; } else { volumeOnButton.visible = isVolumeOn; volumeOffButton.visible = !isVolumeOn; } game.addChild(volumeOnButton); game.addChild(volumeOffButton); } // --- Cosmetics selection visual feedback: bounce animation for selected character --- if (cosmeticsMenuActive) { var bounceBirdUp = function bounceBirdUp() { tween(birdSprite, { y: birdFrame.y - bounceAmount }, { duration: bounceDuration, easing: tween.cubicOut, onFinish: bounceBirdDown }); }; var bounceBirdDown = function bounceBirdDown() { tween(birdSprite, { y: birdFrame.y }, { duration: bounceDuration, easing: tween.cubicIn, onFinish: bounceBirdUp }); }; // Stop any previous tweens tween.stop(birdSprite, { y: true }); tween.stop(dragonSprite, { y: true }); // Dragon is not selected, bird is selected // Dragon: reset to original position, no bounce dragonSprite.y = dragonFrame.y; // Bird: bounce up and down var bounceAmount = 60; var bounceDuration = 600; bounceBirdUp(); } return; } // Handle BACK button in cosmetics menu if (cosmeticsMenuActive && backButton && backButton.visible && x >= backButton.x - backButton.width / 2 && x <= backButton.x + backButton.width / 2 && y >= backButton.y - backButton.height / 2 && y <= backButton.y + backButton.height / 2) { // Hide cosmetics menu assets cosmeticsTitle.visible = false; birdFrame.visible = false; dragonFrame.visible = false; backButton.visible = false; birdSprite.visible = false; dragonSprite.visible = false; cosmeticsMenuActive = false; // Stop bounce tweens and reset positions tween.stop(birdSprite, { y: true }); tween.stop(dragonSprite, { y: true }); birdSprite.y = birdFrame.y; dragonSprite.y = dragonFrame.y; // Show main menu assets playButton.visible = true; cosmeticsButton.visible = true; if (isVolumeOn) { volumeOnButton.visible = true; volumeOffButton.visible = false; } else { volumeOnButton.visible = false; volumeOffButton.visible = true; } highScoreGroup.visible = true; scoreGroup.visible = false; // Hide score display in main menu return; } // Block jump if not started return; } playerJump(); }; // Main update loop game.update = function () { if (!gameStarted || gameOver) { // Idle animation: player slowly falls, pipes don't move player.update(); // Clamp player to not fall below ground if (player.y + 100 > 2732 - GROUND_HEIGHT) { player.y = 2732 - GROUND_HEIGHT - 100; player.vy = 0.0; } return; } // Update player player.update(); // Clamp player to not fall below ground if (player.y + 100 > 2732 - GROUND_HEIGHT) { player.y = 2732 - GROUND_HEIGHT - 100; endGame(); return; } // Clamp player to not go above screen if (player.y - 100 < 0) { player.y = 100; player.vy = 0; } // Spawn pipes ticksSinceLastPipe++; if (ticksSinceLastPipe >= PIPE_INTERVAL) { ticksSinceLastPipe = 0; var pipe = new PipePair(); pipe.speed = pipeSpeed; pipes.push(pipe); game.addChild(pipe); } // Update pipes for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; pipe.speed = pipeSpeed; pipe.update(); // Check for collision if (pipe.intersectsPlayer(player)) { endGame(); return; } // Check if player passed the pipe (score) if (!pipe.passed && pipe.x + 220 < player.x - 100) { pipe.passed = true; score++; setScoreDigits(score); // Increase speed every 5 points, up to max if (score % 5 === 0 && pipeSpeed > PIPE_SPEED_MAX) { pipeSpeed += PIPE_SPEED_INC; } } // Remove pipes that are off screen if (pipe.x + 220 < -100) { pipe.destroy(); pipes.splice(i, 1); } } }; // Idle animation: gently bob player up and down before game starts var idleDir = 1; var idleTicks = 0; game.idleInterval = LK.setInterval(function () { if (!gameStarted && !gameOver) { idleTicks++; if (idleTicks % 30 === 0) { idleDir *= -1; } player.y += idleDir * 2; } }, 16); // Reset game on game over (handled by LK, but for safety) game.onGameOver = function () { gameStarted = false; gameOver = true; if (playButton) { playButton.visible = true; } if (cosmeticsButton) { cosmeticsButton.visible = true; } if (volumeOnButton && volumeOffButton) { if (isVolumeOn) { volumeOnButton.visible = true; volumeOffButton.visible = false; } else { volumeOnButton.visible = false; volumeOffButton.visible = true; } } }; // Reset game on you win (not used, but for completeness) game.onYouWin = function () { gameStarted = false; gameOver = true; if (playButton) { playButton.visible = true; } if (cosmeticsButton) { cosmeticsButton.visible = true; } if (player) { player.visible = false; } // Hide player after you win if (volumeOnButton && volumeOffButton) { if (isVolumeOn) { volumeOnButton.visible = true; volumeOffButton.visible = false; } else { volumeOnButton.visible = false; volumeOffButton.visible = true; } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// PipePair: Represents a pair of pipes (top and bottom) with a gap for the player to pass through
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Constants for pipe dimensions
var pipeWidth = 220;
var minGap = 700;
var maxGap = 1000;
var minY = 350;
var maxY = 2732 - 350 - minGap;
// Randomize gap position
var gapSize = minGap + Math.floor(Math.random() * (maxGap - minGap));
var gapY = minY + Math.floor(Math.random() * (maxY - minY));
// Top pipe
var topPipeHeight = gapY;
var topPipeAssetId = typeof theme !== "undefined" && theme === "dragon" ? "pipeTopDragon" : "pipeTop";
var topPipe = self.attachAsset(topPipeAssetId, {
width: pipeWidth,
height: topPipeHeight,
color: 0x4ec04e,
shape: 'box',
anchorX: 0,
anchorY: 0
});
topPipe.x = 0;
topPipe.y = 0;
// Bottom pipe
var bottomPipeHeight = 2732 - (gapY + gapSize);
var bottomPipeAssetId = typeof theme !== "undefined" && theme === "dragon" ? "pipeBottomDragon" : "pipeBottom";
var bottomPipe = self.attachAsset(bottomPipeAssetId, {
width: pipeWidth,
height: bottomPipeHeight,
color: 0x4ec04e,
shape: 'box',
anchorX: 0,
anchorY: 0
});
bottomPipe.x = 0;
bottomPipe.y = gapY + gapSize;
// Used for collision detection
self.topPipe = topPipe;
self.bottomPipe = bottomPipe;
// Used to check if player has passed this pipe
self.passed = false;
// Set initial position (off the right edge)
self.x = 2048;
self.y = 0;
// Pipe speed (will be set from outside)
self.speed = -8;
// Update method called every tick
self.update = function () {
self.x += self.speed;
};
// Helper for collision with player
self.intersectsPlayer = function (player) {
// Check collision with top pipe
if (player.intersects(topPipe)) {
return true;
}
// Check collision with bottom pipe
if (player.intersects(bottomPipe)) {
return true;
}
return false;
};
return self;
});
// Add player
// Override Player class to use correct asset for theme
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach correct asset for theme
var square = self.attachAsset(playerAssetId, {
width: 200,
height: 200,
color: 0xffd700,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Physics properties
self.vy = 0; // vertical velocity
self.gravity = 1.1;
self.jumpStrength = -22;
// For rotation effect (optional, can be improved later)
self.maxAngle = Math.PI / 6;
self.minAngle = -Math.PI / 3;
// Update method called every tick
self.update = function () {
self.vy += self.gravity;
self.y += self.vy;
// Clamp rotation based on velocity
var angle = self.vy / 40;
if (angle > self.maxAngle) {
angle = self.maxAngle;
}
if (angle < self.minAngle) {
angle = self.minAngle;
}
// Always rotate the current player asset (first child)
if (self.children && self.children.length > 0) {
self.children[0].rotation = angle;
}
};
// Jump method
self.jump = function () {
self.vy = self.jumpStrength;
};
// Reset method
self.reset = function () {
self.vy = 0;
self.y = 2732 / 2;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Tween plugin for smooth animations (not strictly needed for MVP, but included for future use)
var GROUND_HEIGHT = 350;
// Volume button state and asset
var volumeOnButton = null;
var volumeOffButton = null;
var isVolumeOn = true;
// Always play background music when the game starts
if (!window.__FRVR_MUTED) {
LK.playMusic('muzik');
}
// Hide the pause button in the top left if it exists
if (LK.pauseButton) {
LK.pauseButton.visible = false;
}
var PIPE_INTERVAL = 110; // frames between pipes
var PIPE_SPEED_START = -8;
var PIPE_SPEED_INC = -0.2;
var PIPE_SPEED_MAX = -22;
// Game state variables
var theme = "bird"; // "bird" or "dragon"
var pipes = [];
var player = null;
var ground = null;
// scoreTxt and bestScoreTxt removed (no longer used)
var score = 0;
var bestScore = 0;
var ticksSinceLastPipe = 0;
var pipeSpeed = PIPE_SPEED_START;
var gameStarted = false;
var gameOver = false;
// --- Score Display (top center, only during gameplay) ---
var scoreGroup = new Container();
scoreGroup.visible = false; // Only visible during gameplay
// Helper to clear and set numeric assets for score
function setScoreDigits(n) {
// Remove previous digits (keep score frame at index 0)
for (var i = scoreGroup.children.length - 1; i >= 1; i--) {
scoreGroup.removeChild(scoreGroup.children[i]);
}
var digits = String(n).split('');
// Use the correct score frame asset for theme
var scoreFrameAssetId = theme === "dragon" ? "scoreSignDragon" : "scoreSign";
var scoreFrame = scoreGroup.children[0];
if (!scoreFrame || scoreFrame.assetId !== scoreFrameAssetId) {
// Remove old frame if wrong
if (scoreFrame) {
scoreGroup.removeChild(scoreFrame);
}
scoreFrame = LK.getAsset(scoreFrameAssetId, {
width: 500,
height: 300,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 150
});
scoreFrame.assetId = scoreFrameAssetId;
scoreGroup.addChildAt(scoreFrame, 0);
}
// Layout digits centered on score frame
var digitWidth = 120;
var digitSpacing = 16;
var totalDigitsWidth = digits.length * digitWidth + (digits.length - 1) * digitSpacing;
var startX = scoreFrame.x - totalDigitsWidth / 2;
var digitY = scoreFrame.y + 8; // Vertically centered on frame
for (var i = 0; i < digits.length; i++) {
var digitAsset = LK.getAsset(digits[i], {
width: digitWidth,
height: LK.getAsset(digits[i], {
width: 100,
height: 100
}).height * (digitWidth / 100),
anchorX: 0,
anchorY: 0.5,
x: startX + i * (digitWidth + digitSpacing),
y: digitY
});
scoreGroup.addChild(digitAsset);
}
}
// Add to game
game.addChild(scoreGroup);
// Load best score from storage at game start
bestScore = 31;
storage.flappyBest = 31;
// Add background image (behind everything)
var backgroundAssetId = theme === "dragon" ? "backgroundDragon" : "background";
var groundAssetId = theme === "dragon" ? "groundDragon" : "ground";
var playerAssetId = theme === "dragon" ? "playerDragon" : "playerSquare";
var background = LK.getAsset(backgroundAssetId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
background.x = 0;
background.y = 0;
game.addChild(background);
// Add ground (visual only, for collision)
ground = LK.getAsset(groundAssetId, {
width: 2048,
height: GROUND_HEIGHT,
color: 0x8b5a2b,
shape: 'box',
anchorX: 0,
anchorY: 0
});
ground.x = 0;
ground.y = 2732 - GROUND_HEIGHT;
game.addChild(ground);
player = new Player();
player.x = 600;
player.y = 2732 / 2;
player.visible = false; // Hide player at first
game.addChild(player);
// Add play and cosmetics buttons (in front of background, before game starts)
var playButton = LK.getAsset('playButton', {
width: 1000,
height: 440,
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600
});
var cosmeticsButton = LK.getAsset('cosmeticsButton', {
width: 1340,
height: 440,
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1100
});
game.addChild(playButton);
game.addChild(cosmeticsButton);
// Cosmetics menu state
var cosmeticsMenuActive = false;
// Cosmetics menu assets (created once, shown/hidden as needed)
var cosmeticsTitle = LK.getAsset('cosmeticsButton', {
width: 1340,
height: 440,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 440
});
cosmeticsTitle.visible = false;
game.addChild(cosmeticsTitle);
// Character frames
var frameY = 1366; // vertical center
var frameSpacing = 300;
var birdFrame = LK.getAsset('birdFrame', {
width: 840,
height: 960,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - frameSpacing - 150,
y: frameY
});
birdFrame.visible = false;
game.addChild(birdFrame);
// Bird character sprite (centered in birdFrame)
var birdSprite = LK.getAsset('playerSquare', {
width: 520,
// fits well inside 840x960 frame, leaves margin for decorations
height: 490,
// maintain aspect ratio (original 1000x945.31)
anchorX: 0.5,
anchorY: 0.5,
x: birdFrame.x,
y: birdFrame.y
});
birdSprite.visible = false;
game.addChild(birdSprite);
var dragonFrame = LK.getAsset('dragonFrame', {
width: 920,
height: 1150,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + frameSpacing + 150,
y: frameY
});
dragonFrame.visible = false;
game.addChild(dragonFrame);
// Dragon character sprite (centered in dragonFrame)
var dragonSprite = LK.getAsset('playerDragon', {
width: 600,
// fits well inside 920x1150 frame, leaves margin for decorations
height: 610,
// maintain aspect ratio (original 1000x1019.92)
anchorX: 0.5,
anchorY: 0.5,
x: dragonFrame.x,
y: dragonFrame.y
});
dragonSprite.visible = false;
game.addChild(dragonSprite);
// BACK button, centered below frames, same vertical distance as title above
var backBtnY = frameY + (frameY - cosmeticsTitle.y);
var backButton = LK.getAsset('backButton', {
width: 1000,
height: 531.25,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: backBtnY
});
backButton.visible = false;
game.addChild(backButton);
// Add volume buttons (positioned under cosmetics button, matching cosmetics button width)
var cosmeticsBtnWidth = 1340;
var cosmeticsBtnHeight = 440;
var volumeBtnWidth = cosmeticsBtnWidth * 0.25; // make volume buttons bigger
var volumeBtnHeightOn = volumeBtnWidth * 0.77; // keep aspect ratio similar to original
var volumeBtnHeightOff = volumeBtnWidth * 0.98; // keep aspect ratio similar to original
var volumeBtnX = 2048 / 2;
var volumeBtnY = 1100 + cosmeticsBtnHeight / 2 + 60 + volumeBtnHeightOn / 2; // 60px gap below cosmetics
volumeOnButton = LK.getAsset('volumeOn', {
width: volumeBtnWidth,
height: volumeBtnHeightOn,
anchorX: 0.5,
anchorY: 0.5,
x: volumeBtnX,
y: volumeBtnY
});
volumeOffButton = LK.getAsset('volumeOff', {
width: volumeBtnWidth,
height: volumeBtnHeightOff,
anchorX: 0.5,
anchorY: 0.5,
x: volumeBtnX,
y: volumeBtnY
});
volumeOnButton.visible = true;
volumeOffButton.visible = false;
game.addChild(volumeOnButton);
game.addChild(volumeOffButton);
// (score text removed)
// (best score text removed)
// --- High Score Display (main menu) ---
var highScoreGroup = new Container();
// Make the high score icon and digits EVEN bigger
// Position: to the left under the cosmetics button, now stack vertically
var highScoreIconScale = 2.5; // scale up the icon even more
// Centered horizontally with cosmetics button, below it
var highScoreIconX = 725;
var highScoreIconY = 1875;
var highScoreIcon = LK.getAsset('highScore', {
width: volumeBtnWidth * 0.7 * highScoreIconScale,
height: 54.3 * (volumeBtnWidth * 0.7 * highScoreIconScale / 100),
anchorX: 0.5,
anchorY: 0.5,
x: highScoreIconX + volumeBtnWidth * 0.7 * highScoreIconScale / 2,
y: highScoreIconY
});
highScoreGroup.addChild(highScoreIcon);
// Helper to clear and set numeric assets for high score
function setHighScoreDigits(n) {
// Remove previous digits
for (var i = highScoreGroup.children.length - 1; i >= 1; i--) {
highScoreGroup.removeChild(highScoreGroup.children[i]);
}
var digits = String(n).split('');
var digitWidth = volumeBtnWidth * 0.32 * 2.2; // scale up digits even more
var digitSpacing = digitWidth * 0.12; // more spacing for bigger digits
// Center digits horizontally under the icon
var totalDigitsWidth = digits.length * digitWidth + (digits.length - 1) * digitSpacing;
var startX = highScoreIcon.x - totalDigitsWidth / 2;
var digitY = highScoreIcon.y + highScoreIcon.height / 2 + digitWidth / 2 + 20; // 20px gap below icon
for (var i = 0; i < digits.length; i++) {
var digitAsset = LK.getAsset(digits[i], {
width: digitWidth,
height: LK.getAsset(digits[i], {
width: 100,
height: 100
}).height * (digitWidth / 100),
anchorX: 0,
anchorY: 0.5,
x: startX + i * (digitWidth + digitSpacing),
y: digitY
});
highScoreGroup.addChild(digitAsset);
}
}
// Set initial best score
setHighScoreDigits(bestScore);
// Add to game
game.addChild(highScoreGroup);
// Show only in main menu
highScoreGroup.visible = !gameStarted && !gameOver;
// Update high score display on game over
var _oldOnGameOver = game.onGameOver;
game.onGameOver = function () {
// Always update bestScore from storage in case it was updated elsewhere
if (typeof storage.flappyBest !== "undefined") {
bestScore = storage.flappyBest;
}
if (score > bestScore) {
bestScore = score;
storage.flappyBest = bestScore;
setHighScoreDigits(bestScore);
} else {
setHighScoreDigits(bestScore);
}
highScoreGroup.visible = true;
if (_oldOnGameOver) {
_oldOnGameOver();
}
};
// Hide high score display when game starts
var _oldStartGame = startGame;
startGame = function startGame() {
highScoreGroup.visible = false;
_oldStartGame();
};
// Helper: Start the game
function startGame() {
// Reset state
for (var i = pipes.length - 1; i >= 0; i--) {
pipes[i].destroy();
pipes.splice(i, 1);
}
player.x = 600;
player.y = 2732 / 2;
player.vy = 0;
player.visible = true; // Show player when game starts
score = 0;
setScoreDigits(score);
scoreGroup.visible = true; // Show score display
pipeSpeed = PIPE_SPEED_START;
ticksSinceLastPipe = 0;
gameStarted = true;
gameOver = false;
// Play music if not muted
if (!window.__FRVR_MUTED) {
LK.playMusic('muzik');
}
}
// Helper: End the game
function endGame() {
gameOver = true;
gameStarted = false;
// Swap player asset to correct dead asset for theme
if (player && player.children && player.children.length > 0) {
var oldAsset = player.children[0];
var deadAssetId = theme === "dragon" ? "playerDragonDead" : "playerDead";
var deadAsset = player.attachAsset(deadAssetId, {
width: 200,
height: 200,
anchorX: 0.5,
anchorY: 0.5
});
deadAsset.x = oldAsset.x;
deadAsset.y = oldAsset.y;
deadAsset.rotation = oldAsset.rotation;
player.removeChild(oldAsset);
player.visible = true; // Show dead asset after game over
}
// Flash screen red
LK.effects.flashScreen(0xff0000, 800);
// Save best score
if (score > bestScore) {
bestScore = score;
storage.flappyBest = bestScore;
}
// Show game over popup (handled by LK)
scoreGroup.visible = false; // Hide score display
LK.showGameOver();
}
// Helper: Player jump
function playerJump() {
if (!gameStarted) {
startGame();
}
if (!gameOver) {
player.jump();
}
}
// Touch/click to jump or handle play/cosmetics button
game.down = function (x, y, obj) {
// Prevent jump if touch is in top left 100x100 (menu area)
if (x < 100 && y < 100) {
return;
}
// If game hasn't started, check if playButton or cosmeticsButton was pressed
if (!gameStarted && !gameOver) {
// Check volumeOnButton (centered hitbox)
if (volumeOnButton && volumeOnButton.visible && x >= volumeOnButton.x - volumeOnButton.width / 2 && x <= volumeOnButton.x + volumeOnButton.width / 2 && y >= volumeOnButton.y - volumeOnButton.height / 2 && y <= volumeOnButton.y + volumeOnButton.height / 2) {
// Mute all sounds
isVolumeOn = false;
volumeOnButton.visible = false;
volumeOffButton.visible = true;
// Set a global mute flag; all sound/music play calls should check this flag
window.__FRVR_MUTED = true;
LK.stopMusic();
return;
}
// Check volumeOffButton (centered hitbox)
if (volumeOffButton && volumeOffButton.visible && x >= volumeOffButton.x - volumeOffButton.width / 2 && x <= volumeOffButton.x + volumeOffButton.width / 2 && y >= volumeOffButton.y - volumeOffButton.height / 2 && y <= volumeOffButton.y + volumeOffButton.height / 2) {
// Unmute all sounds
isVolumeOn = true;
volumeOnButton.visible = true;
volumeOffButton.visible = false;
// Unset the global mute flag; all sound/music play calls should check this flag
window.__FRVR_MUTED = false;
LK.playMusic('muzik');
return;
}
// Check playButton (disable if cosmetics menu is active)
if (!cosmeticsMenuActive && playButton && x >= playButton.x - playButton.width / 2 && x <= playButton.x + playButton.width / 2 && y >= playButton.y - playButton.height / 2 && y <= playButton.y + playButton.height / 2) {
// Start game, hide buttons
playButton.visible = false;
cosmeticsButton.visible = false;
if (volumeOnButton) {
volumeOnButton.visible = false;
}
if (volumeOffButton) {
volumeOffButton.visible = false;
}
startGame();
return;
}
// Check cosmeticsButton (open cosmetics menu)
if (cosmeticsButton && x >= cosmeticsButton.x - cosmeticsButton.width / 2 && x <= cosmeticsButton.x + cosmeticsButton.width / 2 && y >= cosmeticsButton.y - cosmeticsButton.height / 2 && y <= cosmeticsButton.y + cosmeticsButton.height / 2) {
// Hide main menu assets
playButton.visible = false;
cosmeticsButton.visible = false;
if (volumeOnButton) {
volumeOnButton.visible = false;
}
if (volumeOffButton) {
volumeOffButton.visible = false;
}
highScoreGroup.visible = false;
// Show cosmetics menu assets
cosmeticsTitle.visible = true;
birdFrame.visible = true;
dragonFrame.visible = true;
backButton.visible = true;
birdSprite.visible = true;
dragonSprite.visible = true;
cosmeticsMenuActive = true;
scoreGroup.visible = false; // Hide score display in cosmetics menu
return;
}
// Handle selecting dragon character in cosmetics menu
if (cosmeticsMenuActive && dragonSprite && dragonSprite.visible && x >= dragonSprite.x - dragonSprite.width / 2 && x <= dragonSprite.x + dragonSprite.width / 2 && y >= dragonSprite.y - dragonSprite.height / 2 && y <= dragonSprite.y + dragonSprite.height / 2) {
theme = "dragon";
// Update background
var newBackgroundAssetId = "backgroundDragon";
var newGroundAssetId = "groundDragon";
var newPlayerAssetId = "playerDragon";
// Update score frame asset for dragon theme
setScoreDigits(score);
// Remove old background and ground
if (background && background.parent) {
background.parent.removeChild(background);
}
if (ground && ground.parent) {
ground.parent.removeChild(ground);
}
// Add new background and ground
background = LK.getAsset(newBackgroundAssetId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
background.x = 0;
background.y = 0;
game.addChildAt(background, 0);
ground = LK.getAsset(newGroundAssetId, {
width: 2048,
height: GROUND_HEIGHT,
color: 0x8b5a2b,
shape: 'box',
anchorX: 0,
anchorY: 0
});
ground.x = 0;
ground.y = 2732 - GROUND_HEIGHT;
game.addChildAt(ground, 1);
// Update player asset
if (player && player.children && player.children.length > 0) {
var oldAsset = player.children[0];
var newAsset = player.attachAsset(newPlayerAssetId, {
width: 200,
height: 200,
color: 0xffd700,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
newAsset.x = oldAsset.x;
newAsset.y = oldAsset.y;
newAsset.rotation = oldAsset.rotation;
player.removeChild(oldAsset);
}
// Update volume button assets for dragon theme
if (volumeOnButton && volumeOffButton) {
// Remove old buttons from game
if (volumeOnButton.parent) {
volumeOnButton.parent.removeChild(volumeOnButton);
}
if (volumeOffButton.parent) {
volumeOffButton.parent.removeChild(volumeOffButton);
}
// Create new dragon theme volume buttons
volumeOnButton = LK.getAsset('volumeOnDragon', {
width: volumeBtnWidth,
height: volumeBtnHeightOn,
anchorX: 0.5,
anchorY: 0.5,
x: volumeBtnX,
y: volumeBtnY
});
volumeOffButton = LK.getAsset('volumeOffDragon', {
width: volumeBtnWidth,
height: volumeBtnHeightOff,
anchorX: 0.5,
anchorY: 0.5,
x: volumeBtnX,
y: volumeBtnY
});
// Hide volume buttons if cosmetics menu is active, otherwise set based on isVolumeOn
if (cosmeticsMenuActive) {
volumeOnButton.visible = false;
volumeOffButton.visible = false;
} else {
volumeOnButton.visible = isVolumeOn;
volumeOffButton.visible = !isVolumeOn;
}
game.addChild(volumeOnButton);
game.addChild(volumeOffButton);
}
// Optionally, you could add a visual feedback here (e.g. highlight selection)
// --- Cosmetics selection visual feedback: bounce animation for selected character ---
if (cosmeticsMenuActive) {
var bounceDragonUp = function bounceDragonUp() {
tween(dragonSprite, {
y: dragonFrame.y - bounceAmount
}, {
duration: bounceDuration,
easing: tween.cubicOut,
onFinish: bounceDragonDown
});
};
var bounceDragonDown = function bounceDragonDown() {
tween(dragonSprite, {
y: dragonFrame.y
}, {
duration: bounceDuration,
easing: tween.cubicIn,
onFinish: bounceDragonUp
});
};
// Stop any previous tweens
tween.stop(birdSprite, {
y: true
});
tween.stop(dragonSprite, {
y: true
});
// Bird is not selected, dragon is selected
// Bird: reset to original position, no bounce
birdSprite.y = birdFrame.y;
// Dragon: bounce up and down
var bounceAmount = 60;
var bounceDuration = 600;
bounceDragonUp();
}
return;
}
// Handle selecting bird character in cosmetics menu
if (cosmeticsMenuActive && birdSprite && birdSprite.visible && x >= birdSprite.x - birdSprite.width / 2 && x <= birdSprite.x + birdSprite.width / 2 && y >= birdSprite.y - birdSprite.height / 2 && y <= birdSprite.y + birdSprite.height / 2) {
theme = "bird";
// Update background
var newBackgroundAssetId = "background";
var newGroundAssetId = "ground";
var newPlayerAssetId = "playerSquare";
// Update score frame asset for bird theme
setScoreDigits(score);
// Remove old background and ground
if (background && background.parent) {
background.parent.removeChild(background);
}
if (ground && ground.parent) {
ground.parent.removeChild(ground);
}
// Add new background and ground
background = LK.getAsset(newBackgroundAssetId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
background.x = 0;
background.y = 0;
game.addChildAt(background, 0);
ground = LK.getAsset(newGroundAssetId, {
width: 2048,
height: GROUND_HEIGHT,
color: 0x8b5a2b,
shape: 'box',
anchorX: 0,
anchorY: 0
});
ground.x = 0;
ground.y = 2732 - GROUND_HEIGHT;
game.addChildAt(ground, 1);
// Update player asset
if (player && player.children && player.children.length > 0) {
var oldAsset = player.children[0];
var newAsset = player.attachAsset(newPlayerAssetId, {
width: 200,
height: 200,
color: 0xffd700,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
newAsset.x = oldAsset.x;
newAsset.y = oldAsset.y;
newAsset.rotation = oldAsset.rotation;
player.removeChild(oldAsset);
}
// Update volume button assets for bird theme
if (volumeOnButton && volumeOffButton) {
// Remove old buttons from game
if (volumeOnButton.parent) {
volumeOnButton.parent.removeChild(volumeOnButton);
}
if (volumeOffButton.parent) {
volumeOffButton.parent.removeChild(volumeOffButton);
}
// Create new bird theme volume buttons
volumeOnButton = LK.getAsset('volumeOn', {
width: volumeBtnWidth,
height: volumeBtnHeightOn,
anchorX: 0.5,
anchorY: 0.5,
x: volumeBtnX,
y: volumeBtnY
});
volumeOffButton = LK.getAsset('volumeOff', {
width: volumeBtnWidth,
height: volumeBtnHeightOff,
anchorX: 0.5,
anchorY: 0.5,
x: volumeBtnX,
y: volumeBtnY
});
// Hide volume buttons if cosmetics menu is active, otherwise set based on isVolumeOn
if (cosmeticsMenuActive) {
volumeOnButton.visible = false;
volumeOffButton.visible = false;
} else {
volumeOnButton.visible = isVolumeOn;
volumeOffButton.visible = !isVolumeOn;
}
game.addChild(volumeOnButton);
game.addChild(volumeOffButton);
}
// --- Cosmetics selection visual feedback: bounce animation for selected character ---
if (cosmeticsMenuActive) {
var bounceBirdUp = function bounceBirdUp() {
tween(birdSprite, {
y: birdFrame.y - bounceAmount
}, {
duration: bounceDuration,
easing: tween.cubicOut,
onFinish: bounceBirdDown
});
};
var bounceBirdDown = function bounceBirdDown() {
tween(birdSprite, {
y: birdFrame.y
}, {
duration: bounceDuration,
easing: tween.cubicIn,
onFinish: bounceBirdUp
});
};
// Stop any previous tweens
tween.stop(birdSprite, {
y: true
});
tween.stop(dragonSprite, {
y: true
});
// Dragon is not selected, bird is selected
// Dragon: reset to original position, no bounce
dragonSprite.y = dragonFrame.y;
// Bird: bounce up and down
var bounceAmount = 60;
var bounceDuration = 600;
bounceBirdUp();
}
return;
}
// Handle BACK button in cosmetics menu
if (cosmeticsMenuActive && backButton && backButton.visible && x >= backButton.x - backButton.width / 2 && x <= backButton.x + backButton.width / 2 && y >= backButton.y - backButton.height / 2 && y <= backButton.y + backButton.height / 2) {
// Hide cosmetics menu assets
cosmeticsTitle.visible = false;
birdFrame.visible = false;
dragonFrame.visible = false;
backButton.visible = false;
birdSprite.visible = false;
dragonSprite.visible = false;
cosmeticsMenuActive = false;
// Stop bounce tweens and reset positions
tween.stop(birdSprite, {
y: true
});
tween.stop(dragonSprite, {
y: true
});
birdSprite.y = birdFrame.y;
dragonSprite.y = dragonFrame.y;
// Show main menu assets
playButton.visible = true;
cosmeticsButton.visible = true;
if (isVolumeOn) {
volumeOnButton.visible = true;
volumeOffButton.visible = false;
} else {
volumeOnButton.visible = false;
volumeOffButton.visible = true;
}
highScoreGroup.visible = true;
scoreGroup.visible = false; // Hide score display in main menu
return;
}
// Block jump if not started
return;
}
playerJump();
};
// Main update loop
game.update = function () {
if (!gameStarted || gameOver) {
// Idle animation: player slowly falls, pipes don't move
player.update();
// Clamp player to not fall below ground
if (player.y + 100 > 2732 - GROUND_HEIGHT) {
player.y = 2732 - GROUND_HEIGHT - 100;
player.vy = 0.0;
}
return;
}
// Update player
player.update();
// Clamp player to not fall below ground
if (player.y + 100 > 2732 - GROUND_HEIGHT) {
player.y = 2732 - GROUND_HEIGHT - 100;
endGame();
return;
}
// Clamp player to not go above screen
if (player.y - 100 < 0) {
player.y = 100;
player.vy = 0;
}
// Spawn pipes
ticksSinceLastPipe++;
if (ticksSinceLastPipe >= PIPE_INTERVAL) {
ticksSinceLastPipe = 0;
var pipe = new PipePair();
pipe.speed = pipeSpeed;
pipes.push(pipe);
game.addChild(pipe);
}
// Update pipes
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.speed = pipeSpeed;
pipe.update();
// Check for collision
if (pipe.intersectsPlayer(player)) {
endGame();
return;
}
// Check if player passed the pipe (score)
if (!pipe.passed && pipe.x + 220 < player.x - 100) {
pipe.passed = true;
score++;
setScoreDigits(score);
// Increase speed every 5 points, up to max
if (score % 5 === 0 && pipeSpeed > PIPE_SPEED_MAX) {
pipeSpeed += PIPE_SPEED_INC;
}
}
// Remove pipes that are off screen
if (pipe.x + 220 < -100) {
pipe.destroy();
pipes.splice(i, 1);
}
}
};
// Idle animation: gently bob player up and down before game starts
var idleDir = 1;
var idleTicks = 0;
game.idleInterval = LK.setInterval(function () {
if (!gameStarted && !gameOver) {
idleTicks++;
if (idleTicks % 30 === 0) {
idleDir *= -1;
}
player.y += idleDir * 2;
}
}, 16);
// Reset game on game over (handled by LK, but for safety)
game.onGameOver = function () {
gameStarted = false;
gameOver = true;
if (playButton) {
playButton.visible = true;
}
if (cosmeticsButton) {
cosmeticsButton.visible = true;
}
if (volumeOnButton && volumeOffButton) {
if (isVolumeOn) {
volumeOnButton.visible = true;
volumeOffButton.visible = false;
} else {
volumeOnButton.visible = false;
volumeOffButton.visible = true;
}
}
};
// Reset game on you win (not used, but for completeness)
game.onYouWin = function () {
gameStarted = false;
gameOver = true;
if (playButton) {
playButton.visible = true;
}
if (cosmeticsButton) {
cosmeticsButton.visible = true;
}
if (player) {
player.visible = false;
} // Hide player after you win
if (volumeOnButton && volumeOffButton) {
if (isVolumeOn) {
volumeOnButton.visible = true;
volumeOffButton.visible = false;
} else {
volumeOnButton.visible = false;
volumeOffButton.visible = true;
}
}
};
A flat, horizontal ground layer made of bright green grass on top and brown soil underneath, designed in pixel art style. The grass is slightly jagged at the top to suggest texture, and the soil has subtle pixel shading with small rocks and dirt patches. The image should be side-view and seamless, suitable for a 2D side-scrolling game like Flappy Bird, with tiny pixel flowers or varied shades of green for extra detail.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. seamless. ground texture. retro style
A pixel art green pipe bottom section from a side view, designed for a 2D side-scrolling game like Flappy Bird. The pipe is vertically stretched and has a bright green, smooth surface with subtle shading to show depth. The pipe should be closed at the bottom and open at the top, and it should look metallic and cartoonish, consistent with a retro game style.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. retro style. bright colors
A unique pixel art bird character designed for a 2D side-scrolling game. The bird has a round, compact body and large expressive eyes. Its feathers are colorful, with shades of turquoise and orange, and it has small flapping wings. It has a slightly cartoonish look, with a tiny beak and simple outlines. The bird is seen from the side and appears mid-flight, with its wings lifted. The overall design is cute and distinct from Flappy Bird, but still fits in a retro-style arcade game.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. game character. cute. retro style. colorful
A pixel art version of the same bird character shown in a defeated state, designed for a 2D side-scrolling game. The bird appears to be falling downward or lying upside down, with closed or X-shaped eyes and limp wings. Its beak might be slightly open, and its posture should suggest that it is unconscious or dead. The overall style should remain cute and cartoonish, fitting the retro pixel art theme, but clearly indicate that the bird is no longer alive.
A pixel art baby dragon character designed for a 2D side-scrolling game, viewed from the side. The dragon has small wings, a chubby body, and a cute cartoonish face. It has small horns, a short tail, and colorful scales—shades of purple and teal. The dragon is in a flying pose, flapping its wings. Its eyes are big and expressive, and it has tiny fangs showing. The overall design is playful and fantasy-inspired, while still fitting a retro arcade pixel style.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. game character. baby dragon. cute. fantasy. retro style
A pixel art version of the baby dragon character shown in a defeated state, designed for a 2D side-scrolling game. The dragon appears to be falling or lying on its back, with X-shaped eyes or closed eyelids. Its wings are limp and slightly spread, the tail droops, and its mouth is slightly open with a small tongue sticking out. The overall design remains cute and fantasy-inspired, with a cartoonish touch, but clearly shows the dragon is no longer active or alive. The colors and features should match the flying version for consistency.
A small and cute pixel art icon representing sound on, designed for a 2D game UI. The icon shows a colorful, rounded speaker with smiling face and small animated sound waves coming out in bright pastel colors like yellow and light blue. The design is chibi and cartoonish, with soft outlines and playful details. It fits the style of a fun, colorful retro arcade game.. In-Game asset. 2d. High contrast. No shadows. pixel art. cute. ui icon. colorful. retro style. chibi. cartoonish
A small and cute pixel art icon representing sound off, designed for a 2D game UI. The icon shows the same round speaker, but now with a sad or sleepy face and a small red X or mute symbol on it. The sound waves are gone or faded out. The color palette is still colorful, using soft reds and purples. The style is playful, chibi, and pixel art, fitting a cute arcade game interface.
remove the dragon and ground
get only ground
only take "PLAY" button
only take "COSMETICS" button
A flat pixel art button for a 2D retro-style game UI. The button is rectangular with rounded corners and minimal shading. It features the word "BACK" written in large, centered pixel font. The button color should be a clear pastel blue-grey, leaning more towards light blue than grey. The design is clean and simple, with no extra icons, borders, or decorations—just a cozy retro button.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d game ui. flat design. pastel colors. soft light blue. clean. simple. rounded corners. large pixel font. minimal
A decorative pixel art character card frame for a 2D retro-style game, themed around a cute bird world. The card is rectangular with rounded corners and has a pastel-colored background. Each corner includes soft, themed details: A cloud and sun in the top-left, A feather or flying bird in the top-right, Grass or flowers in the bottom-left, A cracked egg in the bottom-right. The center area is left empty for a character sprite to be added later. The overall design is cozy, cute, colorful, and flat—fitting the aesthetic of a peaceful, playful bird-themed game world. Style tags: pixel art, retro, bird theme, character card, cozy, pastel colors, cloud, feather, egg, grass, flowers, soft design, minimal, 2D UI, flat, cute. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. bird theme. character card. cozy. pastel colors. cloud. feather. egg. grass. flowers. soft design. minimal. 2d ui. flat. cute
A pixel art text label for a retro 2D game main menu displaying "HIGH SCORE" in bold uppercase letters. The text is centered, large enough to be easily readable, and uses a pastel light green color. The style is simple, flat, and fits the cozy retro pixel art theme of the game. The text label is static and not interactive.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. text label. pastel green. bold uppercase letters. clear font. minimal. flat design. non-interactive
A pixel art number "5" designed for a cozy, cute retro 2D game UI with a bird theme. The number is rendered in soft pastel light green tones to harmonize with the gentle and sweet aesthetic of the game. It has a clean, simple, and rounded pixel style with no harsh edges, fitting perfectly within the pixel art UI style. The number is bold and easily readable at small sizes.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. number. pastel light green. simple. rounded edges. bold. cute. minimal
A pixel art number "3" designed for a cozy, cute retro 2D game UI with a bird theme. The number is rendered in soft pastel light green tones to harmonize with the gentle and sweet aesthetic of the game. It has a clean, simple, and rounded pixel style with no harsh edges, fitting perfectly within the pixel art UI style. The number is bold and easily readable at small sizes.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. number. pastel light green. simple. rounded edges. bold. cute. minimal
A pixel art number "2" designed for a cozy, cute retro 2D game UI with a bird theme. The number is rendered in soft pastel light green tones to harmonize with the gentle and sweet aesthetic of the game. It has a clean, simple, and rounded pixel style with no harsh edges, fitting perfectly within the pixel art UI style. The number is bold and easily readable at small sizes.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. number. pastel light green. simple. rounded edges. bold. cute. minimal
A pixel art number "1" designed for a cozy, cute retro 2D game UI with a bird theme. The number is rendered in soft pastel light green tones to harmonize with the gentle and sweet aesthetic of the game. It has a clean, simple, and rounded pixel style with no harsh edges, fitting perfectly within the pixel art UI style. The number is bold and easily readable at small sizes.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. number. pastel light green. simple. rounded edges. bold. cute. minimal
A pixel art number "0" designed for a cozy, cute retro 2D game UI with a bird theme. The number is rendered in soft pastel light green tones to harmonize with the gentle and sweet aesthetic of the game. It has a clean, simple, and rounded pixel style with no harsh edges, fitting perfectly within the pixel art UI style. The number is bold and easily readable at small sizes.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. number. pastel light green. simple. rounded edges. bold. cute. minimal
A pixel art number "7" designed for a cozy, cute retro 2D game UI with a bird theme. The number is rendered in soft pastel light green tones to harmonize with the gentle and sweet aesthetic of the game. It has a clean, simple, and rounded pixel style with no harsh edges, fitting perfectly within the pixel art UI style. The number is bold and easily readable at small sizes.. In-Game asset. 2d. High contrast. No shadows. pixel art. retro. 2d ui. number. pastel light green. simple. rounded edges. bold. cute. minimal
A square pixel art character selection frame inspired by a cute baby dragon. The frame includes fantasy elements like tiny dragon horns on the top corners, small claws at the bottom, and subtle flame or smoke decorations curling around the edges. The background inside the frame should have a soft and decorative texture, matching the baby dragon theme, using soft red, orange, and golden tones. The character will be placed on top of this frame later, so leave the center visually calm and not too detailed. The frame should be viewed from the front and designed for a 2D game UI.. In-Game asset. 2d. High contrast. No shadows. pixel art. front view. fantasy. cute. retro. pastel colors. ui element
A single vertical top pipe for a 2D pixel art side-scrolling game with classic Flappy Bird hitbox. The pipe is designed with a dragon theme using red, orange, and gold tones. The pipe surface features subtle dragon scale textures and glowing ember details. Around the opening at the top, there is a stylized flame motif to give a mystical fantasy feel. The pipe is viewed from the side with a clear silhouette suitable for gameplay. The style is cute, retro, and fits a pixel art fantasy game.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. retro. fantasy. dragon theme. red. orange. gold. cute. mystical
A small pixel art dragon head viewed from the side, mouth slightly open as if letting out a small roar. Glowing embers or a little flame is coming out of its mouth to symbolize that sound is on. The style is cute and retro, matching a fantasy-themed 2D game UI icon.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. cute. fantasy. retro. flame. icon. dragon
A small pixel art dragon head viewed from the side, mouth closed with a small puff of smoke or a speech bubble with a cross to symbolize that sound is off. The expression is calm and cute. Designed as a fantasy-themed icon for a retro-style 2D game UI.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. cute. fantasy. retro. dragon. mute. icon
A horizontally-oriented pixel art score frame designed for a cute 2D retro game. The frame is meant to be placed at the top center of the screen during gameplay. It has a soft pastel green or cream background with a rounded rectangular shape. The inside of the frame is filled with a solid light color (not transparent), ready for number assets to be placed on top. The border is subtle and cute, with minimal decorative details like tiny sparkles or stars to match the playful and colorful retro game aesthetic.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. cute. pastel colors. soft edges. retro ui. video game hud. minimal
A horizontally-oriented pixel art score frame designed for a fantasy-themed 2D retro game featuring a baby dragon. The frame is intended to be placed at the top center of the screen during gameplay. It has a rounded rectangular shape with a rich dark red or deep gold background and mystical decorative accents. The inside of the frame is filled with a solid warm color (not transparent) to allow number assets to be placed on top. Small fantasy elements like tiny dragon scales, curved horns, or red lantern motifs can appear on the corners or edges to match the baby dragon theme. The overall look is mystical, cute, and fits a traditional Eastern-inspired fantasy aesthetic.. In-Game asset. 2d. High contrast. No shadows. pixel art. side view. fantasy. retro game. dragon theme. eastern style. cute. rich colors. mystical