/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Bird class var Bird = Container.expand(function () { var self = Container.call(this); var birdSprite = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); var birdDrinkSprite = self.attachAsset('Bird_drink', { anchorX: 0.5, anchorY: 0.5, visible: false }); var drink2Sprite = self.attachAsset('Drink_2', { anchorX: 0.5, anchorY: 0.5, visible: false }); var drink3Sprite = self.attachAsset('Drink3', { anchorX: 0.5, anchorY: 0.5, visible: false }); // Position drink in front of bird birdDrinkSprite.x = birdSprite.width * 0.7; // Offset to the right (in front) drink2Sprite.x = birdDrinkSprite.x; // Align drink2Sprite with birdDrinkSprite drink3Sprite.x = birdDrinkSprite.x; // Align drink3Sprite with birdDrinkSprite self.currentSprite = birdSprite; self.radius = birdSprite.width * 0.5 * 0.85; // for collision self.vy = 0; // vertical speed self.gravity = 0.6; // gravity per frame (slower fall) self.flapStrength = -22; // negative = up, less strong flap // Flap method self.flap = function () { self.vy = self.flapStrength; LK.getSound('flap').play(); }; // Toggle drink method self.toggleDrink = function () { // Cycle through no drink, bird drink, drink 2, and drink 3 if (!birdDrinkSprite.visible && !drink2Sprite.visible && !drink3Sprite.visible) { birdDrinkSprite.visible = true; drink2Sprite.visible = false; drink3Sprite.visible = false; } else if (birdDrinkSprite.visible) { birdDrinkSprite.visible = false; drink2Sprite.visible = true; drink3Sprite.visible = false; } else if (drink2Sprite.visible) { birdDrinkSprite.visible = false; drink2Sprite.visible = false; drink3Sprite.visible = true; } else { birdDrinkSprite.visible = false; drink2Sprite.visible = false; drink3Sprite.visible = false; } // Save the current drink state to storage storage.lastDrink = birdDrinkSprite.visible ? 1 : drink2Sprite.visible ? 2 : 0; }; // Update method self.update = function () { self.vy += self.gravity * (slowTimeActive ? slowTimeMultiplier : 1); self.y += self.vy; // Rotate bird based on velocity (visual only) var maxAngle = Math.PI / 4; var minAngle = -Math.PI / 6; var angle = self.vy / 40 * maxAngle; if (angle > maxAngle) angle = maxAngle; if (angle < minAngle) angle = minAngle; birdSprite.rotation = angle; // Keep drink upright and in front birdDrinkSprite.rotation = 0; }; return self; }); // Fire class for standalone fire effects var Fire = Container.expand(function () { var self = Container.call(this); var fireSprite = self.attachAsset('fire', { anchorX: 0.5, anchorY: 0.5, visible: false, alpha: 0.5 }); self.shootFire = function () { fireSprite.visible = true; fireSprite.x = 0; // Reset position when shooting fireSprite.y = 0; tween(fireSprite, { alpha: 1 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { tween(fireSprite, { alpha: 0.5 }, { duration: 500, easing: tween.linear }); } }); }; self.getBoundingRect = function () { if (!fireSprite.visible) return null; return { x: self.x + fireSprite.x - fireSprite.width * 0.5, y: self.y + fireSprite.y - fireSprite.height * 0.5, width: fireSprite.width, height: fireSprite.height }; }; self.update = function () { if (fireSprite.visible) { fireSprite.x -= pipeSpeed; if (fireSprite.x < -fireSprite.width) { fireSprite.visible = false; } } }; return self; }); var FirePipe = Container.expand(function () { var self = Container.call(this); var firePipeSprite = self.attachAsset('FirePipe', { anchorX: 0.5, anchorY: 0.5, visible: true, alpha: 1 }); self.movingRight = true; // Track movement direction self.startMovement = function () { // Start continuous left-right movement self.moveLeftRight(); }; self.moveLeftRight = function () { // Check if this pipe is frozen if (self.frozen) return; var targetX = self.movingRight ? 300 : -300; // Move 300px in each direction tween(firePipeSprite, { x: targetX }, { duration: 2000, // 2 seconds to move in one direction easing: tween.easeInOut, onFinish: function onFinish() { // Reverse direction and continue moving self.movingRight = !self.movingRight; self.moveLeftRight(); } }); }; self.freeze = function () { self.frozen = true; // Add visual effect for frozen state tween(firePipeSprite, { tint: 0x88ccff }, { duration: 300, easing: tween.easeOut }); }; self.unfreeze = function () { self.frozen = false; // Remove visual effect tween(firePipeSprite, { tint: 0xffffff }, { duration: 300, easing: tween.easeOut }); // Resume movement self.moveLeftRight(); }; self.getBoundingRect = function () { if (!firePipeSprite.visible) return null; return { x: self.x + firePipeSprite.x - firePipeSprite.width * 0.5, y: self.y + firePipeSprite.y - firePipeSprite.height * 0.5, width: firePipeSprite.width, height: firePipeSprite.height }; }; self.update = function () { // Movement is now handled by tween }; return self; }); // PipePair class (top and bottom pipes) var PipePair = Container.expand(function () { var self = Container.call(this); // Pipe config var pipeWidth = 200; var pipeHeight = 1200; var gapHeight = 480; // vertical gap between pipes // Randomly decide if pipes are flipped (top/bottom swap orientation) var flip = Math.random() < 0.5; // Randomly decide pipe type: normal (70%) or fire (30%) var isFirePipe = Math.random() < 0.3; // For fire pipes, always single pipe. For regular pipes, 30% chance of single pipe var isSinglePipe = isFirePipe || Math.random() < 0.3; var pipeAsset = isFirePipe ? 'FirePipe' : 'pipe'; var topPipe = null; var bottomPipe = null; // Create pipes based on configuration if (isSinglePipe) { // Single pipe - randomly choose top or bottom position var isTopPosition = Math.random() < 0.5; if (isTopPosition) { topPipe = self.attachAsset(pipeAsset, { anchorX: 0, anchorY: flip ? 0 : 1, width: pipeWidth, height: pipeHeight, rotation: flip ? Math.PI : 0 }); } else { bottomPipe = self.attachAsset(pipeAsset, { anchorX: 0, anchorY: flip ? 1 : 0, width: pipeWidth, height: pipeHeight, rotation: flip ? Math.PI : 0 }); } } else { // Double pipes (traditional setup) topPipe = self.attachAsset(pipeAsset, { anchorX: 0, anchorY: flip ? 0 : 1, width: pipeWidth, height: pipeHeight, rotation: flip ? Math.PI : 0 }); bottomPipe = self.attachAsset(pipeAsset, { anchorX: 0, anchorY: flip ? 1 : 0, width: pipeWidth, height: pipeHeight, rotation: flip ? Math.PI : 0 }); } self.pipeWidth = pipeWidth; self.gapHeight = gapHeight; self.passed = false; // for scoring self.isFirePipe = isFirePipe; self.isSinglePipe = isSinglePipe; self.fireTimer = 0; self.topPipe = topPipe; self.bottomPipe = bottomPipe; // Set pipes' vertical positions based on gapY (top of gap) self.setGap = function (gapY) { // gapY: y position of top of gap if (topPipe) topPipe.y = gapY; if (bottomPipe) bottomPipe.y = gapY + gapHeight; }; // Move pipes left self.update = function () { self.x -= pipeSpeed; }; // Get bounding rects for collision self.getTopRect = function () { if (!topPipe) return null; return { x: self.x, y: topPipe.y - pipeHeight, width: pipeWidth, height: pipeHeight }; }; self.getBottomRect = function () { if (!bottomPipe) return null; return { x: self.x, y: bottomPipe.y, width: pipeWidth, height: pipeHeight }; }; // Add fire shooting capability for fire pipes if (isFirePipe) { var firePipe = new FirePipe(); self.addChild(firePipe); self.firePipe = firePipe; self.fireStarted = false; // Track if fire has been started } self.update = function () { self.x -= pipeSpeed * (slowTimeActive ? slowTimeMultiplier : 1); // Update fire pipe if this is a fire pipe if (self.isFirePipe && self.firePipe) { self.firePipe.x = self.x; // Position fire at pipe position // Center fire in the gap or at pipe level for single pipes var fireY = bottomPipe ? bottomPipe.y - gapHeight / 2 : topPipe ? topPipe.y + gapHeight / 2 : 1366; self.firePipe.y = fireY; self.firePipe.update(); // Start movement when pipe enters screen (only once) if (!self.fireStarted && self.x < 2048) { self.fireStarted = true; // Check if power-up is active and freeze immediately if (slowTimeActive) { self.firePipe.freeze(); frozenPipes.push(self.firePipe); } else { self.firePipe.startMovement(); } } } }; return self; }); // PowerUp class var PowerUp = Container.expand(function () { var self = Container.call(this); // Create a star-shaped power-up visual using new asset var powerUpSprite = self.attachAsset('powerUpStar', { anchorX: 0.5, anchorY: 0.5, tint: 0x00ffff // Cyan color for freeze power-up }); // Add pulsing effect self.pulse = function () { tween(powerUpSprite, { scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(powerUpSprite, { scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { self.pulse(); } }); } }); }; self.collected = false; self.update = function () { if (!self.collected) { self.x -= pipeSpeed * (slowTimeActive ? slowTimeMultiplier : 1); } }; self.getBoundingRect = function () { return { x: self.x - powerUpSprite.width * 0.5, y: self.y - powerUpSprite.height * 0.5, width: powerUpSprite.width, height: powerUpSprite.height }; }; // Start pulsing animation self.pulse(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // sky blue }); /**** * Game Code ****/ // Sound: Hit // Sound: Score // Sound: Flap // Ground: brown box // Pipe: green box // Bird: yellow ellipse // Game constants var BIRD_START_X = 600; var BIRD_START_Y = 1200; var PIPE_INTERVAL = 1300; // px between pipes horizontally (slower spawn rate) var PIPE_MIN_Y = 350; // min y for top of gap var PIPE_MAX_Y = 1800; // max y for top of gap var GROUND_Y = 2732 - 120; // ground top y var pipeSpeed = 8; // px per frame (slower pipes) var POWERUP_DURATION = 30000; // 30 seconds in milliseconds var slowTimeMultiplier = 0.3; // 30% speed during slow time // Game state var bird; var pipes = []; var powerUps = []; var ground; var score = 0; var scoreTxt; var gameOver = false; var started = false; var lastPipeX = 0; var slowTimeActive = false; var slowTimeTimer = null; var frozenPipes = []; // GUI: Score scoreTxt = new Text2('0', { size: 160, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // GUI: Game Over text var gameOverTxt = new Text2('Game Over', { size: 180, fill: 0xFF4444 }); gameOverTxt.anchor.set(0.5, 0.5); gameOverTxt.visible = false; LK.gui.center.addChild(gameOverTxt); // GUI: Tap to restart var restartTxt = new Text2('Tap to restart', { size: 100, fill: 0xFFFFFF }); restartTxt.anchor.set(0.5, 0.5); restartTxt.visible = false; LK.gui.center.addChild(restartTxt); // GUI: Drink toggle button var drinkToggleBtn = new Container(); var drinkBtnBg = drinkToggleBtn.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150, tint: 0x4444ff }); var drinkBtnText = new Text2('🥤', { size: 80, fill: 0xFFFFFF }); drinkBtnText.anchor.set(0.5, 0.5); drinkToggleBtn.addChild(drinkBtnText); drinkToggleBtn.x = -100; drinkToggleBtn.y = 100; LK.gui.topRight.addChild(drinkToggleBtn); // GUI: Reset score button var resetScoreBtn = new Container(); var resetBtnBg = resetScoreBtn.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150, tint: 0xff4444 }); var resetBtnText = new Text2('Ø', { size: 100, fill: 0xFFFFFF }); resetBtnText.anchor.set(0.5, 0.5); resetScoreBtn.addChild(resetBtnText); resetScoreBtn.x = -100; resetScoreBtn.y = 280; LK.gui.topRight.addChild(resetScoreBtn); // GUI: Bird swapper button var birdSwapBtn = new Container(); var birdSwapBtnBg = birdSwapBtn.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150, tint: 0x44ff44 }); var birdSwapIcon = birdSwapBtn.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); birdSwapBtn.x = -100; birdSwapBtn.y = 460; LK.gui.topRight.addChild(birdSwapBtn); // Start spinning animation for reset button function spinResetButton() { tween(resetBtnText, { rotation: resetBtnText.rotation + Math.PI * 2 }, { duration: 2000, easing: tween.linear, onFinish: function onFinish() { spinResetButton(); } }); } spinResetButton(); // GUI: Power-up indicator var powerUpIndicator = new Container(); var powerUpBg = powerUpIndicator.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 80, tint: 0x00ffff }); var powerUpText = new Text2('FREEZE: 30s', { size: 40, fill: 0xFFFFFF }); powerUpText.anchor.set(0.5, 0.5); powerUpIndicator.addChild(powerUpText); powerUpIndicator.x = 0; powerUpIndicator.y = 200; powerUpIndicator.visible = false; LK.gui.top.addChild(powerUpIndicator); // Drink toggle handler drinkToggleBtn.down = function () { if (bird) { bird.toggleDrink(); } }; // Reset score handler resetScoreBtn.down = function () { score = 0; scoreTxt.setText('0'); storage.lastScore = 0; // Flash effect to indicate reset LK.effects.flashObject(resetScoreBtn, 0xffffff, 300); }; // Bird swap handler birdSwapBtn.down = function () { if (bird) { // Get current bird type from storage or default to 'bird' var currentBirdType = storage.currentBirdType || 'bird'; var newBirdType = currentBirdType === 'bird' ? 'Bird2' : 'bird'; // Store current drink state var drinkState = { drink1: bird.children[1].visible, drink2: bird.children[2].visible, drink3: bird.children[3].visible }; // Remove current main bird sprite var currentBird = bird.children[0]; bird.removeChild(currentBird); // Create new bird sprite var newBird = LK.getAsset(newBirdType, { anchorX: 0.5, anchorY: 0.5 }); bird.addChildAt(newBird, 0); bird.currentSprite = newBird; // Update rotation to match current velocity var maxAngle = Math.PI / 4; var minAngle = -Math.PI / 6; var angle = bird.vy / 40 * maxAngle; if (angle > maxAngle) angle = maxAngle; if (angle < minAngle) angle = minAngle; newBird.rotation = angle; // Restore drink state bird.children[1].visible = drinkState.drink1; bird.children[2].visible = drinkState.drink2; bird.children[3].visible = drinkState.drink3; // Save new bird type storage.currentBirdType = newBirdType; // Flash effect to indicate swap LK.effects.flashObject(birdSwapBtn, 0xffffff, 300); } }; // Add ground ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: GROUND_Y }); game.addChild(ground); // Helper: Reset game state function resetGame() { // Remove pipes for (var i = 0; i < pipes.length; i++) { pipes[i].destroy(); } pipes = []; // Remove power-ups for (var i = 0; i < powerUps.length; i++) { powerUps[i].destroy(); } powerUps = []; // Clear power-up state if (slowTimeTimer) { LK.clearTimeout(slowTimeTimer); slowTimeTimer = null; } slowTimeActive = false; powerUpIndicator.visible = false; frozenPipes = []; // Remove bird if exists if (bird) bird.destroy(); // Reset variables score = 0; scoreTxt.setText('0'); gameOver = false; started = false; lastPipeX = 0; // Hide game over gameOverTxt.visible = false; restartTxt.visible = false; // Add bird bird = new Bird(); bird.x = BIRD_START_X; bird.y = BIRD_START_Y; game.addChild(bird); // Restore score if available if (typeof storage.lastScore !== "undefined") { score = storage.lastScore; scoreTxt.setText(score + ''); } // Restore bird type if available if (typeof storage.currentBirdType !== "undefined" && storage.currentBirdType === 'Bird2') { var currentBird = bird.children[0]; var newBird = LK.getAsset('Bird2', { anchorX: 0.5, anchorY: 0.5 }); bird.removeChild(currentBird); bird.addChildAt(newBird, 0); bird.currentSprite = newBird; } // Restore drink state if available if (typeof storage.lastDrink !== "undefined") { if (storage.lastDrink === 1) { bird.children[1].visible = true; bird.children[2].visible = false; } else if (storage.lastDrink === 2) { bird.children[1].visible = false; bird.children[2].visible = true; } else if (storage.lastDrink === 3) { bird.children[1].visible = false; bird.children[2].visible = false; bird.children[3].visible = true; } else { bird.children[1].visible = false; bird.children[2].visible = false; bird.children[3].visible = false; } } // Add first pipes for (var i = 0; i < 3; i++) { spawnPipe(2048 + i * PIPE_INTERVAL); } } // Helper: Spawn a pipe pair at x function spawnPipe(x) { var gapY = PIPE_MIN_Y + Math.floor(Math.random() * (PIPE_MAX_Y - PIPE_MIN_Y)); var pipePair = new PipePair(); pipePair.x = x; pipePair.setGap(gapY); pipes.push(pipePair); game.addChild(pipePair); lastPipeX = x; // 10% chance to spawn power-up with pipe if (Math.random() < 0.1 && !slowTimeActive) { var powerUp = new PowerUp(); powerUp.x = x + pipePair.pipeWidth / 2; powerUp.y = gapY + pipePair.gapHeight / 2; powerUps.push(powerUp); game.addChild(powerUp); } } // Helper: Check collision between bird and a pipe rect function birdHitsRect(rect) { // Return false if rect is null (e.g., when fire sprite is not visible) if (!rect) return false; // Bird is a circle, rect is {x, y, width, height} var cx = bird.x; var cy = bird.y; var r = bird.radius; // Find closest point in rect to bird center var closestX = cx; if (cx < rect.x) closestX = rect.x;else if (cx > rect.x + rect.width) closestX = rect.x + rect.width; var closestY = cy; if (cy < rect.y) closestY = rect.y;else if (cy > rect.y + rect.height) closestY = rect.y + rect.height; var dx = cx - closestX; var dy = cy - closestY; return dx * dx + dy * dy < r * r; } // Helper: Activate power-up function activatePowerUp() { slowTimeActive = true; powerUpIndicator.visible = true; // Freeze all fire pipes for (var i = 0; i < pipes.length; i++) { if (pipes[i].isFirePipe && pipes[i].firePipe) { pipes[i].firePipe.freeze(); frozenPipes.push(pipes[i].firePipe); } } // Start countdown var timeLeft = POWERUP_DURATION / 1000; var countdownInterval = LK.setInterval(function () { timeLeft--; powerUpText.setText('FREEZE: ' + timeLeft + 's'); if (timeLeft <= 0) { LK.clearInterval(countdownInterval); } }, 1000); // Set timer to deactivate power-up if (slowTimeTimer) { LK.clearTimeout(slowTimeTimer); } slowTimeTimer = LK.setTimeout(function () { slowTimeActive = false; powerUpIndicator.visible = false; // Unfreeze all frozen pipes for (var i = 0; i < frozenPipes.length; i++) { frozenPipes[i].unfreeze(); } frozenPipes = []; LK.clearInterval(countdownInterval); }, POWERUP_DURATION); } // Helper: End game function triggerGameOver() { if (gameOver) return; gameOver = true; // Save score and drink state to storage storage.lastScore = score; // Access birdDrinkSprite via bird instance if available storage.lastDrink = bird && bird.hasOwnProperty('children') && bird.children.length > 1 ? bird.children[1].visible ? 1 : bird.children[2].visible ? 2 : bird.children[3].visible ? 3 : 0 : 0; LK.getSound('hit').play(); gameOverTxt.visible = true; restartTxt.visible = true; // Flash screen LK.effects.flashScreen(0xff0000, 600); // Show Game Over popup (will pause game) // LK.showGameOver(); } // Game tap/flap handler function handleTap(x, y, obj) { if (gameOver) { resetGame(); return; } if (!started) { started = true; } bird.flap(); } // Attach tap handler game.down = handleTap; // Main update loop game.update = function () { if (gameOver) return; // Bird update bird.update(); // Pipes update for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; pipe.update(); // Remove pipes off screen if (pipe.x + pipe.pipeWidth < 0) { pipe.destroy(); pipes.splice(i, 1); continue; } // Scoring: passed pipe if (!pipe.passed && pipe.x + pipe.pipeWidth < bird.x) { pipe.passed = true; score += 1; scoreTxt.setText(score + ''); LK.getSound('score').play(); } } // Spawn new pipes if (pipes.length > 0) { var rightmost = pipes[pipes.length - 1]; if (2048 - (rightmost.x + rightmost.pipeWidth) >= PIPE_INTERVAL - 10) { spawnPipe(2048); } } // Power-ups update and collision for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; powerUp.update(); // Remove power-ups off screen if (powerUp.x + 40 < 0) { powerUp.destroy(); powerUps.splice(i, 1); continue; } // Check collision with bird if (!powerUp.collected && birdHitsRect(powerUp.getBoundingRect())) { powerUp.collected = true; powerUp.visible = false; activatePowerUp(); LK.getSound('score').play(); } } // Collision: pipes for (var i = 0; i < pipes.length; i++) { var pipe = pipes[i]; var hitTopPipe = birdHitsRect(pipe.getTopRect()); var hitBottomPipe = birdHitsRect(pipe.getBottomRect()); var hitPipe = hitTopPipe || hitBottomPipe; var hitFire = pipe.isFirePipe && pipe.firePipe && pipe.firePipe.getBoundingRect && birdHitsRect(pipe.firePipe.getBoundingRect()); if (hitPipe || hitFire) { triggerGameOver(); return; } } // Collision: ground if (bird.y + bird.radius > GROUND_Y) { bird.y = GROUND_Y - bird.radius; triggerGameOver(); return; } // Collision: ceiling if (bird.y - bird.radius < 0) { bird.y = bird.radius; bird.vy = 0; } // Out of bounds (left/right) if (bird.x - bird.radius < 0 || bird.x + bird.radius > 2048) { triggerGameOver(); return; } }; // Initial game state resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdSprite = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
var birdDrinkSprite = self.attachAsset('Bird_drink', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
var drink2Sprite = self.attachAsset('Drink_2', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
var drink3Sprite = self.attachAsset('Drink3', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
// Position drink in front of bird
birdDrinkSprite.x = birdSprite.width * 0.7; // Offset to the right (in front)
drink2Sprite.x = birdDrinkSprite.x; // Align drink2Sprite with birdDrinkSprite
drink3Sprite.x = birdDrinkSprite.x; // Align drink3Sprite with birdDrinkSprite
self.currentSprite = birdSprite;
self.radius = birdSprite.width * 0.5 * 0.85; // for collision
self.vy = 0; // vertical speed
self.gravity = 0.6; // gravity per frame (slower fall)
self.flapStrength = -22; // negative = up, less strong flap
// Flap method
self.flap = function () {
self.vy = self.flapStrength;
LK.getSound('flap').play();
};
// Toggle drink method
self.toggleDrink = function () {
// Cycle through no drink, bird drink, drink 2, and drink 3
if (!birdDrinkSprite.visible && !drink2Sprite.visible && !drink3Sprite.visible) {
birdDrinkSprite.visible = true;
drink2Sprite.visible = false;
drink3Sprite.visible = false;
} else if (birdDrinkSprite.visible) {
birdDrinkSprite.visible = false;
drink2Sprite.visible = true;
drink3Sprite.visible = false;
} else if (drink2Sprite.visible) {
birdDrinkSprite.visible = false;
drink2Sprite.visible = false;
drink3Sprite.visible = true;
} else {
birdDrinkSprite.visible = false;
drink2Sprite.visible = false;
drink3Sprite.visible = false;
}
// Save the current drink state to storage
storage.lastDrink = birdDrinkSprite.visible ? 1 : drink2Sprite.visible ? 2 : 0;
};
// Update method
self.update = function () {
self.vy += self.gravity * (slowTimeActive ? slowTimeMultiplier : 1);
self.y += self.vy;
// Rotate bird based on velocity (visual only)
var maxAngle = Math.PI / 4;
var minAngle = -Math.PI / 6;
var angle = self.vy / 40 * maxAngle;
if (angle > maxAngle) angle = maxAngle;
if (angle < minAngle) angle = minAngle;
birdSprite.rotation = angle;
// Keep drink upright and in front
birdDrinkSprite.rotation = 0;
};
return self;
});
// Fire class for standalone fire effects
var Fire = Container.expand(function () {
var self = Container.call(this);
var fireSprite = self.attachAsset('fire', {
anchorX: 0.5,
anchorY: 0.5,
visible: false,
alpha: 0.5
});
self.shootFire = function () {
fireSprite.visible = true;
fireSprite.x = 0; // Reset position when shooting
fireSprite.y = 0;
tween(fireSprite, {
alpha: 1
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
tween(fireSprite, {
alpha: 0.5
}, {
duration: 500,
easing: tween.linear
});
}
});
};
self.getBoundingRect = function () {
if (!fireSprite.visible) return null;
return {
x: self.x + fireSprite.x - fireSprite.width * 0.5,
y: self.y + fireSprite.y - fireSprite.height * 0.5,
width: fireSprite.width,
height: fireSprite.height
};
};
self.update = function () {
if (fireSprite.visible) {
fireSprite.x -= pipeSpeed;
if (fireSprite.x < -fireSprite.width) {
fireSprite.visible = false;
}
}
};
return self;
});
var FirePipe = Container.expand(function () {
var self = Container.call(this);
var firePipeSprite = self.attachAsset('FirePipe', {
anchorX: 0.5,
anchorY: 0.5,
visible: true,
alpha: 1
});
self.movingRight = true; // Track movement direction
self.startMovement = function () {
// Start continuous left-right movement
self.moveLeftRight();
};
self.moveLeftRight = function () {
// Check if this pipe is frozen
if (self.frozen) return;
var targetX = self.movingRight ? 300 : -300; // Move 300px in each direction
tween(firePipeSprite, {
x: targetX
}, {
duration: 2000,
// 2 seconds to move in one direction
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reverse direction and continue moving
self.movingRight = !self.movingRight;
self.moveLeftRight();
}
});
};
self.freeze = function () {
self.frozen = true;
// Add visual effect for frozen state
tween(firePipeSprite, {
tint: 0x88ccff
}, {
duration: 300,
easing: tween.easeOut
});
};
self.unfreeze = function () {
self.frozen = false;
// Remove visual effect
tween(firePipeSprite, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
// Resume movement
self.moveLeftRight();
};
self.getBoundingRect = function () {
if (!firePipeSprite.visible) return null;
return {
x: self.x + firePipeSprite.x - firePipeSprite.width * 0.5,
y: self.y + firePipeSprite.y - firePipeSprite.height * 0.5,
width: firePipeSprite.width,
height: firePipeSprite.height
};
};
self.update = function () {
// Movement is now handled by tween
};
return self;
});
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe config
var pipeWidth = 200;
var pipeHeight = 1200;
var gapHeight = 480; // vertical gap between pipes
// Randomly decide if pipes are flipped (top/bottom swap orientation)
var flip = Math.random() < 0.5;
// Randomly decide pipe type: normal (70%) or fire (30%)
var isFirePipe = Math.random() < 0.3;
// For fire pipes, always single pipe. For regular pipes, 30% chance of single pipe
var isSinglePipe = isFirePipe || Math.random() < 0.3;
var pipeAsset = isFirePipe ? 'FirePipe' : 'pipe';
var topPipe = null;
var bottomPipe = null;
// Create pipes based on configuration
if (isSinglePipe) {
// Single pipe - randomly choose top or bottom position
var isTopPosition = Math.random() < 0.5;
if (isTopPosition) {
topPipe = self.attachAsset(pipeAsset, {
anchorX: 0,
anchorY: flip ? 0 : 1,
width: pipeWidth,
height: pipeHeight,
rotation: flip ? Math.PI : 0
});
} else {
bottomPipe = self.attachAsset(pipeAsset, {
anchorX: 0,
anchorY: flip ? 1 : 0,
width: pipeWidth,
height: pipeHeight,
rotation: flip ? Math.PI : 0
});
}
} else {
// Double pipes (traditional setup)
topPipe = self.attachAsset(pipeAsset, {
anchorX: 0,
anchorY: flip ? 0 : 1,
width: pipeWidth,
height: pipeHeight,
rotation: flip ? Math.PI : 0
});
bottomPipe = self.attachAsset(pipeAsset, {
anchorX: 0,
anchorY: flip ? 1 : 0,
width: pipeWidth,
height: pipeHeight,
rotation: flip ? Math.PI : 0
});
}
self.pipeWidth = pipeWidth;
self.gapHeight = gapHeight;
self.passed = false; // for scoring
self.isFirePipe = isFirePipe;
self.isSinglePipe = isSinglePipe;
self.fireTimer = 0;
self.topPipe = topPipe;
self.bottomPipe = bottomPipe;
// Set pipes' vertical positions based on gapY (top of gap)
self.setGap = function (gapY) {
// gapY: y position of top of gap
if (topPipe) topPipe.y = gapY;
if (bottomPipe) bottomPipe.y = gapY + gapHeight;
};
// Move pipes left
self.update = function () {
self.x -= pipeSpeed;
};
// Get bounding rects for collision
self.getTopRect = function () {
if (!topPipe) return null;
return {
x: self.x,
y: topPipe.y - pipeHeight,
width: pipeWidth,
height: pipeHeight
};
};
self.getBottomRect = function () {
if (!bottomPipe) return null;
return {
x: self.x,
y: bottomPipe.y,
width: pipeWidth,
height: pipeHeight
};
};
// Add fire shooting capability for fire pipes
if (isFirePipe) {
var firePipe = new FirePipe();
self.addChild(firePipe);
self.firePipe = firePipe;
self.fireStarted = false; // Track if fire has been started
}
self.update = function () {
self.x -= pipeSpeed * (slowTimeActive ? slowTimeMultiplier : 1);
// Update fire pipe if this is a fire pipe
if (self.isFirePipe && self.firePipe) {
self.firePipe.x = self.x; // Position fire at pipe position
// Center fire in the gap or at pipe level for single pipes
var fireY = bottomPipe ? bottomPipe.y - gapHeight / 2 : topPipe ? topPipe.y + gapHeight / 2 : 1366;
self.firePipe.y = fireY;
self.firePipe.update();
// Start movement when pipe enters screen (only once)
if (!self.fireStarted && self.x < 2048) {
self.fireStarted = true;
// Check if power-up is active and freeze immediately
if (slowTimeActive) {
self.firePipe.freeze();
frozenPipes.push(self.firePipe);
} else {
self.firePipe.startMovement();
}
}
}
};
return self;
});
// PowerUp class
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Create a star-shaped power-up visual using new asset
var powerUpSprite = self.attachAsset('powerUpStar', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00ffff // Cyan color for freeze power-up
});
// Add pulsing effect
self.pulse = function () {
tween(powerUpSprite, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(powerUpSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.pulse();
}
});
}
});
};
self.collected = false;
self.update = function () {
if (!self.collected) {
self.x -= pipeSpeed * (slowTimeActive ? slowTimeMultiplier : 1);
}
};
self.getBoundingRect = function () {
return {
x: self.x - powerUpSprite.width * 0.5,
y: self.y - powerUpSprite.height * 0.5,
width: powerUpSprite.width,
height: powerUpSprite.height
};
};
// Start pulsing animation
self.pulse();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // sky blue
});
/****
* Game Code
****/
// Sound: Hit
// Sound: Score
// Sound: Flap
// Ground: brown box
// Pipe: green box
// Bird: yellow ellipse
// Game constants
var BIRD_START_X = 600;
var BIRD_START_Y = 1200;
var PIPE_INTERVAL = 1300; // px between pipes horizontally (slower spawn rate)
var PIPE_MIN_Y = 350; // min y for top of gap
var PIPE_MAX_Y = 1800; // max y for top of gap
var GROUND_Y = 2732 - 120; // ground top y
var pipeSpeed = 8; // px per frame (slower pipes)
var POWERUP_DURATION = 30000; // 30 seconds in milliseconds
var slowTimeMultiplier = 0.3; // 30% speed during slow time
// Game state
var bird;
var pipes = [];
var powerUps = [];
var ground;
var score = 0;
var scoreTxt;
var gameOver = false;
var started = false;
var lastPipeX = 0;
var slowTimeActive = false;
var slowTimeTimer = null;
var frozenPipes = [];
// GUI: Score
scoreTxt = new Text2('0', {
size: 160,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// GUI: Game Over text
var gameOverTxt = new Text2('Game Over', {
size: 180,
fill: 0xFF4444
});
gameOverTxt.anchor.set(0.5, 0.5);
gameOverTxt.visible = false;
LK.gui.center.addChild(gameOverTxt);
// GUI: Tap to restart
var restartTxt = new Text2('Tap to restart', {
size: 100,
fill: 0xFFFFFF
});
restartTxt.anchor.set(0.5, 0.5);
restartTxt.visible = false;
LK.gui.center.addChild(restartTxt);
// GUI: Drink toggle button
var drinkToggleBtn = new Container();
var drinkBtnBg = drinkToggleBtn.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150,
tint: 0x4444ff
});
var drinkBtnText = new Text2('🥤', {
size: 80,
fill: 0xFFFFFF
});
drinkBtnText.anchor.set(0.5, 0.5);
drinkToggleBtn.addChild(drinkBtnText);
drinkToggleBtn.x = -100;
drinkToggleBtn.y = 100;
LK.gui.topRight.addChild(drinkToggleBtn);
// GUI: Reset score button
var resetScoreBtn = new Container();
var resetBtnBg = resetScoreBtn.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150,
tint: 0xff4444
});
var resetBtnText = new Text2('Ø', {
size: 100,
fill: 0xFFFFFF
});
resetBtnText.anchor.set(0.5, 0.5);
resetScoreBtn.addChild(resetBtnText);
resetScoreBtn.x = -100;
resetScoreBtn.y = 280;
LK.gui.topRight.addChild(resetScoreBtn);
// GUI: Bird swapper button
var birdSwapBtn = new Container();
var birdSwapBtnBg = birdSwapBtn.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150,
tint: 0x44ff44
});
var birdSwapIcon = birdSwapBtn.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
birdSwapBtn.x = -100;
birdSwapBtn.y = 460;
LK.gui.topRight.addChild(birdSwapBtn);
// Start spinning animation for reset button
function spinResetButton() {
tween(resetBtnText, {
rotation: resetBtnText.rotation + Math.PI * 2
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
spinResetButton();
}
});
}
spinResetButton();
// GUI: Power-up indicator
var powerUpIndicator = new Container();
var powerUpBg = powerUpIndicator.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 80,
tint: 0x00ffff
});
var powerUpText = new Text2('FREEZE: 30s', {
size: 40,
fill: 0xFFFFFF
});
powerUpText.anchor.set(0.5, 0.5);
powerUpIndicator.addChild(powerUpText);
powerUpIndicator.x = 0;
powerUpIndicator.y = 200;
powerUpIndicator.visible = false;
LK.gui.top.addChild(powerUpIndicator);
// Drink toggle handler
drinkToggleBtn.down = function () {
if (bird) {
bird.toggleDrink();
}
};
// Reset score handler
resetScoreBtn.down = function () {
score = 0;
scoreTxt.setText('0');
storage.lastScore = 0;
// Flash effect to indicate reset
LK.effects.flashObject(resetScoreBtn, 0xffffff, 300);
};
// Bird swap handler
birdSwapBtn.down = function () {
if (bird) {
// Get current bird type from storage or default to 'bird'
var currentBirdType = storage.currentBirdType || 'bird';
var newBirdType = currentBirdType === 'bird' ? 'Bird2' : 'bird';
// Store current drink state
var drinkState = {
drink1: bird.children[1].visible,
drink2: bird.children[2].visible,
drink3: bird.children[3].visible
};
// Remove current main bird sprite
var currentBird = bird.children[0];
bird.removeChild(currentBird);
// Create new bird sprite
var newBird = LK.getAsset(newBirdType, {
anchorX: 0.5,
anchorY: 0.5
});
bird.addChildAt(newBird, 0);
bird.currentSprite = newBird;
// Update rotation to match current velocity
var maxAngle = Math.PI / 4;
var minAngle = -Math.PI / 6;
var angle = bird.vy / 40 * maxAngle;
if (angle > maxAngle) angle = maxAngle;
if (angle < minAngle) angle = minAngle;
newBird.rotation = angle;
// Restore drink state
bird.children[1].visible = drinkState.drink1;
bird.children[2].visible = drinkState.drink2;
bird.children[3].visible = drinkState.drink3;
// Save new bird type
storage.currentBirdType = newBirdType;
// Flash effect to indicate swap
LK.effects.flashObject(birdSwapBtn, 0xffffff, 300);
}
};
// Add ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GROUND_Y
});
game.addChild(ground);
// Helper: Reset game state
function resetGame() {
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Remove power-ups
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].destroy();
}
powerUps = [];
// Clear power-up state
if (slowTimeTimer) {
LK.clearTimeout(slowTimeTimer);
slowTimeTimer = null;
}
slowTimeActive = false;
powerUpIndicator.visible = false;
frozenPipes = [];
// Remove bird if exists
if (bird) bird.destroy();
// Reset variables
score = 0;
scoreTxt.setText('0');
gameOver = false;
started = false;
lastPipeX = 0;
// Hide game over
gameOverTxt.visible = false;
restartTxt.visible = false;
// Add bird
bird = new Bird();
bird.x = BIRD_START_X;
bird.y = BIRD_START_Y;
game.addChild(bird);
// Restore score if available
if (typeof storage.lastScore !== "undefined") {
score = storage.lastScore;
scoreTxt.setText(score + '');
}
// Restore bird type if available
if (typeof storage.currentBirdType !== "undefined" && storage.currentBirdType === 'Bird2') {
var currentBird = bird.children[0];
var newBird = LK.getAsset('Bird2', {
anchorX: 0.5,
anchorY: 0.5
});
bird.removeChild(currentBird);
bird.addChildAt(newBird, 0);
bird.currentSprite = newBird;
}
// Restore drink state if available
if (typeof storage.lastDrink !== "undefined") {
if (storage.lastDrink === 1) {
bird.children[1].visible = true;
bird.children[2].visible = false;
} else if (storage.lastDrink === 2) {
bird.children[1].visible = false;
bird.children[2].visible = true;
} else if (storage.lastDrink === 3) {
bird.children[1].visible = false;
bird.children[2].visible = false;
bird.children[3].visible = true;
} else {
bird.children[1].visible = false;
bird.children[2].visible = false;
bird.children[3].visible = false;
}
}
// Add first pipes
for (var i = 0; i < 3; i++) {
spawnPipe(2048 + i * PIPE_INTERVAL);
}
}
// Helper: Spawn a pipe pair at x
function spawnPipe(x) {
var gapY = PIPE_MIN_Y + Math.floor(Math.random() * (PIPE_MAX_Y - PIPE_MIN_Y));
var pipePair = new PipePair();
pipePair.x = x;
pipePair.setGap(gapY);
pipes.push(pipePair);
game.addChild(pipePair);
lastPipeX = x;
// 10% chance to spawn power-up with pipe
if (Math.random() < 0.1 && !slowTimeActive) {
var powerUp = new PowerUp();
powerUp.x = x + pipePair.pipeWidth / 2;
powerUp.y = gapY + pipePair.gapHeight / 2;
powerUps.push(powerUp);
game.addChild(powerUp);
}
}
// Helper: Check collision between bird and a pipe rect
function birdHitsRect(rect) {
// Return false if rect is null (e.g., when fire sprite is not visible)
if (!rect) return false;
// Bird is a circle, rect is {x, y, width, height}
var cx = bird.x;
var cy = bird.y;
var r = bird.radius;
// Find closest point in rect to bird center
var closestX = cx;
if (cx < rect.x) closestX = rect.x;else if (cx > rect.x + rect.width) closestX = rect.x + rect.width;
var closestY = cy;
if (cy < rect.y) closestY = rect.y;else if (cy > rect.y + rect.height) closestY = rect.y + rect.height;
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < r * r;
}
// Helper: Activate power-up
function activatePowerUp() {
slowTimeActive = true;
powerUpIndicator.visible = true;
// Freeze all fire pipes
for (var i = 0; i < pipes.length; i++) {
if (pipes[i].isFirePipe && pipes[i].firePipe) {
pipes[i].firePipe.freeze();
frozenPipes.push(pipes[i].firePipe);
}
}
// Start countdown
var timeLeft = POWERUP_DURATION / 1000;
var countdownInterval = LK.setInterval(function () {
timeLeft--;
powerUpText.setText('FREEZE: ' + timeLeft + 's');
if (timeLeft <= 0) {
LK.clearInterval(countdownInterval);
}
}, 1000);
// Set timer to deactivate power-up
if (slowTimeTimer) {
LK.clearTimeout(slowTimeTimer);
}
slowTimeTimer = LK.setTimeout(function () {
slowTimeActive = false;
powerUpIndicator.visible = false;
// Unfreeze all frozen pipes
for (var i = 0; i < frozenPipes.length; i++) {
frozenPipes[i].unfreeze();
}
frozenPipes = [];
LK.clearInterval(countdownInterval);
}, POWERUP_DURATION);
}
// Helper: End game
function triggerGameOver() {
if (gameOver) return;
gameOver = true;
// Save score and drink state to storage
storage.lastScore = score;
// Access birdDrinkSprite via bird instance if available
storage.lastDrink = bird && bird.hasOwnProperty('children') && bird.children.length > 1 ? bird.children[1].visible ? 1 : bird.children[2].visible ? 2 : bird.children[3].visible ? 3 : 0 : 0;
LK.getSound('hit').play();
gameOverTxt.visible = true;
restartTxt.visible = true;
// Flash screen
LK.effects.flashScreen(0xff0000, 600);
// Show Game Over popup (will pause game)
// LK.showGameOver();
}
// Game tap/flap handler
function handleTap(x, y, obj) {
if (gameOver) {
resetGame();
return;
}
if (!started) {
started = true;
}
bird.flap();
}
// Attach tap handler
game.down = handleTap;
// Main update loop
game.update = function () {
if (gameOver) return;
// Bird update
bird.update();
// Pipes update
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove pipes off screen
if (pipe.x + pipe.pipeWidth < 0) {
pipe.destroy();
pipes.splice(i, 1);
continue;
}
// Scoring: passed pipe
if (!pipe.passed && pipe.x + pipe.pipeWidth < bird.x) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score + '');
LK.getSound('score').play();
}
}
// Spawn new pipes
if (pipes.length > 0) {
var rightmost = pipes[pipes.length - 1];
if (2048 - (rightmost.x + rightmost.pipeWidth) >= PIPE_INTERVAL - 10) {
spawnPipe(2048);
}
}
// Power-ups update and collision
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
powerUp.update();
// Remove power-ups off screen
if (powerUp.x + 40 < 0) {
powerUp.destroy();
powerUps.splice(i, 1);
continue;
}
// Check collision with bird
if (!powerUp.collected && birdHitsRect(powerUp.getBoundingRect())) {
powerUp.collected = true;
powerUp.visible = false;
activatePowerUp();
LK.getSound('score').play();
}
}
// Collision: pipes
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
var hitTopPipe = birdHitsRect(pipe.getTopRect());
var hitBottomPipe = birdHitsRect(pipe.getBottomRect());
var hitPipe = hitTopPipe || hitBottomPipe;
var hitFire = pipe.isFirePipe && pipe.firePipe && pipe.firePipe.getBoundingRect && birdHitsRect(pipe.firePipe.getBoundingRect());
if (hitPipe || hitFire) {
triggerGameOver();
return;
}
}
// Collision: ground
if (bird.y + bird.radius > GROUND_Y) {
bird.y = GROUND_Y - bird.radius;
triggerGameOver();
return;
}
// Collision: ceiling
if (bird.y - bird.radius < 0) {
bird.y = bird.radius;
bird.vy = 0;
}
// Out of bounds (left/right)
if (bird.x - bird.radius < 0 || bird.x + bird.radius > 2048) {
triggerGameOver();
return;
}
};
// Initial game state
resetGame();
Pipe. In-Game asset. 2d. High contrast. No shadows
Bird. In-Game asset. 2d. High contrast. No shadows
Rock bg. In-Game asset. 2d. High contrast. No shadows
Cup with Straw. In-Game asset. 2d. High contrast. No shadows
Coke bottle with straw. In-Game asset. 2d. High contrast. No shadows
Fire. In-Game asset. 2d. High contrast. No shadows
Red pipe. In-Game asset. 2d. High contrast. No shadows