/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Define a class for flying bird enemies var Bird = Container.expand(function () { var self = Container.call(this); var birdGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply a tint to make it look different from regular enemies birdGraphics.tint = 0x00FFFF; // Create a hitbox for the bird self.hitArea = new Rectangle(-30, -30, 60, 60); self.speed = 6; self.verticalSpeed = 0; self.amplitude = 100; // How high the bird flies up and down self.frequency = 0.05; // How fast the bird completes a wave cycle self.initialY = 0; // Will store the initial Y position self.time = 0; // Time counter for sine wave movement self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // Bird moves regardless of collisions with other enemies // Move bird horizontally self.x -= self.speed; // Update time counter self.time += self.frequency; // Apply sine wave vertical movement self.y = self.initialY + Math.sin(self.time) * self.amplitude; // Rotate bird slightly to match flight direction self.rotation = Math.sin(self.time) * 0.2; // Remove bird if it's off screen if (self.x < -50) { // Remove from enemies array when reaching the left side for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; return self; }); // Define a class for enemies var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Create a much smaller hitbox for the enemy (reduce the collision area) self.hitArea = new Rectangle(-30, -30, 60, 60); self.speed = 8; self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // Enemy moves regardless of collisions with other enemies self.x -= self.speed; // Make the enemy roll by rotating it in the opposite direction self.rotation -= 0.1; if (self.x < -50) { // Remove from enemies array when reaching the left side for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; }); // Define a class for enemies that fall from the sky var FallingEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply a purple tint to make it visually distinct enemyGraphics.tint = 0x9900FF; // Create a hitbox for the falling enemy self.hitArea = new Rectangle(-35, -35, 70, 70); self.fallSpeed = 10; self.rotationSpeed = 0.08; self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // FallingEnemy moves regardless of collisions with other enemies // Move enemy down self.y += self.fallSpeed; // Rotate enemy as it falls self.rotation += self.rotationSpeed; // Remove if off screen if (self.y > 2732 + 100) { // Remove from enemies array when reaching the bottom for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; return self; }); // Define a class for heart display var Heart = Container.expand(function () { var self = Container.call(this); // Create a heart using the heart asset var heartShape = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); // Animate heart slightly self.animate = function () { tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOutQuad, onFinish: function onFinish() { tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOutQuad, onFinish: self.animate }); } }); }; return self; }); // Define a class for heart pickups that fly in the sky var HeartPickup = Container.expand(function () { var self = Container.call(this); var heartGraphics = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); // Add visual effects to make the heart look more like a pickup heartGraphics.tint = 0xFF8FB8; // Lighter pink color // Create a hitbox for the heart self.hitArea = new Rectangle(-35, -35, 70, 70); // Movement properties self.speed = 3 + Math.random() * 2; // Random speed between 3-5 self.floatAmplitude = 40 + Math.random() * 30; // Random float height self.floatSpeed = 0.03 + Math.random() * 0.02; // Random float speed self.initialY = 0; self.time = Math.random() * Math.PI * 2; // Random starting position in float cycle // Wings have been removed // Wing animation method removed // No wing animation needed self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // Move heart horizontally self.x -= self.speed; // Apply floating movement self.time += self.floatSpeed; self.y = self.initialY + Math.sin(self.time) * self.floatAmplitude; // Remove heart if it's off screen if (self.x < -100) { // Remove from hearts array for (var i = 0; i < heartPickups.length; i++) { if (heartPickups[i] === self) { heartPickups.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; return self; }); //<Assets used in the game will automatically appear here> // Define a class for the player character var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Create a smaller hitbox for the player to make collisions more precise self.hitArea = new Rectangle(-50, -30, 100, 60); self.speed = 5; self.jumpHeight = 40; self.isJumping = false; self.velocityY = 0; self.invulnerable = false; self.jumpsRemaining = 2; // Allow for double jump (initial + 1 more in air) self.update = function () { if (self.isJumping) { self.y += self.velocityY; self.velocityY += 0.9; // Gravity effect if (self.y >= 2732 / 2) { // Ground level self.y = 2732 / 2; self.isJumping = false; self.velocityY = 0; self.jumpsRemaining = 2; // Reset jumps when landing // Add landing animation - slight squash effect tween(self, { scaleX: 1.3, scaleY: 0.7 }, { duration: 200, easing: tween.easeOutQuad, onFinish: function onFinish() { // Return to normal scale tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeOutElastic }); } }); } } // Check for falling enemies above the player for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy instanceof FallingEnemy && enemy.y > self.y - 400 && enemy.y < self.y && Math.abs(enemy.x - self.x) < 100 && self.isJumping && self.velocityY < 0) { // If player is jumping upward into a falling enemy, destroy it enemies.splice(i, 1); enemy.destroy(); // Give bonus score // Check if score was reset to 0 and restart music if (LK.getScore() === 0) { LK.stopMusic(); LK.playMusic('epicMusic', { loop: true, fade: { start: 0, end: 0.8, duration: 2000 } }); } LK.setScore(LK.getScore() + 5); scoreText.setText(LK.getScore()); // Add visual effect for killing enemy LK.effects.flashObject(self, 0x00ff00, 500); break; } } }; self.jump = function () { // Allow jumping if either on ground or have jumps remaining if (!self.isJumping || self.jumpsRemaining > 0) { // Only play jump sound on first jump (not on double jump) if (!self.isJumping) { // Play jump sound effect only for the initial jump LK.getSound('jump').play(); } // If already jumping, this is a double jump if (self.isJumping) { self.jumpsRemaining--; // Second jump has significantly less height self.velocityY = -self.jumpHeight * 0.6; } else { // First jump self.isJumping = true; self.jumpsRemaining = 1; // One more jump available after initial jump self.velocityY = -self.jumpHeight; } // Add a jump animation - squash before stretching up tween(self, { scaleX: 1.2, scaleY: 0.8 }, { duration: 150, easing: tween.easeOutQuad, onFinish: function onFinish() { // Stretch when jumping up tween(self, { scaleX: 0.9, scaleY: 1.3 }, { duration: 300, easing: tween.easeOutQuad, onFinish: function onFinish() { // Return to normal scale gradually tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOutQuad }); } }); } }); } }; }); // Define a class for enemies that shoot from the right side var ShootingEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply a green tint to make it visually distinct enemyGraphics.tint = 0x00FF00; // Create a hitbox for the shooting enemy self.hitArea = new Rectangle(-30, -30, 60, 60); // Movement properties - much faster self.speed = 10; // Significantly faster than regular enemies self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // Move enemy much faster self.x -= self.speed * 2.5; // Make the enemy roll by rotating it in the opposite direction self.rotation -= 0.1; // Remove if off screen if (self.x < -100) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; return self; }); // Portal class removed as it's no longer needed // Define a class for slow, big enemies var SlowEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Make it slightly larger but smaller than before enemyGraphics.scale.set(1.8, 1.8); // Apply a reddish tint to make it look more threatening enemyGraphics.tint = 0xFF5555; // Create a hitbox that matches the new smaller size self.hitArea = new Rectangle(-55, -55, 110, 110); // Faster speed compared to previous value self.speed = 5; self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // SlowEnemy moves regardless of collisions with other enemies // Move enemy slowly self.x -= self.speed; // Make the enemy roll but slower than regular enemies self.rotation -= 0.04; // Remove if off screen if (self.x < -100) { // Remove from enemies array when reaching the left side for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; return self; }); // Define a class for small, fast enemies var SmallEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Scale down the enemy to make it smaller enemyGraphics.scale.set(0.7, 0.7); // Apply a yellow tint to make it visually distinct enemyGraphics.tint = 0xFFFF00; // Create a hitbox that matches the smaller size self.hitArea = new Rectangle(-20, -20, 40, 40); // Faster speed compared to normal enemy self.speed = 12; self.update = function () { // Store last position for collision detection if (self.lastX === undefined) { self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; } // Move enemy fast self.x -= self.speed; // Make the enemy roll by rotating it in the opposite direction self.rotation -= 0.15; // Remove if off screen if (self.x < -50) { // Remove from enemies array when reaching the left side for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } // Update last positions self.lastX = self.x; self.lastY = self.y; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x654321 // Darker brown background }); /**** * Game Code ****/ // Using same asset as enemy but will be tinted differently var background = game.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 0 })); background.x = 0; background.y = 0; // Add a second background for the empty space var background2 = game.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 0 })); background2.x = background.width; background2.y = 0; // Initialize player var player = game.addChild(new Player()); player.x = 2048 / 2; player.y = 2732 / 2; // Enemies will spawn from the right edge without a portal // Initialize enemies var enemies = []; var enemySpawnInterval = 100; var enemySpawnCounter = 0; // Create a new Text2 object to display the score var scoreText = new Text2('0', { size: 100, fill: 0xFFFFFF }); // Add instruction text var instructionText = new Text2('Tap once to jump, tap again for double jump!', { size: 64, fill: 0xFFFFFF }); LK.gui.bottom.addChild(instructionText); instructionText.x = 2048 / 2; instructionText.y = -100; instructionText.anchor.set(0.5, 0.5); // Fade out instruction after 5 seconds LK.setTimeout(function () { tween(instructionText, { alpha: 0 }, { duration: 1000, easing: tween.easeInCubic }); }, 5000); // Add the score text to the game GUI at the top center of the screen LK.gui.top.addChild(scoreText); scoreText.x = 2048 / 2; scoreText.y = 0; // Initialize lives counter var lives = 3; var hearts = []; var heartsContainer = new Container(); game.addChild(heartsContainer); // Position hearts container in the middle of the screen heartsContainer.x = 2048 / 2; heartsContainer.y = 150; // Create heart display function updateHeartDisplay() { // Clear existing hearts while (hearts.length > 0) { var heart = hearts.pop(); heart.destroy(); } // Create new hearts based on current lives for (var i = 0; i < lives; i++) { var heart = new Heart(); heart.x = (i - (lives - 1) / 2) * 100; // Distribute hearts evenly heart.y = 0; hearts.push(heart); heartsContainer.addChild(heart); heart.animate(); } } // Initial heart display updateHeartDisplay(); // Play epic background music with fade-in effect LK.playMusic('epicMusic', { fade: { start: 0, end: 0.8, duration: 2000 } }); // Add listener for game reset to restart music LK.on('gameReset', function () { // Restart the music when game resets from the beginning (second 0) LK.stopMusic(); // Stop current music to ensure we start from the beginning LK.playMusic('epicMusic', { loop: true, fade: { start: 0, end: 0.8, duration: 2000 } }); }); // Add listener for game over to restart music LK.on('gameOver', function () { // Stop current music to ensure we start from the beginning LK.stopMusic(); // Restart the music from the beginning LK.playMusic('epicMusic', { loop: true, fade: { start: 0, end: 0.8, duration: 2000 } }); }); // Initialize array for heart pickups var heartPickups = []; // Variables for heart spawn control var heartSpawnInterval = 600; // Less frequent than enemies var heartSpawnCounter = 0; // Handle game updates game.update = function () { player.update(); // No portal to update // Spawn hearts occasionally heartSpawnCounter++; if (heartSpawnCounter >= heartSpawnInterval) { // Only spawn heart if player has less than maximum lives if (lives < 3) { var heartPickup = new HeartPickup(); heartPickup.x = 2048 + 100; // Start right of screen heartPickup.y = player.y; // Spawn at the same y position as the player heartPickup.initialY = heartPickup.y; heartPickups.push(heartPickup); game.addChild(heartPickup); // Add spawn effect heartPickup.scale.set(0.1, 0.1); tween(heartPickup, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeOutElastic }); } // Reset heart spawn counter and randomize next interval heartSpawnInterval = Math.floor(Math.random() * 400) + 600; // Between 600-1000 heartSpawnCounter = 0; } // Update all heart pickups for (var i = heartPickups.length - 1; i >= 0; i--) { heartPickups[i].update(); // Check if player collected the heart if (player.intersects(heartPickups[i])) { // Regenerate one heart if not at max if (lives < 3) { lives++; updateHeartDisplay(); // Play yeah sound when gaining a heart LK.getSound('yeah').play(); // Show healing effect LK.effects.flashObject(player, 0x00FF00, 500); // Show text effect var healText = new Text2('+1', { size: 80, fill: 0x00FF00 }); healText.anchor.set(0.5, 0.5); healText.x = player.x; healText.y = player.y - 100; game.addChild(healText); // Animate and remove text tween(healText, { y: healText.y - 150, alpha: 0 }, { duration: 1000, easing: tween.easeOutCubic, onFinish: function onFinish() { healText.destroy(); } }); } // Remove heart pickup heartPickups[i].destroy(); heartPickups.splice(i, 1); } } // Spawn enemies enemySpawnCounter++; if (enemySpawnCounter >= enemySpawnInterval) { // Enemies spawn without portal effects // Track if a slow enemy is present in the game var hasSlowEnemy = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i] instanceof SlowEnemy) { hasSlowEnemy = true; break; } } // Ensure first three enemies are normal enemies var spawnChance = 0; if (LK.getScore() < 3) { // Force normal enemy for first three spawns spawnChance = 0; } else { // Randomly decide between enemy types spawnChance = Math.random(); } // If a slow enemy is present, don't spawn normal enemies if (hasSlowEnemy && spawnChance < 0.4) { // Redirect to either bird or falling enemy spawnChance = 0.4 + spawnChance * 0.6; // Scale between 0.4 and 0.7 } if (spawnChance < 0.25) { // 25% chance for regular enemy (only if no slow enemy is present) var enemy = new Enemy(); enemy.x = 2048; // Right edge of screen // Set enemy Y position to match player when player is on ground enemy.y = !player.isJumping ? player.y : 2732 / 2; // Match player Y if on ground, otherwise middle of screen enemies.push(enemy); game.addChild(enemy); // Apply rolling animation with tween in the opposite direction tween(enemy, { rotation: -Math.PI * 4 }, { duration: 2000, easing: tween.linear, onFinish: function onFinish() { // Continuously roll the enemy in the opposite direction tween(enemy, { rotation: enemy.rotation - Math.PI * 4 }, { duration: 2000, easing: tween.linear }); } }); } else if (spawnChance < 0.35) { // 10% chance for small, fast enemy var smallEnemy = new SmallEnemy(); smallEnemy.x = 2048; // Right edge of screen // Set enemy Y position to match player when player is on ground smallEnemy.y = !player.isJumping ? player.y : 2732 / 2; // Match player Y if on ground, otherwise middle of screen enemies.push(smallEnemy); game.addChild(smallEnemy); // Apply faster rolling animation with tween tween(smallEnemy, { rotation: -Math.PI * 6 }, { duration: 1500, easing: tween.linear, onFinish: function onFinish() { // Continuously roll the enemy in the opposite direction tween(smallEnemy, { rotation: smallEnemy.rotation - Math.PI * 6 }, { duration: 1500, easing: tween.linear }); } }); } else if (spawnChance < 0.55) { // 20% chance for bird var bird = new Bird(); bird.x = 2048; // Right edge of screen bird.y = Math.random() * (2732 / 2 - 400) + 400; // Random position between 400 and half the screen height bird.initialY = bird.y; // Set the initial Y position for the sine wave enemies.push(bird); game.addChild(bird); // Apply flapping wing animation tween(bird, { scaleY: 0.85 }, { duration: 300, easing: tween.easeInOutQuad, onFinish: function onFinish() { // Flap wings function that will call itself repeatedly function flapWings() { tween(bird, { scaleY: 1.15 }, { duration: 300, easing: tween.easeInOutQuad, onFinish: function onFinish() { tween(bird, { scaleY: 0.85 }, { duration: 300, easing: tween.easeInOutQuad, onFinish: flapWings }); } }); } flapWings(); } }); } else if (spawnChance < 0.75) { // 20% chance for shooting enemy // Create warning stripe before shooting enemy appears var warningStripe = game.addChild(LK.getAsset('hitbox', { anchorX: 0, anchorY: 0.5, width: 2048, height: 50 })); warningStripe.y = Math.random() * (2732 / 2 - 200) + 200; // Random position higher than ground warningStripe.tint = 0xFF0000; // Red warning stripe warningStripe.alpha = 0.7; // Flash warning stripe tween(warningStripe, { alpha: 0.3 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { tween(warningStripe, { alpha: 0.7 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { // Spawn shooting enemy after warning var shootEnemy = new ShootingEnemy(); shootEnemy.x = 2048; // Right edge of screen shootEnemy.y = warningStripe.y; // Use same Y position as warning stripe enemies.push(shootEnemy); game.addChild(shootEnemy); // Remove warning stripe warningStripe.destroy(); } }); } }); } else if (spawnChance < 0.85) { // 10% chance for slow, big enemy var slowEnemy = new SlowEnemy(); slowEnemy.x = 2048; // Right edge of screen // Set slow enemy Y position to match player when player is on ground slowEnemy.y = !player.isJumping ? player.y : 2732 / 2; // Match player Y if on ground, otherwise middle of screen enemies.push(slowEnemy); game.addChild(slowEnemy); // Apply slow stomping animation tween(slowEnemy, { rotation: -Math.PI * 2 }, { duration: 4000, easing: tween.linear, onFinish: function onFinish() { // Continuously roll the enemy in the opposite direction but slower tween(slowEnemy, { rotation: slowEnemy.rotation - Math.PI * 2 }, { duration: 4000, easing: tween.linear }); } }); } else { // 15% chance for falling enemy var fallingEnemy = new FallingEnemy(); // Position falling enemy at the player's x position at the top of the screen fallingEnemy.x = player.x; fallingEnemy.y = -100; // Start above the screen enemies.push(fallingEnemy); game.addChild(fallingEnemy); // Apply spinning animation tween(fallingEnemy, { rotation: Math.PI * 4 }, { duration: 2000, easing: tween.linear, onFinish: function onFinish() { // Continuously spin the enemy tween(fallingEnemy, { rotation: fallingEnemy.rotation + Math.PI * 4 }, { duration: 2000, easing: tween.linear }); } }); } // Randomize the spawn interval for the next enemy enemySpawnInterval = Math.floor(Math.random() * 150) + 50; enemySpawnCounter = 0; } // Update enemies for (var j = enemies.length - 1; j >= 0; j--) { if (enemies[j]) { // Check if enemy exists before accessing enemies[j].update(); if (enemies[j] && player.intersects(enemies[j]) && !player.invulnerable) { // Handle collision lives--; updateHeartDisplay(); // Play aua sound when player is hit LK.getSound('aua').play(); // Make player invulnerable temporarily player.invulnerable = true; player.alpha = 0.5; // Flash the screen red LK.effects.flashScreen(0xff0000, 500); // Remove the enemy enemies[j].destroy(); enemies.splice(j, 1); // Check if player has run out of lives if (lives <= 0) { // Stop and restart music from the beginning before showing game over LK.stopMusic(); LK.playMusic('epicMusic', { loop: true, fade: { start: 0, end: 0.8, duration: 2000 } }); LK.showGameOver(); } else { // Make player vulnerable again after a short time LK.setTimeout(function () { player.invulnerable = false; player.alpha = 1; }, 1500); } } else if (enemies[j] && player.x > enemies[j].x && !enemies[j].passed) { enemies[j].passed = true; LK.setScore(LK.getScore() + 1); scoreText.setText(LK.getScore()); } } // Close the enemy existence check } }; // Handle player jump game.down = function (x, y, obj) { player.jump(); }; // Import tween plugin for animations
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Define a class for flying bird enemies
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply a tint to make it look different from regular enemies
birdGraphics.tint = 0x00FFFF;
// Create a hitbox for the bird
self.hitArea = new Rectangle(-30, -30, 60, 60);
self.speed = 6;
self.verticalSpeed = 0;
self.amplitude = 100; // How high the bird flies up and down
self.frequency = 0.05; // How fast the bird completes a wave cycle
self.initialY = 0; // Will store the initial Y position
self.time = 0; // Time counter for sine wave movement
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// Bird moves regardless of collisions with other enemies
// Move bird horizontally
self.x -= self.speed;
// Update time counter
self.time += self.frequency;
// Apply sine wave vertical movement
self.y = self.initialY + Math.sin(self.time) * self.amplitude;
// Rotate bird slightly to match flight direction
self.rotation = Math.sin(self.time) * 0.2;
// Remove bird if it's off screen
if (self.x < -50) {
// Remove from enemies array when reaching the left side
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Define a class for enemies
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Create a much smaller hitbox for the enemy (reduce the collision area)
self.hitArea = new Rectangle(-30, -30, 60, 60);
self.speed = 8;
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// Enemy moves regardless of collisions with other enemies
self.x -= self.speed;
// Make the enemy roll by rotating it in the opposite direction
self.rotation -= 0.1;
if (self.x < -50) {
// Remove from enemies array when reaching the left side
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
});
// Define a class for enemies that fall from the sky
var FallingEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply a purple tint to make it visually distinct
enemyGraphics.tint = 0x9900FF;
// Create a hitbox for the falling enemy
self.hitArea = new Rectangle(-35, -35, 70, 70);
self.fallSpeed = 10;
self.rotationSpeed = 0.08;
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// FallingEnemy moves regardless of collisions with other enemies
// Move enemy down
self.y += self.fallSpeed;
// Rotate enemy as it falls
self.rotation += self.rotationSpeed;
// Remove if off screen
if (self.y > 2732 + 100) {
// Remove from enemies array when reaching the bottom
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Define a class for heart display
var Heart = Container.expand(function () {
var self = Container.call(this);
// Create a heart using the heart asset
var heartShape = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
// Animate heart slightly
self.animate = function () {
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOutQuad,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOutQuad,
onFinish: self.animate
});
}
});
};
return self;
});
// Define a class for heart pickups that fly in the sky
var HeartPickup = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
// Add visual effects to make the heart look more like a pickup
heartGraphics.tint = 0xFF8FB8; // Lighter pink color
// Create a hitbox for the heart
self.hitArea = new Rectangle(-35, -35, 70, 70);
// Movement properties
self.speed = 3 + Math.random() * 2; // Random speed between 3-5
self.floatAmplitude = 40 + Math.random() * 30; // Random float height
self.floatSpeed = 0.03 + Math.random() * 0.02; // Random float speed
self.initialY = 0;
self.time = Math.random() * Math.PI * 2; // Random starting position in float cycle
// Wings have been removed
// Wing animation method removed
// No wing animation needed
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// Move heart horizontally
self.x -= self.speed;
// Apply floating movement
self.time += self.floatSpeed;
self.y = self.initialY + Math.sin(self.time) * self.floatAmplitude;
// Remove heart if it's off screen
if (self.x < -100) {
// Remove from hearts array
for (var i = 0; i < heartPickups.length; i++) {
if (heartPickups[i] === self) {
heartPickups.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
//<Assets used in the game will automatically appear here>
// Define a class for the player character
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Create a smaller hitbox for the player to make collisions more precise
self.hitArea = new Rectangle(-50, -30, 100, 60);
self.speed = 5;
self.jumpHeight = 40;
self.isJumping = false;
self.velocityY = 0;
self.invulnerable = false;
self.jumpsRemaining = 2; // Allow for double jump (initial + 1 more in air)
self.update = function () {
if (self.isJumping) {
self.y += self.velocityY;
self.velocityY += 0.9; // Gravity effect
if (self.y >= 2732 / 2) {
// Ground level
self.y = 2732 / 2;
self.isJumping = false;
self.velocityY = 0;
self.jumpsRemaining = 2; // Reset jumps when landing
// Add landing animation - slight squash effect
tween(self, {
scaleX: 1.3,
scaleY: 0.7
}, {
duration: 200,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Return to normal scale
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOutElastic
});
}
});
}
}
// Check for falling enemies above the player
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy instanceof FallingEnemy && enemy.y > self.y - 400 && enemy.y < self.y && Math.abs(enemy.x - self.x) < 100 && self.isJumping && self.velocityY < 0) {
// If player is jumping upward into a falling enemy, destroy it
enemies.splice(i, 1);
enemy.destroy();
// Give bonus score
// Check if score was reset to 0 and restart music
if (LK.getScore() === 0) {
LK.stopMusic();
LK.playMusic('epicMusic', {
loop: true,
fade: {
start: 0,
end: 0.8,
duration: 2000
}
});
}
LK.setScore(LK.getScore() + 5);
scoreText.setText(LK.getScore());
// Add visual effect for killing enemy
LK.effects.flashObject(self, 0x00ff00, 500);
break;
}
}
};
self.jump = function () {
// Allow jumping if either on ground or have jumps remaining
if (!self.isJumping || self.jumpsRemaining > 0) {
// Only play jump sound on first jump (not on double jump)
if (!self.isJumping) {
// Play jump sound effect only for the initial jump
LK.getSound('jump').play();
}
// If already jumping, this is a double jump
if (self.isJumping) {
self.jumpsRemaining--;
// Second jump has significantly less height
self.velocityY = -self.jumpHeight * 0.6;
} else {
// First jump
self.isJumping = true;
self.jumpsRemaining = 1; // One more jump available after initial jump
self.velocityY = -self.jumpHeight;
}
// Add a jump animation - squash before stretching up
tween(self, {
scaleX: 1.2,
scaleY: 0.8
}, {
duration: 150,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Stretch when jumping up
tween(self, {
scaleX: 0.9,
scaleY: 1.3
}, {
duration: 300,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Return to normal scale gradually
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOutQuad
});
}
});
}
});
}
};
});
// Define a class for enemies that shoot from the right side
var ShootingEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply a green tint to make it visually distinct
enemyGraphics.tint = 0x00FF00;
// Create a hitbox for the shooting enemy
self.hitArea = new Rectangle(-30, -30, 60, 60);
// Movement properties - much faster
self.speed = 10; // Significantly faster than regular enemies
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// Move enemy much faster
self.x -= self.speed * 2.5;
// Make the enemy roll by rotating it in the opposite direction
self.rotation -= 0.1;
// Remove if off screen
if (self.x < -100) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Portal class removed as it's no longer needed
// Define a class for slow, big enemies
var SlowEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it slightly larger but smaller than before
enemyGraphics.scale.set(1.8, 1.8);
// Apply a reddish tint to make it look more threatening
enemyGraphics.tint = 0xFF5555;
// Create a hitbox that matches the new smaller size
self.hitArea = new Rectangle(-55, -55, 110, 110);
// Faster speed compared to previous value
self.speed = 5;
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// SlowEnemy moves regardless of collisions with other enemies
// Move enemy slowly
self.x -= self.speed;
// Make the enemy roll but slower than regular enemies
self.rotation -= 0.04;
// Remove if off screen
if (self.x < -100) {
// Remove from enemies array when reaching the left side
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Define a class for small, fast enemies
var SmallEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale down the enemy to make it smaller
enemyGraphics.scale.set(0.7, 0.7);
// Apply a yellow tint to make it visually distinct
enemyGraphics.tint = 0xFFFF00;
// Create a hitbox that matches the smaller size
self.hitArea = new Rectangle(-20, -20, 40, 40);
// Faster speed compared to normal enemy
self.speed = 12;
self.update = function () {
// Store last position for collision detection
if (self.lastX === undefined) {
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false;
}
// Move enemy fast
self.x -= self.speed;
// Make the enemy roll by rotating it in the opposite direction
self.rotation -= 0.15;
// Remove if off screen
if (self.x < -50) {
// Remove from enemies array when reaching the left side
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x654321 // Darker brown background
});
/****
* Game Code
****/
// Using same asset as enemy but will be tinted differently
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0
}));
background.x = 0;
background.y = 0;
// Add a second background for the empty space
var background2 = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0
}));
background2.x = background.width;
background2.y = 0;
// Initialize player
var player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2;
// Enemies will spawn from the right edge without a portal
// Initialize enemies
var enemies = [];
var enemySpawnInterval = 100;
var enemySpawnCounter = 0;
// Create a new Text2 object to display the score
var scoreText = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
// Add instruction text
var instructionText = new Text2('Tap once to jump, tap again for double jump!', {
size: 64,
fill: 0xFFFFFF
});
LK.gui.bottom.addChild(instructionText);
instructionText.x = 2048 / 2;
instructionText.y = -100;
instructionText.anchor.set(0.5, 0.5);
// Fade out instruction after 5 seconds
LK.setTimeout(function () {
tween(instructionText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInCubic
});
}, 5000);
// Add the score text to the game GUI at the top center of the screen
LK.gui.top.addChild(scoreText);
scoreText.x = 2048 / 2;
scoreText.y = 0;
// Initialize lives counter
var lives = 3;
var hearts = [];
var heartsContainer = new Container();
game.addChild(heartsContainer);
// Position hearts container in the middle of the screen
heartsContainer.x = 2048 / 2;
heartsContainer.y = 150;
// Create heart display
function updateHeartDisplay() {
// Clear existing hearts
while (hearts.length > 0) {
var heart = hearts.pop();
heart.destroy();
}
// Create new hearts based on current lives
for (var i = 0; i < lives; i++) {
var heart = new Heart();
heart.x = (i - (lives - 1) / 2) * 100; // Distribute hearts evenly
heart.y = 0;
hearts.push(heart);
heartsContainer.addChild(heart);
heart.animate();
}
}
// Initial heart display
updateHeartDisplay();
// Play epic background music with fade-in effect
LK.playMusic('epicMusic', {
fade: {
start: 0,
end: 0.8,
duration: 2000
}
});
// Add listener for game reset to restart music
LK.on('gameReset', function () {
// Restart the music when game resets from the beginning (second 0)
LK.stopMusic(); // Stop current music to ensure we start from the beginning
LK.playMusic('epicMusic', {
loop: true,
fade: {
start: 0,
end: 0.8,
duration: 2000
}
});
});
// Add listener for game over to restart music
LK.on('gameOver', function () {
// Stop current music to ensure we start from the beginning
LK.stopMusic();
// Restart the music from the beginning
LK.playMusic('epicMusic', {
loop: true,
fade: {
start: 0,
end: 0.8,
duration: 2000
}
});
});
// Initialize array for heart pickups
var heartPickups = [];
// Variables for heart spawn control
var heartSpawnInterval = 600; // Less frequent than enemies
var heartSpawnCounter = 0;
// Handle game updates
game.update = function () {
player.update();
// No portal to update
// Spawn hearts occasionally
heartSpawnCounter++;
if (heartSpawnCounter >= heartSpawnInterval) {
// Only spawn heart if player has less than maximum lives
if (lives < 3) {
var heartPickup = new HeartPickup();
heartPickup.x = 2048 + 100; // Start right of screen
heartPickup.y = player.y; // Spawn at the same y position as the player
heartPickup.initialY = heartPickup.y;
heartPickups.push(heartPickup);
game.addChild(heartPickup);
// Add spawn effect
heartPickup.scale.set(0.1, 0.1);
tween(heartPickup, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeOutElastic
});
}
// Reset heart spawn counter and randomize next interval
heartSpawnInterval = Math.floor(Math.random() * 400) + 600; // Between 600-1000
heartSpawnCounter = 0;
}
// Update all heart pickups
for (var i = heartPickups.length - 1; i >= 0; i--) {
heartPickups[i].update();
// Check if player collected the heart
if (player.intersects(heartPickups[i])) {
// Regenerate one heart if not at max
if (lives < 3) {
lives++;
updateHeartDisplay();
// Play yeah sound when gaining a heart
LK.getSound('yeah').play();
// Show healing effect
LK.effects.flashObject(player, 0x00FF00, 500);
// Show text effect
var healText = new Text2('+1', {
size: 80,
fill: 0x00FF00
});
healText.anchor.set(0.5, 0.5);
healText.x = player.x;
healText.y = player.y - 100;
game.addChild(healText);
// Animate and remove text
tween(healText, {
y: healText.y - 150,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOutCubic,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Remove heart pickup
heartPickups[i].destroy();
heartPickups.splice(i, 1);
}
}
// Spawn enemies
enemySpawnCounter++;
if (enemySpawnCounter >= enemySpawnInterval) {
// Enemies spawn without portal effects
// Track if a slow enemy is present in the game
var hasSlowEnemy = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] instanceof SlowEnemy) {
hasSlowEnemy = true;
break;
}
}
// Ensure first three enemies are normal enemies
var spawnChance = 0;
if (LK.getScore() < 3) {
// Force normal enemy for first three spawns
spawnChance = 0;
} else {
// Randomly decide between enemy types
spawnChance = Math.random();
}
// If a slow enemy is present, don't spawn normal enemies
if (hasSlowEnemy && spawnChance < 0.4) {
// Redirect to either bird or falling enemy
spawnChance = 0.4 + spawnChance * 0.6; // Scale between 0.4 and 0.7
}
if (spawnChance < 0.25) {
// 25% chance for regular enemy (only if no slow enemy is present)
var enemy = new Enemy();
enemy.x = 2048; // Right edge of screen
// Set enemy Y position to match player when player is on ground
enemy.y = !player.isJumping ? player.y : 2732 / 2; // Match player Y if on ground, otherwise middle of screen
enemies.push(enemy);
game.addChild(enemy);
// Apply rolling animation with tween in the opposite direction
tween(enemy, {
rotation: -Math.PI * 4
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
// Continuously roll the enemy in the opposite direction
tween(enemy, {
rotation: enemy.rotation - Math.PI * 4
}, {
duration: 2000,
easing: tween.linear
});
}
});
} else if (spawnChance < 0.35) {
// 10% chance for small, fast enemy
var smallEnemy = new SmallEnemy();
smallEnemy.x = 2048; // Right edge of screen
// Set enemy Y position to match player when player is on ground
smallEnemy.y = !player.isJumping ? player.y : 2732 / 2; // Match player Y if on ground, otherwise middle of screen
enemies.push(smallEnemy);
game.addChild(smallEnemy);
// Apply faster rolling animation with tween
tween(smallEnemy, {
rotation: -Math.PI * 6
}, {
duration: 1500,
easing: tween.linear,
onFinish: function onFinish() {
// Continuously roll the enemy in the opposite direction
tween(smallEnemy, {
rotation: smallEnemy.rotation - Math.PI * 6
}, {
duration: 1500,
easing: tween.linear
});
}
});
} else if (spawnChance < 0.55) {
// 20% chance for bird
var bird = new Bird();
bird.x = 2048; // Right edge of screen
bird.y = Math.random() * (2732 / 2 - 400) + 400; // Random position between 400 and half the screen height
bird.initialY = bird.y; // Set the initial Y position for the sine wave
enemies.push(bird);
game.addChild(bird);
// Apply flapping wing animation
tween(bird, {
scaleY: 0.85
}, {
duration: 300,
easing: tween.easeInOutQuad,
onFinish: function onFinish() {
// Flap wings function that will call itself repeatedly
function flapWings() {
tween(bird, {
scaleY: 1.15
}, {
duration: 300,
easing: tween.easeInOutQuad,
onFinish: function onFinish() {
tween(bird, {
scaleY: 0.85
}, {
duration: 300,
easing: tween.easeInOutQuad,
onFinish: flapWings
});
}
});
}
flapWings();
}
});
} else if (spawnChance < 0.75) {
// 20% chance for shooting enemy
// Create warning stripe before shooting enemy appears
var warningStripe = game.addChild(LK.getAsset('hitbox', {
anchorX: 0,
anchorY: 0.5,
width: 2048,
height: 50
}));
warningStripe.y = Math.random() * (2732 / 2 - 200) + 200; // Random position higher than ground
warningStripe.tint = 0xFF0000; // Red warning stripe
warningStripe.alpha = 0.7;
// Flash warning stripe
tween(warningStripe, {
alpha: 0.3
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
tween(warningStripe, {
alpha: 0.7
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
// Spawn shooting enemy after warning
var shootEnemy = new ShootingEnemy();
shootEnemy.x = 2048; // Right edge of screen
shootEnemy.y = warningStripe.y; // Use same Y position as warning stripe
enemies.push(shootEnemy);
game.addChild(shootEnemy);
// Remove warning stripe
warningStripe.destroy();
}
});
}
});
} else if (spawnChance < 0.85) {
// 10% chance for slow, big enemy
var slowEnemy = new SlowEnemy();
slowEnemy.x = 2048; // Right edge of screen
// Set slow enemy Y position to match player when player is on ground
slowEnemy.y = !player.isJumping ? player.y : 2732 / 2; // Match player Y if on ground, otherwise middle of screen
enemies.push(slowEnemy);
game.addChild(slowEnemy);
// Apply slow stomping animation
tween(slowEnemy, {
rotation: -Math.PI * 2
}, {
duration: 4000,
easing: tween.linear,
onFinish: function onFinish() {
// Continuously roll the enemy in the opposite direction but slower
tween(slowEnemy, {
rotation: slowEnemy.rotation - Math.PI * 2
}, {
duration: 4000,
easing: tween.linear
});
}
});
} else {
// 15% chance for falling enemy
var fallingEnemy = new FallingEnemy();
// Position falling enemy at the player's x position at the top of the screen
fallingEnemy.x = player.x;
fallingEnemy.y = -100; // Start above the screen
enemies.push(fallingEnemy);
game.addChild(fallingEnemy);
// Apply spinning animation
tween(fallingEnemy, {
rotation: Math.PI * 4
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
// Continuously spin the enemy
tween(fallingEnemy, {
rotation: fallingEnemy.rotation + Math.PI * 4
}, {
duration: 2000,
easing: tween.linear
});
}
});
}
// Randomize the spawn interval for the next enemy
enemySpawnInterval = Math.floor(Math.random() * 150) + 50;
enemySpawnCounter = 0;
}
// Update enemies
for (var j = enemies.length - 1; j >= 0; j--) {
if (enemies[j]) {
// Check if enemy exists before accessing
enemies[j].update();
if (enemies[j] && player.intersects(enemies[j]) && !player.invulnerable) {
// Handle collision
lives--;
updateHeartDisplay();
// Play aua sound when player is hit
LK.getSound('aua').play();
// Make player invulnerable temporarily
player.invulnerable = true;
player.alpha = 0.5;
// Flash the screen red
LK.effects.flashScreen(0xff0000, 500);
// Remove the enemy
enemies[j].destroy();
enemies.splice(j, 1);
// Check if player has run out of lives
if (lives <= 0) {
// Stop and restart music from the beginning before showing game over
LK.stopMusic();
LK.playMusic('epicMusic', {
loop: true,
fade: {
start: 0,
end: 0.8,
duration: 2000
}
});
LK.showGameOver();
} else {
// Make player vulnerable again after a short time
LK.setTimeout(function () {
player.invulnerable = false;
player.alpha = 1;
}, 1500);
}
} else if (enemies[j] && player.x > enemies[j].x && !enemies[j].passed) {
enemies[j].passed = true;
LK.setScore(LK.getScore() + 1);
scoreText.setText(LK.getScore());
}
} // Close the enemy existence check
}
};
// Handle player jump
game.down = function (x, y, obj) {
player.jump();
};
// Import tween plugin for animations