/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { coins: 0, selectedSkin: "bird", unlockedSkins: ["bird"] }); /**** * Classes ****/ var Bird = Container.expand(function () { var self = Container.call(this); // Get the selected skin var selectedSkin = storage.selectedSkin || "bird"; // Bird asset - use the correct asset based on the selected skin var birdGraphics = self.attachAsset(selectedSkin, { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.5 }); // Add a bigger hitbox for collision detection while keeping visual size self.hitArea = new Rectangle(-25, -25, 50, 50); // Set special flag for rainbow bird if (selectedSkin === "rainbow_bird") { self.isRainbow = true; // Will be handled in update } // Bird physics properties self.velocityY = 0; self.velocityX = 12; // Increased from 8 to 12 for faster horizontal movement self.gravity = 1.0; // Increased from 0.7 to 1.0 for stronger gravity self.flapStrength = -20; // Increased from -15 to -20 for higher jumps self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Handle tap/click on bird self.down = function (x, y, obj) { self.flap(); }; // Make bird flap wings self.flap = function () { self.velocityY = self.flapStrength; // Play flap sound LK.getSound('flap').play(); // Make rainbow bird do a backflip (360 degree rotation) when flapping if (self.isRainbow) { // Stop any existing rotation tweens tween.stop(birdGraphics, { rotation: true }); // Store the current direction (positive or negative scaleX) var currentDirection = Math.sign(birdGraphics.scaleX); // Perform a complete 360-degree backflip tween(birdGraphics, { rotation: birdGraphics.rotation + currentDirection * Math.PI * 2 // Full rotation in the correct direction }, { duration: 500, // Half a second for the flip easing: tween.easeOut }); } }; // Update bird physics self.update = function () { // Store last position self.lastX = self.x; self.lastY = self.y; // Apply gravity self.velocityY += self.gravity; // Update position self.y += self.velocityY; self.x += self.velocityX; // Make sure hitArea is always centered on the bird (follows the bird's position) // No need to update position since the hitArea is relative to the bird's coordinate system // Use a more fair hitbox size - not too big, not too small self.hitArea = new Rectangle(-30, -30, 60, 60); // Visual feedback - rotate bird based on velocity var targetRotation = Math.min(Math.max(self.velocityY / 15, -0.5), 0.5); // Make bird face the direction it's flying by flipping the sprite if (self.velocityX > 0) { birdGraphics.scaleX = Math.abs(birdGraphics.scaleX); // Face right if (!self.isRainbow || !tween.has(birdGraphics, 'rotation')) { birdGraphics.rotation = targetRotation; // Apply rotation normally for right-facing bird } } else { birdGraphics.scaleX = -Math.abs(birdGraphics.scaleX); // Face left if (!self.isRainbow || !tween.has(birdGraphics, 'rotation')) { birdGraphics.rotation = -targetRotation; // Invert rotation for left-facing bird } } // Rainbow effect is now handled by the rainbow bird asset // We can still animate it slightly for visual effect if (self.isRainbow) { // Apply subtle visual effects for rainbow bird var pulseAmount = Math.sin(LK.ticks / 10) * 0.1; // Maintain the correct direction by checking current scaleX sign var scaleXDirection = Math.sign(birdGraphics.scaleX); var baseScale = 2.5; // Match the original scale set in constructor // Apply pulse effect while preserving direction birdGraphics.scale.set(scaleXDirection * (baseScale + pulseAmount), baseScale + pulseAmount); } }; return self; }); var Seed = Container.expand(function () { var self = Container.call(this); // Seed asset var seedGraphics = self.attachAsset('seed', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.5, scaleY: 3.5 }); // Seed properties self.lastWasIntersecting = false; self.speedX = 0; // Set speed to 0 to stop seeds from moving // Animation effect self.update = function () { // No longer updating x position seedGraphics.rotation += 0.05; // Remove seeds that are off-screen if (self.x < -50) { self.shouldBeRemoved = true; } }; return self; }); var ShopScreen = Container.expand(function () { var self = Container.call(this); // Background panel var panel = self.attachAsset('shape', { width: 1800, height: 2200, color: 0x4682B4, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); panel.alpha = 0.9; // Title var titleText = new Text2('SKIN SHOP', { size: 120, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0); titleText.x = 0; titleText.y = -950; self.addChild(titleText); // Coins display var coinsText = new Text2('Coins: ' + storage.coins, { size: 80, fill: 0xFFD700 }); coinsText.anchor.set(0.5, 0); coinsText.x = 0; coinsText.y = -800; self.addChild(coinsText); // Skins configuration var skins = [{ id: "bird", name: "Default Bird", price: 0, x: -500, y: -500 }, { id: "red_bird", name: "Red Bird", price: 20, x: 0, y: -500 }, { id: "gold_bird", name: "Gold Bird", price: 40, x: 500, y: -500 }, { id: "blue_bird", name: "Blue Bird", price: 60, x: -500, y: 0 }, { id: "green_bird", name: "Green Bird", price: 80, x: 0, y: 0 }, { id: "rainbow_bird", name: "Rainbow Bird", price: 100, x: 500, y: 0 }]; // Selected skin indicator var selectedIndicator = self.attachAsset('shape', { width: 350, height: 350, color: 0xFFD700, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); selectedIndicator.alpha = 0.5; selectedIndicator.visible = false; // Skin buttons var skinButtons = []; for (var i = 0; i < skins.length; i++) { createSkinButton(skins[i]); } function createSkinButton(skin) { var button = new Container(); // Button background var bg = button.attachAsset('shape', { width: 300, height: 300, color: 0x87CEEB, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Bird icon using the specific skin asset var birdIcon = button.attachAsset(skin.id, { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); // Set rainbow flag if needed if (skin.id === "rainbow_bird") { button.isRainbow = true; } // Price/status text var priceText = new Text2(skin.price === 0 ? "FREE" : skin.price + " coins", { size: 40, fill: 0xFFFFFF }); priceText.anchor.set(0.5, 0); priceText.y = 80; button.addChild(priceText); // Set button position button.x = skin.x; button.y = skin.y; button.skinId = skin.id; button.skinName = skin.name; button.price = skin.price; // Check if skin is unlocked var unlockedSkins = storage.unlockedSkins || ["bird"]; if (unlockedSkins.indexOf(skin.id) !== -1) { button.isUnlocked = true; priceText.setText("UNLOCKED"); } else { button.isUnlocked = false; } // Handle button click button.down = function (x, y, obj) { if (button.isUnlocked) { // Select this skin storage.selectedSkin = button.skinId; self.updateSelectedIndicator(); if (self.onSkinSelected) { self.onSkinSelected(button.skinId); } } else if (storage.coins >= button.price) { // Buy this skin storage.coins -= button.price; if (!storage.unlockedSkins) { storage.unlockedSkins = ["bird"]; } storage.unlockedSkins.push(button.skinId); button.isUnlocked = true; priceText.setText("UNLOCKED"); coinsText.setText("Coins: " + storage.coins); } }; self.addChild(button); skinButtons.push(button); return button; } // Close button var closeButton = new Container(); var closeBg = closeButton.attachAsset('shape', { width: 500, height: 120, color: 0xFF0000, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); var closeText = new Text2('CLOSE', { size: 80, fill: 0xFFFFFF }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = 0; closeButton.y = 800; closeButton.down = function () { if (self.onClose) { self.onClose(); } }; self.addChild(closeButton); // Update which skin is selected self.updateSelectedIndicator = function () { var currentSkin = storage.selectedSkin || "bird"; for (var i = 0; i < skinButtons.length; i++) { if (skinButtons[i].skinId === currentSkin) { selectedIndicator.x = skinButtons[i].x; selectedIndicator.y = skinButtons[i].y; selectedIndicator.visible = true; break; } } }; // Position shop in the center self.x = gameWidth / 2; self.y = gameHeight / 2; // Update rainbow skin colors and selection indicator self.update = function () { // Rainbow effect animation for rainbow skin buttons for (var i = 0; i < skinButtons.length; i++) { if (skinButtons[i].isRainbow) { // Instead of tinting, apply a subtle scale animation var pulseAmount = Math.sin(LK.ticks / 10) * 0.1; // Find the bird icon within the button for (var j = 0; j < skinButtons[i].children.length; j++) { var child = skinButtons[i].children[j]; if (child.isAsset) { // Animate the rainbow bird icon with a subtle pulse child.scale.set(2 + pulseAmount, 2 + pulseAmount); break; } } } } }; // Add general down handler to prevent click propagation to elements below self.down = function (x, y, obj) { // Catch all clicks on shop screen that aren't caught by buttons return; }; // Initialize the selection indicator self.updateSelectedIndicator(); return self; }); var Spike = Container.expand(function () { var self = Container.call(this); // Create spike shape using a triangle var spikeGraphics = self.attachAsset('shape', { width: 200, height: 200, color: 0x0000FF, // Blue color for spike shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Rotate to make it look like a spike spikeGraphics.rotation = Math.PI / 4; // 45 degrees self.isStuck = false; self.wall = null; self.lastWasIntersecting = false; // Stick to a wall self.stickTo = function (wall) { self.isStuck = true; self.wall = wall; // Position on wall edge based on wall orientation if (wall.isVertical) { self.x = wall.x + (wall.x < 1024 ? 50 : -50); // Left or right wall - adjusted for wider walls // Keep y position where the collision happened } else { self.y = wall.y + (wall.y < 1366 ? 50 : -50); // Top or bottom wall - adjusted for taller walls // Keep x position where the collision happened } }; self.update = function () { // Nothing to update when stuck if (!self.isStuck) { // If not stuck, spike can move or be collected later self.rotation += 0.05; } }; return self; }); var StartScreen = Container.expand(function () { var self = Container.call(this); // Create title text var titleText = new Text2('BOUNCE BIRD', { size: 150, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 2732 / 3; self.addChild(titleText); // Create start button var startButton = new Container(); var buttonBg = startButton.attachAsset('shape', { width: 500, height: 150, color: 0x4682B4, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); var startText = new Text2('TAP TO START', { size: 80, fill: 0xFFFFFF }); startText.anchor.set(0.5, 0.5); startButton.addChild(startText); startButton.x = 2048 / 2; startButton.y = 2732 / 2; self.addChild(startButton); // Create shop button var shopButton = new Container(); var shopBg = shopButton.attachAsset('shape', { width: 300, height: 120, color: 0x4682B4, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); var shopText = new Text2('SHOP', { size: 60, fill: 0xFFFFFF }); shopText.anchor.set(0.5, 0.5); shopButton.addChild(shopText); shopButton.x = 2048 / 2; shopButton.y = 2732 / 2 + 200; shopButton.down = function () { if (self.onShop) { self.onShop(); } }; self.addChild(shopButton); // Add a bouncing bird animation - use the selected skin if available var skinToShow = storage.selectedSkin || "bird"; var demoBird = self.attachAsset(skinToShow, { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 4, x: 2048 / 2, y: 2732 * 0.7 }); // Make the button pulse self.update = function () { var scale = 1 + Math.sin(LK.ticks / 20) * 0.05; startButton.scale.set(scale); // Make the bird bounce demoBird.y = 2732 * 0.7 + Math.sin(LK.ticks / 15) * 50; demoBird.rotation = Math.sin(LK.ticks / 30) * 0.2; }; // Handle tap event self.down = function () { if (self.onStart) { self.onStart(); } }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); // Wall properties self.isVertical = true; self.wallGraphics = null; // Initialize with wall type (vertical/horizontal) self.init = function (vertical) { self.isVertical = vertical; if (vertical) { self.wallGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); } else { self.wallGraphics = self.attachAsset('ceiling', { anchorX: 0.5, anchorY: 0.5 }); } return self; }; return self; }); /**** * Initialize Game ****/ // Change background color to sky blue var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Change background color to sky blue // Assets for Bounce Bird // Import tween plugin // Add custom method to check if object has active tweens for a property tween.has = function (target, property) { // This is a custom implementation since the tween plugin doesn't have this method // We'll return false to allow the rotation to be set manually return false; }; game.setBackgroundColor(0x87CEEB); // Game variables var bird; var walls = []; var seeds = []; var spikes = []; var score = 0; var gameWidth = 2048; var gameHeight = 2732; var gameStarted = false; var startScreen; var shopScreen; var shopOpen = false; var coinText; // Create start screen function showStartScreen() { startScreen = new StartScreen(); startScreen.onStart = function () { gameStarted = true; startScreen.destroy(); startScreen = null; startGame(); }; startScreen.onShop = function () { showShopScreen(); }; game.addChild(startScreen); } // Show shop screen function showShopScreen() { shopOpen = true; // If start screen exists, hide it temporarily if (startScreen) { startScreen.visible = false; } shopScreen = new ShopScreen(); // Add shop background to block events from propagating through var shopBlocker = new Container(); var blockerBg = shopBlocker.attachAsset('shape', { width: gameWidth, height: gameHeight, color: 0x000000, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); blockerBg.alpha = 0.5; // More visible to ensure it blocks events shopBlocker.x = gameWidth / 2; shopBlocker.y = gameHeight / 2; // Add event handler to block events shopBlocker.down = function (x, y, obj) { // This function blocks events from propagating return; }; // Add blocker first, then shop on top game.addChild(shopBlocker); shopScreen.onClose = function () { shopOpen = false; shopScreen.destroy(); shopScreen = null; shopBlocker.destroy(); // Reset the game state when closing the shop gameStarted = false; // Reset game elements if they exist if (bird) { bird.destroy(); bird = null; } // Clear all game objects for (var i = 0; i < seeds.length; i++) { seeds[i].destroy(); } seeds = []; for (var i = 0; i < spikes.length; i++) { spikes[i].destroy(); } spikes = []; // Reset score score = 0; if (scoreText) { scoreText.setText("Score: 0"); } // Update coin display if it exists if (coinText) { coinText.setText("Coins: " + storage.coins); } // Show the start screen if (startScreen && !startScreen.visible) { startScreen.visible = true; } else { showStartScreen(); } }; shopScreen.onSkinSelected = function (skinId) { // This will be applied next time a bird is created }; game.addChild(shopScreen); } // Initialize the actual game function startGame() { createBird(); createWalls(); createScoreDisplay(); } // Create score text var scoreText; function createScoreDisplay() { scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.topLeft.addChild(scoreText); scoreText.x = 120; // Avoid the top-left 100x100 menu area scoreText.y = 50; // Create coin text coinText = new Text2('Coins: ' + storage.coins, { size: 60, fill: 0xFFD700 }); coinText.anchor.set(0, 0); LK.gui.topLeft.addChild(coinText); coinText.x = 120; // Avoid the top-left 100x100 menu area coinText.y = 140; } // Create bird function createBird() { bird = new Bird(); bird.x = gameWidth / 4; bird.y = gameHeight / 2; game.addChild(bird); } // Create walls (top, bottom, left, right) function createWalls() { // Top wall (ceiling) var ceiling = new Wall().init(false); ceiling.x = gameWidth / 2; ceiling.y = 40; game.addChild(ceiling); walls.push(ceiling); // Bottom wall (floor) var floor = new Wall().init(false); floor.x = gameWidth / 2; floor.y = gameHeight - 40; game.addChild(floor); walls.push(floor); // Left wall var leftWall = new Wall().init(true); leftWall.x = 40; leftWall.y = gameHeight / 2; game.addChild(leftWall); walls.push(leftWall); // Right wall var rightWall = new Wall().init(true); rightWall.x = gameWidth - 40; rightWall.y = gameHeight / 2; game.addChild(rightWall); walls.push(rightWall); } // Show start screen instead of immediately starting the game showStartScreen(); // Spawn a new seed at a random position function spawnSeed() { var seed = new Seed(); seed.x = gameWidth + 50; seed.y = Math.random() * (gameHeight - 200) + 100; seeds.push(seed); game.addChild(seed); } // Handle click/tap anywhere on screen game.down = function (x, y, obj) { // First, check if shop is open - don't process any game inputs if (shopOpen) { // Don't handle game inputs when shop is open return; } // Add a shop button after game over if (!gameStarted && !startScreen) { // This means we're in the game over state showShopScreen(); return; } // Only allow bird flap if game has actually started if (gameStarted && bird) { bird.flap(); } }; // Listen for game over event to preserve our score LK.on('gameover', function () { // Game is over but we want to preserve score // This ensures score is saved when player dies scoreText.setText("Score: " + score); LK.setScore(score); }); // Update function game.update = function () { // Don't process game logic if game hasn't started yet or shop is open if (!gameStarted || shopOpen) { return; } // Seeds now spawn on bounce, not periodically // Update seeds for (var i = seeds.length - 1; i >= 0; i--) { var seed = seeds[i]; // Check for collision with bird var currentIntersecting = bird.intersects(seed); if (!seed.lastWasIntersecting && currentIntersecting) { // Collected seed score++; scoreText.setText("Score: " + score); LK.setScore(score); // Award a coin for each seed storage.coins += 1; coinText.setText("Coins: " + storage.coins); LK.getSound('collect').play(); // Remove seed seed.destroy(); seeds.splice(i, 1); } else { seed.lastWasIntersecting = currentIntersecting; // Remove seeds that should be removed if (seed.shouldBeRemoved) { seed.destroy(); seeds.splice(i, 1); } } } // Check for wall collisions for (var i = 0; i < walls.length; i++) { var wall = walls[i]; var currentIntersecting = bird.intersects(wall); if (!bird.lastWasIntersecting && currentIntersecting) { // Check if bird hit ceiling or floor (horizontal walls) if (!wall.isVertical) { // Bird hit ceiling or floor - game over! LK.effects.flashScreen(0xFF0000, 500); LK.showGameOver(); break; } else { // Bird hit a side wall - bounce! LK.getSound('bounce').play(); // Apply bounce physics bird.velocityX *= -1; // Reverse horizontal direction // Push bird away from wall to prevent sticking bird.x += bird.velocityX > 0 ? 5 : -5; // Visual feedback for bounce LK.effects.flashObject(bird, 0xFFFFFF, 200); // Clear old seeds and spikes on bounce for (var j = seeds.length - 1; j >= 0; j--) { seeds[j].destroy(); seeds.splice(j, 1); } // Clear all existing spikes for (var j = spikes.length - 1; j >= 0; j--) { spikes[j].destroy(); spikes.splice(j, 1); } // Reset all wall colors to the original blue color for (var j = 0; j < walls.length; j++) { if (walls[j].isVertical) { tween(walls[j].wallGraphics, { tint: 0x4682b4 }, { duration: 300, easing: tween.easeIn }); } } // Spawn two new seeds on bounce for (var k = 0; k < 2; k++) { var newSeed = new Seed(); // Make seeds spawn more in the middle - use narrower range centered around the middle var middleX = gameWidth / 2; var middleY = gameHeight / 2; var spawnWidth = gameWidth * 0.6; // Use 60% of screen width var spawnHeight = gameHeight * 0.6; // Use 60% of screen height newSeed.x = middleX + (Math.random() - 0.5) * spawnWidth; newSeed.y = middleY + (Math.random() - 0.5) * spawnHeight; seeds.push(newSeed); game.addChild(newSeed); } // Add a spike that sticks to the opposite wall var spike = new Spike(); // Place spike on opposite vertical wall targetWall = wall.x < gameWidth / 2 ? walls[3] : walls[2]; // walls[3] is right wall, walls[2] is left wall spike.x = targetWall.x; spike.y = Math.random() * (gameHeight - 200) + 100; // Random Y position spike.stickTo(targetWall); spikes.push(spike); game.addChild(spike); // Change the target wall color to match the spike color (blue) tween(targetWall.wallGraphics, { tint: 0x0000FF }, { duration: 300, easing: tween.easeOut }); } // Set lastWasIntersecting for the specific wall collision that just happened bird.lastWasIntersecting = true; break; // Exit the loop after handling a collision } } // Reset lastWasIntersecting if not intersecting any wall var intersectingAnyWall = false; for (var i = 0; i < walls.length; i++) { if (bird.intersects(walls[i])) { intersectingAnyWall = true; break; } } if (!intersectingAnyWall) { bird.lastWasIntersecting = false; } // Check for spike collisions for (var i = 0; i < spikes.length; i++) { var spike = spikes[i]; // Use bird's intersects method for collision detection var currentIntersection = bird.intersects(spike); // Use hit box collision instead of sprite collision if (currentIntersection) { // Bird hit a spike - game over! LK.effects.flashScreen(0xFF0000, 500); LK.showGameOver(); break; } spike.lastWasIntersecting = currentIntersection; } // Check if bird flew off screen (game over condition) if (bird.y < -100 || bird.y > gameHeight + 100 || bird.x < -100 || bird.x > gameWidth + 100) { LK.effects.flashScreen(0xFF0000, 500); LK.showGameOver(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
selectedSkin: "bird",
unlockedSkins: ["bird"]
});
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
// Get the selected skin
var selectedSkin = storage.selectedSkin || "bird";
// Bird asset - use the correct asset based on the selected skin
var birdGraphics = self.attachAsset(selectedSkin, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
// Add a bigger hitbox for collision detection while keeping visual size
self.hitArea = new Rectangle(-25, -25, 50, 50);
// Set special flag for rainbow bird
if (selectedSkin === "rainbow_bird") {
self.isRainbow = true; // Will be handled in update
}
// Bird physics properties
self.velocityY = 0;
self.velocityX = 12; // Increased from 8 to 12 for faster horizontal movement
self.gravity = 1.0; // Increased from 0.7 to 1.0 for stronger gravity
self.flapStrength = -20; // Increased from -15 to -20 for higher jumps
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Handle tap/click on bird
self.down = function (x, y, obj) {
self.flap();
};
// Make bird flap wings
self.flap = function () {
self.velocityY = self.flapStrength;
// Play flap sound
LK.getSound('flap').play();
// Make rainbow bird do a backflip (360 degree rotation) when flapping
if (self.isRainbow) {
// Stop any existing rotation tweens
tween.stop(birdGraphics, {
rotation: true
});
// Store the current direction (positive or negative scaleX)
var currentDirection = Math.sign(birdGraphics.scaleX);
// Perform a complete 360-degree backflip
tween(birdGraphics, {
rotation: birdGraphics.rotation + currentDirection * Math.PI * 2 // Full rotation in the correct direction
}, {
duration: 500,
// Half a second for the flip
easing: tween.easeOut
});
}
};
// Update bird physics
self.update = function () {
// Store last position
self.lastX = self.x;
self.lastY = self.y;
// Apply gravity
self.velocityY += self.gravity;
// Update position
self.y += self.velocityY;
self.x += self.velocityX;
// Make sure hitArea is always centered on the bird (follows the bird's position)
// No need to update position since the hitArea is relative to the bird's coordinate system
// Use a more fair hitbox size - not too big, not too small
self.hitArea = new Rectangle(-30, -30, 60, 60);
// Visual feedback - rotate bird based on velocity
var targetRotation = Math.min(Math.max(self.velocityY / 15, -0.5), 0.5);
// Make bird face the direction it's flying by flipping the sprite
if (self.velocityX > 0) {
birdGraphics.scaleX = Math.abs(birdGraphics.scaleX); // Face right
if (!self.isRainbow || !tween.has(birdGraphics, 'rotation')) {
birdGraphics.rotation = targetRotation; // Apply rotation normally for right-facing bird
}
} else {
birdGraphics.scaleX = -Math.abs(birdGraphics.scaleX); // Face left
if (!self.isRainbow || !tween.has(birdGraphics, 'rotation')) {
birdGraphics.rotation = -targetRotation; // Invert rotation for left-facing bird
}
}
// Rainbow effect is now handled by the rainbow bird asset
// We can still animate it slightly for visual effect
if (self.isRainbow) {
// Apply subtle visual effects for rainbow bird
var pulseAmount = Math.sin(LK.ticks / 10) * 0.1;
// Maintain the correct direction by checking current scaleX sign
var scaleXDirection = Math.sign(birdGraphics.scaleX);
var baseScale = 2.5; // Match the original scale set in constructor
// Apply pulse effect while preserving direction
birdGraphics.scale.set(scaleXDirection * (baseScale + pulseAmount), baseScale + pulseAmount);
}
};
return self;
});
var Seed = Container.expand(function () {
var self = Container.call(this);
// Seed asset
var seedGraphics = self.attachAsset('seed', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 3.5
});
// Seed properties
self.lastWasIntersecting = false;
self.speedX = 0; // Set speed to 0 to stop seeds from moving
// Animation effect
self.update = function () {
// No longer updating x position
seedGraphics.rotation += 0.05;
// Remove seeds that are off-screen
if (self.x < -50) {
self.shouldBeRemoved = true;
}
};
return self;
});
var ShopScreen = Container.expand(function () {
var self = Container.call(this);
// Background panel
var panel = self.attachAsset('shape', {
width: 1800,
height: 2200,
color: 0x4682B4,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
panel.alpha = 0.9;
// Title
var titleText = new Text2('SKIN SHOP', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 0;
titleText.y = -950;
self.addChild(titleText);
// Coins display
var coinsText = new Text2('Coins: ' + storage.coins, {
size: 80,
fill: 0xFFD700
});
coinsText.anchor.set(0.5, 0);
coinsText.x = 0;
coinsText.y = -800;
self.addChild(coinsText);
// Skins configuration
var skins = [{
id: "bird",
name: "Default Bird",
price: 0,
x: -500,
y: -500
}, {
id: "red_bird",
name: "Red Bird",
price: 20,
x: 0,
y: -500
}, {
id: "gold_bird",
name: "Gold Bird",
price: 40,
x: 500,
y: -500
}, {
id: "blue_bird",
name: "Blue Bird",
price: 60,
x: -500,
y: 0
}, {
id: "green_bird",
name: "Green Bird",
price: 80,
x: 0,
y: 0
}, {
id: "rainbow_bird",
name: "Rainbow Bird",
price: 100,
x: 500,
y: 0
}];
// Selected skin indicator
var selectedIndicator = self.attachAsset('shape', {
width: 350,
height: 350,
color: 0xFFD700,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
selectedIndicator.alpha = 0.5;
selectedIndicator.visible = false;
// Skin buttons
var skinButtons = [];
for (var i = 0; i < skins.length; i++) {
createSkinButton(skins[i]);
}
function createSkinButton(skin) {
var button = new Container();
// Button background
var bg = button.attachAsset('shape', {
width: 300,
height: 300,
color: 0x87CEEB,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Bird icon using the specific skin asset
var birdIcon = button.attachAsset(skin.id, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// Set rainbow flag if needed
if (skin.id === "rainbow_bird") {
button.isRainbow = true;
}
// Price/status text
var priceText = new Text2(skin.price === 0 ? "FREE" : skin.price + " coins", {
size: 40,
fill: 0xFFFFFF
});
priceText.anchor.set(0.5, 0);
priceText.y = 80;
button.addChild(priceText);
// Set button position
button.x = skin.x;
button.y = skin.y;
button.skinId = skin.id;
button.skinName = skin.name;
button.price = skin.price;
// Check if skin is unlocked
var unlockedSkins = storage.unlockedSkins || ["bird"];
if (unlockedSkins.indexOf(skin.id) !== -1) {
button.isUnlocked = true;
priceText.setText("UNLOCKED");
} else {
button.isUnlocked = false;
}
// Handle button click
button.down = function (x, y, obj) {
if (button.isUnlocked) {
// Select this skin
storage.selectedSkin = button.skinId;
self.updateSelectedIndicator();
if (self.onSkinSelected) {
self.onSkinSelected(button.skinId);
}
} else if (storage.coins >= button.price) {
// Buy this skin
storage.coins -= button.price;
if (!storage.unlockedSkins) {
storage.unlockedSkins = ["bird"];
}
storage.unlockedSkins.push(button.skinId);
button.isUnlocked = true;
priceText.setText("UNLOCKED");
coinsText.setText("Coins: " + storage.coins);
}
};
self.addChild(button);
skinButtons.push(button);
return button;
}
// Close button
var closeButton = new Container();
var closeBg = closeButton.attachAsset('shape', {
width: 500,
height: 120,
color: 0xFF0000,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
var closeText = new Text2('CLOSE', {
size: 80,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = 0;
closeButton.y = 800;
closeButton.down = function () {
if (self.onClose) {
self.onClose();
}
};
self.addChild(closeButton);
// Update which skin is selected
self.updateSelectedIndicator = function () {
var currentSkin = storage.selectedSkin || "bird";
for (var i = 0; i < skinButtons.length; i++) {
if (skinButtons[i].skinId === currentSkin) {
selectedIndicator.x = skinButtons[i].x;
selectedIndicator.y = skinButtons[i].y;
selectedIndicator.visible = true;
break;
}
}
};
// Position shop in the center
self.x = gameWidth / 2;
self.y = gameHeight / 2;
// Update rainbow skin colors and selection indicator
self.update = function () {
// Rainbow effect animation for rainbow skin buttons
for (var i = 0; i < skinButtons.length; i++) {
if (skinButtons[i].isRainbow) {
// Instead of tinting, apply a subtle scale animation
var pulseAmount = Math.sin(LK.ticks / 10) * 0.1;
// Find the bird icon within the button
for (var j = 0; j < skinButtons[i].children.length; j++) {
var child = skinButtons[i].children[j];
if (child.isAsset) {
// Animate the rainbow bird icon with a subtle pulse
child.scale.set(2 + pulseAmount, 2 + pulseAmount);
break;
}
}
}
}
};
// Add general down handler to prevent click propagation to elements below
self.down = function (x, y, obj) {
// Catch all clicks on shop screen that aren't caught by buttons
return;
};
// Initialize the selection indicator
self.updateSelectedIndicator();
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
// Create spike shape using a triangle
var spikeGraphics = self.attachAsset('shape', {
width: 200,
height: 200,
color: 0x0000FF,
// Blue color for spike
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Rotate to make it look like a spike
spikeGraphics.rotation = Math.PI / 4; // 45 degrees
self.isStuck = false;
self.wall = null;
self.lastWasIntersecting = false;
// Stick to a wall
self.stickTo = function (wall) {
self.isStuck = true;
self.wall = wall;
// Position on wall edge based on wall orientation
if (wall.isVertical) {
self.x = wall.x + (wall.x < 1024 ? 50 : -50); // Left or right wall - adjusted for wider walls
// Keep y position where the collision happened
} else {
self.y = wall.y + (wall.y < 1366 ? 50 : -50); // Top or bottom wall - adjusted for taller walls
// Keep x position where the collision happened
}
};
self.update = function () {
// Nothing to update when stuck
if (!self.isStuck) {
// If not stuck, spike can move or be collected later
self.rotation += 0.05;
}
};
return self;
});
var StartScreen = Container.expand(function () {
var self = Container.call(this);
// Create title text
var titleText = new Text2('BOUNCE BIRD', {
size: 150,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 3;
self.addChild(titleText);
// Create start button
var startButton = new Container();
var buttonBg = startButton.attachAsset('shape', {
width: 500,
height: 150,
color: 0x4682B4,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
var startText = new Text2('TAP TO START', {
size: 80,
fill: 0xFFFFFF
});
startText.anchor.set(0.5, 0.5);
startButton.addChild(startText);
startButton.x = 2048 / 2;
startButton.y = 2732 / 2;
self.addChild(startButton);
// Create shop button
var shopButton = new Container();
var shopBg = shopButton.attachAsset('shape', {
width: 300,
height: 120,
color: 0x4682B4,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
var shopText = new Text2('SHOP', {
size: 60,
fill: 0xFFFFFF
});
shopText.anchor.set(0.5, 0.5);
shopButton.addChild(shopText);
shopButton.x = 2048 / 2;
shopButton.y = 2732 / 2 + 200;
shopButton.down = function () {
if (self.onShop) {
self.onShop();
}
};
self.addChild(shopButton);
// Add a bouncing bird animation - use the selected skin if available
var skinToShow = storage.selectedSkin || "bird";
var demoBird = self.attachAsset(skinToShow, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 4,
x: 2048 / 2,
y: 2732 * 0.7
});
// Make the button pulse
self.update = function () {
var scale = 1 + Math.sin(LK.ticks / 20) * 0.05;
startButton.scale.set(scale);
// Make the bird bounce
demoBird.y = 2732 * 0.7 + Math.sin(LK.ticks / 15) * 50;
demoBird.rotation = Math.sin(LK.ticks / 30) * 0.2;
};
// Handle tap event
self.down = function () {
if (self.onStart) {
self.onStart();
}
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
// Wall properties
self.isVertical = true;
self.wallGraphics = null;
// Initialize with wall type (vertical/horizontal)
self.init = function (vertical) {
self.isVertical = vertical;
if (vertical) {
self.wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.wallGraphics = self.attachAsset('ceiling', {
anchorX: 0.5,
anchorY: 0.5
});
}
return self;
};
return self;
});
/****
* Initialize Game
****/
// Change background color to sky blue
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Change background color to sky blue
// Assets for Bounce Bird
// Import tween plugin
// Add custom method to check if object has active tweens for a property
tween.has = function (target, property) {
// This is a custom implementation since the tween plugin doesn't have this method
// We'll return false to allow the rotation to be set manually
return false;
};
game.setBackgroundColor(0x87CEEB);
// Game variables
var bird;
var walls = [];
var seeds = [];
var spikes = [];
var score = 0;
var gameWidth = 2048;
var gameHeight = 2732;
var gameStarted = false;
var startScreen;
var shopScreen;
var shopOpen = false;
var coinText;
// Create start screen
function showStartScreen() {
startScreen = new StartScreen();
startScreen.onStart = function () {
gameStarted = true;
startScreen.destroy();
startScreen = null;
startGame();
};
startScreen.onShop = function () {
showShopScreen();
};
game.addChild(startScreen);
}
// Show shop screen
function showShopScreen() {
shopOpen = true;
// If start screen exists, hide it temporarily
if (startScreen) {
startScreen.visible = false;
}
shopScreen = new ShopScreen();
// Add shop background to block events from propagating through
var shopBlocker = new Container();
var blockerBg = shopBlocker.attachAsset('shape', {
width: gameWidth,
height: gameHeight,
color: 0x000000,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
blockerBg.alpha = 0.5; // More visible to ensure it blocks events
shopBlocker.x = gameWidth / 2;
shopBlocker.y = gameHeight / 2;
// Add event handler to block events
shopBlocker.down = function (x, y, obj) {
// This function blocks events from propagating
return;
};
// Add blocker first, then shop on top
game.addChild(shopBlocker);
shopScreen.onClose = function () {
shopOpen = false;
shopScreen.destroy();
shopScreen = null;
shopBlocker.destroy();
// Reset the game state when closing the shop
gameStarted = false;
// Reset game elements if they exist
if (bird) {
bird.destroy();
bird = null;
}
// Clear all game objects
for (var i = 0; i < seeds.length; i++) {
seeds[i].destroy();
}
seeds = [];
for (var i = 0; i < spikes.length; i++) {
spikes[i].destroy();
}
spikes = [];
// Reset score
score = 0;
if (scoreText) {
scoreText.setText("Score: 0");
}
// Update coin display if it exists
if (coinText) {
coinText.setText("Coins: " + storage.coins);
}
// Show the start screen
if (startScreen && !startScreen.visible) {
startScreen.visible = true;
} else {
showStartScreen();
}
};
shopScreen.onSkinSelected = function (skinId) {
// This will be applied next time a bird is created
};
game.addChild(shopScreen);
}
// Initialize the actual game
function startGame() {
createBird();
createWalls();
createScoreDisplay();
}
// Create score text
var scoreText;
function createScoreDisplay() {
scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topLeft.addChild(scoreText);
scoreText.x = 120; // Avoid the top-left 100x100 menu area
scoreText.y = 50;
// Create coin text
coinText = new Text2('Coins: ' + storage.coins, {
size: 60,
fill: 0xFFD700
});
coinText.anchor.set(0, 0);
LK.gui.topLeft.addChild(coinText);
coinText.x = 120; // Avoid the top-left 100x100 menu area
coinText.y = 140;
}
// Create bird
function createBird() {
bird = new Bird();
bird.x = gameWidth / 4;
bird.y = gameHeight / 2;
game.addChild(bird);
}
// Create walls (top, bottom, left, right)
function createWalls() {
// Top wall (ceiling)
var ceiling = new Wall().init(false);
ceiling.x = gameWidth / 2;
ceiling.y = 40;
game.addChild(ceiling);
walls.push(ceiling);
// Bottom wall (floor)
var floor = new Wall().init(false);
floor.x = gameWidth / 2;
floor.y = gameHeight - 40;
game.addChild(floor);
walls.push(floor);
// Left wall
var leftWall = new Wall().init(true);
leftWall.x = 40;
leftWall.y = gameHeight / 2;
game.addChild(leftWall);
walls.push(leftWall);
// Right wall
var rightWall = new Wall().init(true);
rightWall.x = gameWidth - 40;
rightWall.y = gameHeight / 2;
game.addChild(rightWall);
walls.push(rightWall);
}
// Show start screen instead of immediately starting the game
showStartScreen();
// Spawn a new seed at a random position
function spawnSeed() {
var seed = new Seed();
seed.x = gameWidth + 50;
seed.y = Math.random() * (gameHeight - 200) + 100;
seeds.push(seed);
game.addChild(seed);
}
// Handle click/tap anywhere on screen
game.down = function (x, y, obj) {
// First, check if shop is open - don't process any game inputs
if (shopOpen) {
// Don't handle game inputs when shop is open
return;
}
// Add a shop button after game over
if (!gameStarted && !startScreen) {
// This means we're in the game over state
showShopScreen();
return;
}
// Only allow bird flap if game has actually started
if (gameStarted && bird) {
bird.flap();
}
};
// Listen for game over event to preserve our score
LK.on('gameover', function () {
// Game is over but we want to preserve score
// This ensures score is saved when player dies
scoreText.setText("Score: " + score);
LK.setScore(score);
});
// Update function
game.update = function () {
// Don't process game logic if game hasn't started yet or shop is open
if (!gameStarted || shopOpen) {
return;
}
// Seeds now spawn on bounce, not periodically
// Update seeds
for (var i = seeds.length - 1; i >= 0; i--) {
var seed = seeds[i];
// Check for collision with bird
var currentIntersecting = bird.intersects(seed);
if (!seed.lastWasIntersecting && currentIntersecting) {
// Collected seed
score++;
scoreText.setText("Score: " + score);
LK.setScore(score);
// Award a coin for each seed
storage.coins += 1;
coinText.setText("Coins: " + storage.coins);
LK.getSound('collect').play();
// Remove seed
seed.destroy();
seeds.splice(i, 1);
} else {
seed.lastWasIntersecting = currentIntersecting;
// Remove seeds that should be removed
if (seed.shouldBeRemoved) {
seed.destroy();
seeds.splice(i, 1);
}
}
}
// Check for wall collisions
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
var currentIntersecting = bird.intersects(wall);
if (!bird.lastWasIntersecting && currentIntersecting) {
// Check if bird hit ceiling or floor (horizontal walls)
if (!wall.isVertical) {
// Bird hit ceiling or floor - game over!
LK.effects.flashScreen(0xFF0000, 500);
LK.showGameOver();
break;
} else {
// Bird hit a side wall - bounce!
LK.getSound('bounce').play();
// Apply bounce physics
bird.velocityX *= -1; // Reverse horizontal direction
// Push bird away from wall to prevent sticking
bird.x += bird.velocityX > 0 ? 5 : -5;
// Visual feedback for bounce
LK.effects.flashObject(bird, 0xFFFFFF, 200);
// Clear old seeds and spikes on bounce
for (var j = seeds.length - 1; j >= 0; j--) {
seeds[j].destroy();
seeds.splice(j, 1);
}
// Clear all existing spikes
for (var j = spikes.length - 1; j >= 0; j--) {
spikes[j].destroy();
spikes.splice(j, 1);
}
// Reset all wall colors to the original blue color
for (var j = 0; j < walls.length; j++) {
if (walls[j].isVertical) {
tween(walls[j].wallGraphics, {
tint: 0x4682b4
}, {
duration: 300,
easing: tween.easeIn
});
}
}
// Spawn two new seeds on bounce
for (var k = 0; k < 2; k++) {
var newSeed = new Seed();
// Make seeds spawn more in the middle - use narrower range centered around the middle
var middleX = gameWidth / 2;
var middleY = gameHeight / 2;
var spawnWidth = gameWidth * 0.6; // Use 60% of screen width
var spawnHeight = gameHeight * 0.6; // Use 60% of screen height
newSeed.x = middleX + (Math.random() - 0.5) * spawnWidth;
newSeed.y = middleY + (Math.random() - 0.5) * spawnHeight;
seeds.push(newSeed);
game.addChild(newSeed);
}
// Add a spike that sticks to the opposite wall
var spike = new Spike();
// Place spike on opposite vertical wall
targetWall = wall.x < gameWidth / 2 ? walls[3] : walls[2]; // walls[3] is right wall, walls[2] is left wall
spike.x = targetWall.x;
spike.y = Math.random() * (gameHeight - 200) + 100; // Random Y position
spike.stickTo(targetWall);
spikes.push(spike);
game.addChild(spike);
// Change the target wall color to match the spike color (blue)
tween(targetWall.wallGraphics, {
tint: 0x0000FF
}, {
duration: 300,
easing: tween.easeOut
});
}
// Set lastWasIntersecting for the specific wall collision that just happened
bird.lastWasIntersecting = true;
break; // Exit the loop after handling a collision
}
}
// Reset lastWasIntersecting if not intersecting any wall
var intersectingAnyWall = false;
for (var i = 0; i < walls.length; i++) {
if (bird.intersects(walls[i])) {
intersectingAnyWall = true;
break;
}
}
if (!intersectingAnyWall) {
bird.lastWasIntersecting = false;
}
// Check for spike collisions
for (var i = 0; i < spikes.length; i++) {
var spike = spikes[i];
// Use bird's intersects method for collision detection
var currentIntersection = bird.intersects(spike);
// Use hit box collision instead of sprite collision
if (currentIntersection) {
// Bird hit a spike - game over!
LK.effects.flashScreen(0xFF0000, 500);
LK.showGameOver();
break;
}
spike.lastWasIntersecting = currentIntersection;
}
// Check if bird flew off screen (game over condition)
if (bird.y < -100 || bird.y > gameHeight + 100 || bird.x < -100 || bird.x > gameWidth + 100) {
LK.effects.flashScreen(0xFF0000, 500);
LK.showGameOver();
}
};
Big sunflower seed. In-Game asset. 2d. High contrast. No shadows
Square shaped gold bird. In-Game asset. 2d. High contrast. No shadows
Square shaped green bird facing to the right. In-Game asset. 2d. High contrast. No shadows
Square shaped orange bird facing right
Square shaped red bird facing right. In-Game asset. 2d. High contrast. No shadows
Square shaped rainbow bird facing right. In-Game asset. 2d. High contrast. No shadows