User prompt
Add an ability in which enemies can dodge your bullets every 5 seconds.
User prompt
Make the enemy ships which reach the bottom edge shoot 10 projectile before self-destructing.
User prompt
The bullets which are shot by enemy ships after reaching the bottom edge are very hard to see. Make them the same color as normal enemy bullets.
User prompt
Make it so that whenever an enemy ship reaches the bottom edge of the screen, it aims toward you and shoots with increased fire rate and bullet speed for 5 seconds before self destructing. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Does not play still, how to fix???
User prompt
Still can't hear it.
User prompt
I added a firepool sound, yet it does not play
User prompt
I accidentally deleted firepool sound asset, add again
User prompt
```javascript // Define your sounds at the beginning of your code LK.init.sound('explosion', {volume: 0.7}); LK.init.sound('shieldActivation', {volume: 0.5}); LK.init.sound('firePoolDestroy', {volume: 0.6}); LK.init.sound('bulletExplosion', {volume: 0.4}); LK.init.sound('shieldCollision', {volume: 0.5}); LK.init.sound('playerCollision', {volume: 0.6}); LK.init.sound('levelUp', {volume: 0.8}); LK.init.sound('playerHit', {volume: 0.5}); LK.init.sound('gameOver', {volume: 0.7}); LK.init.sound('winSound', {volume: 0.8}); do this it still does not play
User prompt
do this for me please in my game
User prompt
Add these sounds to assets
User prompt
I still can't hear anything how do I create these sounds
User prompt
Can you create sounds for: Add sound when enemy is destroyed by fire pool shield activation sound effect player bullet explosion sound when bullet is removed at max bounce count collision sound when enemy collides with shield collision sound when player collides with enemy level up sound sound when player gets hit by enemy bullet background music when game starts game over sound effect win sound effect
User prompt
It still hits me even though the bullet is only in the vicinity of me (player)
User prompt
make it so that when a bullet collides with the player, only then damage is dealt to the player, the bullet cannot be in the vicinity of the player and still deal damage.
User prompt
Remove all the hitbox stuff keep the hitboxes normal
User prompt
THe bullets don't hit me sometimes
User prompt
Make the bullet, player and enemy ship hitboxes to be more exact. I am able to get hit by bullets without properly hitting them.
User prompt
Add sounds to this game for all actions.
User prompt
After the explosion of enemies, the enemies inside the radius of the explosion should be destroyed.
User prompt
Remove enemy bullet destruction completely from the whole game.
User prompt
The game is lagging sometimes because of the enemy bullets destruction ability. Remove the lag please.
User prompt
Also make it that enemy bullets cannot be destroyed until 7 seconds after the quad powerup ends
User prompt
Please fix the bug: 'TypeError: tween.stopTweens is not a function. (In 'tween.stopTweens(finalLifeText)', 'tween.stopTweens' is undefined)' in or related to this line: 'tween.stopTweens(finalLifeText);' Line Number: 1007
User prompt
The game starts lagging a lot when the powerup which allows for bullets to fire from 4 sides is activated. MAke it so that during the powerup, enemy bullets cannot be destroyed.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Enemy properties self.health = 1; self.speed = 2; self.scoreValue = 100; self.fireRate = 120; // Ticks between shots self.lastShot = Math.floor(Math.random() * 60); // Randomize initial shot timer self.movementPattern = Math.floor(Math.random() * 3); // 0: straight, 1: zigzag, 2: circular self.movementCounter = 0; // Initial target position (will be set during movement) self.targetX = null; self.update = function () { // Basic downward movement self.y += self.speed; // Apply movement pattern if (self.movementPattern === 1) { // Zigzag with occasional side movement self.x += Math.sin(self.movementCounter * 0.05) * 3; // Occasionally move to a different horizontal position if (self.movementCounter % 500 === 0) { // Choose a new target position on the opposite side self.targetX = self.x < GAME_WIDTH / 2 ? GAME_WIDTH * 0.7 + Math.random() * GAME_WIDTH * 0.2 : GAME_WIDTH * 0.1 + Math.random() * GAME_WIDTH * 0.2; } if (self.targetX) { var diffX = self.targetX - self.x; if (Math.abs(diffX) > 10) { self.x += diffX * 0.03; } } self.movementCounter++; } else if (self.movementPattern === 2) { // Circular with occasional repositioning self.x += Math.sin(self.movementCounter * 0.03) * 4; // Occasionally reset position to avoid grouping if (self.movementCounter % 700 === 0) { // Move to random position on screen self.targetX = Math.random() * GAME_WIDTH * 0.8 + GAME_WIDTH * 0.1; } // Move toward target if exists if (self.targetX) { var diffX = self.targetX - self.x; if (Math.abs(diffX) > 10) { self.x += diffX * 0.02; } } self.movementCounter++; } else { // Add movement for straight pattern (pattern 0) if (self.movementCounter % 900 === 0) { // Occasional side movement self.targetX = Math.random() * GAME_WIDTH * 0.8 + GAME_WIDTH * 0.1; } // Move toward target if exists if (self.targetX) { var diffX = self.targetX - self.x; if (Math.abs(diffX) > 10) { self.x += diffX * 0.01; } } self.movementCounter++; } }; // Take damage and check if destroyed self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { return true; // Enemy is destroyed } return false; }; return self; }); var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 7; // Moving downward self.update = function () { self.y += self.speed; }; return self; }); var Explosion = Container.expand(function () { var self = Container.call(this); var explosionGraphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); // Explosion duration self.duration = 30; // frames self.currentFrame = 0; // Scale explosion from small to large then fade explosionGraphics.scale.x = 0.1; explosionGraphics.scale.y = 0.1; self.update = function () { self.currentFrame++; // Expand and then fade if (self.currentFrame < 15) { explosionGraphics.scale.x += 0.1; explosionGraphics.scale.y += 0.1; } else { explosionGraphics.alpha -= 0.08; } // Remove when animation complete if (self.currentFrame >= self.duration) { self.destroy(); return true; } return false; }; return self; }); var FirePool = Container.expand(function () { var self = Container.call(this); var fireGraphics = self.attachAsset('firePool', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); // Fire pool duration (4 seconds at 60fps) self.duration = 240; self.currentFrame = 0; // Pulsate effect self.pulsateDirection = 1; self.pulsateAmount = 0; self.update = function () { self.currentFrame++; // Pulsate effect self.pulsateAmount += 0.02 * self.pulsateDirection; if (self.pulsateAmount > 0.2 || self.pulsateAmount < -0.2) { self.pulsateDirection *= -1; } fireGraphics.scale.x = 1 + self.pulsateAmount; fireGraphics.scale.y = 1 + self.pulsateAmount; // Fade out at the end if (self.currentFrame > 150) { fireGraphics.alpha -= 0.03; } // Check for enemies touching the fire pool for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.intersects(self)) { // Enemy is instantly destroyed by fire score += enemy.scoreValue; LK.setScore(score); updateScoreDisplay(); LK.effects.flashObject(enemy, 0xff0000, 300); enemy.destroy(); enemies.splice(i, 1); } } // Remove when duration complete if (self.currentFrame >= self.duration) { self.destroy(); return true; } return false; }; return self; }); var HealthBar = Container.expand(function () { var self = Container.call(this); // Health bar properties self.maxHealth = 4; self.currentHealth = 4; self.width = 140; self.height = 20; // Create background bar (gray) var background = self.attachAsset('healthbar', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, // Adjust to match bar width scaleY: 1, // Adjust to match bar height tint: 0x666666 // Gray color }); // Create foreground bar (health indicator - green) var foreground = self.attachAsset('healthbar', { anchorX: 0, anchorY: 0.5, scaleX: 1, // Adjust to match bar width scaleY: 1, // Adjust to match bar height tint: 0x00FF00 // Green color }); // Center the foreground bar foreground.x = -background.width / 2; // Update health bar visual based on current health self.updateHealth = function (health) { self.currentHealth = health; // Update the scale of the foreground bar based on current health var healthPercentage = Math.max(self.currentHealth / self.maxHealth, 0); // Scale should decrease as health decreases, not increase // Ensure that we don't go to 0 until health is actually 0 if (self.currentHealth > 0) { // For non-zero health, ensure we have at least a small visible portion of health bar foreground.scale.x = Math.max(healthPercentage, 0.05); } else { // Only when health is truly zero, set scale to zero foreground.scale.x = 0; } // Change color based on health level if (healthPercentage > 0.6) { foreground.tint = 0x00FF00; // Green } else if (healthPercentage > 0.3) { foreground.tint = 0xFFFF00; // Yellow } else { foreground.tint = 0xFF0000; // Red } }; return self; }); var Missile = Container.expand(function () { var self = Container.call(this); var missileGraphics = self.attachAsset('missile', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.speed = -10; // Moving upward self.targetX = null; self.targetY = null; // Add targetY for better homing self.turnSpeed = 0.25; // Significantly increased turn speed for better tracking self.angle = 0; // For side missiles self.update = function () { // Basic upward movement self.y += self.speed; // Enhanced target tracking if we have a target if (self.targetX && self.targetY) { // Calculate distance to target var diffX = self.targetX - self.x; var diffY = self.targetY - self.y; var dist = Math.sqrt(diffX * diffX + diffY * diffY); // Track frames since launch for initial trajectory control if (self.initialDirectionFrames === undefined) { self.initialDirectionFrames = 0; } // For the first 20 frames, maintain initial trajectory with very minimal homing // This ensures missiles start with distinctive paths if (self.initialDirectionFrames < 20) { self.initialDirectionFrames++; // Initial direction based on missile type if (self.initialDirection === 'left-up') { // Left missile veers slightly left self.x -= 2; self.y += self.speed * 0.2; // Slow vertical movement slightly } else if (self.initialDirection === 'right-up') { // Right missile veers slightly right self.x += 2; self.y += self.speed * 0.2; // Slow vertical movement slightly } else { // Center missile goes straight up // No horizontal movement, just straight up } } else { // After initial frames, apply enhanced homing behavior // Use a larger factor for more effective tracking while keeping realistic movement var homingFactor = self.turnSpeed * 0.3; // Significantly increased factor for better homing // Move more noticeably toward target x position self.x += diffX * homingFactor; // Also add vertical homing for more direct targeting self.y += diffY * (homingFactor * 0.4); // Add vertical component to homing // Apply slight angular adjustment to maintain separation // This ensures missiles maintain some of their initial trajectory if (self.angle) { self.x += Math.sin(self.angle) * 0.2; // Further reduced angular influence for better homing } } // Improved y-speed adjustment if target is far above or below if (diffY < -150) { // Increased speed adjustment for better vertical tracking self.y += self.speed * 0.06; // More aggressive speed adjustment } else if (diffY > 150) { // Increased adjustment if target is below missile self.y += self.speed * -0.04; // More noticeable slowdown } } }; // Create explosion when hitting a target or edge self.explode = function () { var explosion = new Explosion(); explosion.x = self.x; explosion.y = self.y; game.addChild(explosion); // Create fire pool after explosion var firePool = new FirePool(); firePool.x = self.x; firePool.y = self.y; game.addChild(firePool); firePools.push(firePool); // Play explosion sound LK.getSound('explosion').play(); // Damage enemies in area for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If enemy is within explosion radius if (distance < 150) { // Enemy is destroyed by area damage score += enemy.scoreValue; LK.setScore(score); updateScoreDisplay(); LK.effects.flashObject(enemy, 0xff0000, 300); enemy.destroy(); enemies.splice(i, 1); } } return true; }; return self; }); var PlayerBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -20; // Moving upward (increased speed) self.power = 1; self.direction = 'up'; // up, down, left, right self.isPiercing = false; self.isBouncing = false; self.bounceCount = 0; self.maxBounces = 3; self.enemyBulletHits = 0; // Track how many enemy bullets this player bullet has hit self.maxEnemyBulletHits = 2; // Maximum enemy bullets that can be destroyed before exploding self.update = function () { // Move bullet based on direction if (self.direction === 'up') { self.y += self.speed; // Check for bouncing against top edge if (self.isBouncing && self.y < 0 && self.bounceCount < self.maxBounces) { self.direction = 'down'; self.bounceCount++; } } else if (self.direction === 'down') { self.y -= self.speed; // Note: speed is negative, so this is adding a positive value // Check for bouncing against bottom edge if (self.isBouncing && self.y > GAME_HEIGHT && self.bounceCount < self.maxBounces) { self.direction = 'up'; self.bounceCount++; } } else if (self.direction === 'left') { self.x += self.speed; // Check for bouncing against left edge if (self.isBouncing && self.x < 0 && self.bounceCount < self.maxBounces) { self.direction = 'right'; self.bounceCount++; } } else if (self.direction === 'right') { self.x -= self.speed; // Note: speed is negative, so this is adding a positive value // Check for bouncing against right edge if (self.isBouncing && self.x > GAME_WIDTH && self.bounceCount < self.maxBounces) { self.direction = 'left'; self.bounceCount++; } } }; // Method to create explosion when player bullet is destroyed self.explode = function () { var explosion = new Explosion(); explosion.x = self.x; explosion.y = self.y; // Make player bullet explosion slightly smaller explosion.scale.x = 0.6; explosion.scale.y = 0.6; game.addChild(explosion); explosions.push(explosion); // Play explosion sound LK.getSound('explosion').play(); }; return self; }); var PlayerShip = Container.expand(function () { var self = Container.call(this); // Visual representation var shipGraphics = self.attachAsset('playerShip', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Health visual (initially invisible) var shieldGraphics = self.attachAsset('health', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 1.2, scaleY: 1.2 }); // Final life warning text var finalLifeText = new Text2('FINAL LIFE', { size: 50, fill: 0xFF0000 }); finalLifeText.anchor.set(0.5, 0.5); finalLifeText.y = -110; finalLifeText.alpha = 0; self.addChild(finalLifeText); // Player properties self.speed = 10; self.fireRate = 20; // Ticks between shots self.lastShot = 0; self.health = 4; // Player starts with 4 health self.healthBar = new HealthBar(); self.healthBar.y = -80; // Position above player self.healthBar.x = 0; // Perfectly center the healthbar with the ship self.addChild(self.healthBar); self.shieldActive = false; self.powerUpActive = false; self.quadBulletsActive = false; self.rotationSpeed = 0; self.finalLifeWarningActive = false; // Physics properties for smooth movement self.targetX = null; self.targetY = null; self.isMoving = false; self.moveTween = null; // Update method for rotation and missile text positioning self.update = function () { if (self.quadBulletsActive) { shipGraphics.rotation += self.rotationSpeed; } // Keep missile ready text below the ship if (missileReadyText && missileReadyText.visible) { missileReadyText.x = self.x; missileReadyText.y = self.y + 110; } }; // Shield activation self.activateShield = function () { self.shieldActive = true; shieldGraphics.alpha = 0.5; // Shield times out after 5 seconds LK.setTimeout(function () { self.shieldActive = false; shieldGraphics.alpha = 0; }, 5000); }; // Power-up activation self.activatePowerUp = function () { self.powerUpActive = true; shipGraphics.tint = 0xf1c40f; // Yellow tint for power-up self.fireRate = 10; // Faster firing // Power-up times out after 7 seconds LK.setTimeout(function () { self.powerUpActive = false; shipGraphics.tint = 0xFFFFFF; self.fireRate = 20; }, 7000); }; // Quad bullets activation self.activateQuadBullets = function () { self.quadBulletsActive = true; shipGraphics.tint = 0xe74c3c; // Red tint for quad bullets self.fireRate = 15; // Medium firing speed self.rotationSpeed = 0.05; // Start spinning // Quad bullets time out after 8 seconds LK.setTimeout(function () { self.quadBulletsActive = false; shipGraphics.tint = 0xFFFFFF; self.fireRate = 20; self.rotationSpeed = 0; shipGraphics.rotation = 0; // Reset rotation }, 8000); }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerUpGraphics = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.speed = 3; // Add quad bullet type with 1/3 chance var rand = Math.random(); if (rand < 0.33) { self.type = 'health'; } else if (rand < 0.66) { self.type = 'weapon'; } else { self.type = 'quadBullets'; } // Set color based on type if (self.type === 'health') { powerUpGraphics.tint = 0x2ecc71; // Green for health } else if (self.type === 'weapon') { powerUpGraphics.tint = 0xf1c40f; // Yellow for weapon } else { powerUpGraphics.tint = 0xe74c3c; // Red for quad bullets } self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var SPAWN_ENEMY_INTERVAL = 60; // Spawn enemy every 60 ticks initially var SPAWN_POWERUP_INTERVAL = 600; // Spawn power-up every 600 ticks var MIN_SPAWN_INTERVAL = 20; // Minimum spawn rate as difficulty increases // Game state variables var player; var playerBullets = []; var enemies = []; var enemyBullets = []; var powerUps = []; var missiles = []; var explosions = []; var firePools = []; var gameLevel = 1; var spawnCounter = 0; var powerUpCounter = 0; var isGameActive = true; var score = 0; var lastMissileTime = 0; var missileReadyText = new Text2('MISSILE READY', { size: 40, fill: 0xFF0000 }); // Initialize score display var scoreTxt = new Text2('SCORE: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.y = 30; // Initialize level display var levelTxt = new Text2('LEVEL: 1', { size: 60, fill: 0xFFFFFF }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); levelTxt.y = 100; // Create player ship function initializeGame() { player = new PlayerShip(); player.x = GAME_WIDTH / 2; player.y = GAME_HEIGHT - 200; game.addChild(player); // Reset arrays playerBullets = []; enemies = []; enemyBullets = []; powerUps = []; missiles = []; explosions = []; firePools = []; // Reset game state gameLevel = 1; spawnCounter = 0; powerUpCounter = 0; isGameActive = true; lastMissileTime = 0; // Initialize missile ready text missileReadyText.anchor.set(0.5, 0.5); missileReadyText.alpha = 1; missileReadyText.visible = true; // Set initial missile time to be ready at game start lastMissileTime = LK.ticks - 600; // Position text below the player ship game.addChild(missileReadyText); // Flash the text immediately var _flashMissileText = function flashMissileText() { tween(missileReadyText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { tween(missileReadyText, { alpha: 1 }, { duration: 500, onFinish: _flashMissileText }); } }); }; _flashMissileText(); // Reset score and update display LK.setScore(0); score = 0; updateScoreDisplay(); updateLevelDisplay(); // Play background music LK.playMusic('gameMusic'); } // Initialize the game initializeGame(); // Update score display function updateScoreDisplay() { scoreTxt.setText('SCORE: ' + score); } // Update level display function updateLevelDisplay() { levelTxt.setText('LEVEL: ' + gameLevel); } // Player shooting function playerShoot() { // Check fire rate cooldown if (LK.ticks - player.lastShot < player.fireRate) { return; } // Always fire dual cannons (left and right) var bulletLeft = new PlayerBullet(); bulletLeft.x = player.x - 20; bulletLeft.y = player.y - 40; bulletLeft.direction = 'up'; game.addChild(bulletLeft); playerBullets.push(bulletLeft); var bulletRight = new PlayerBullet(); bulletRight.x = player.x + 20; bulletRight.y = player.y - 40; bulletRight.direction = 'up'; game.addChild(bulletRight); playerBullets.push(bulletRight); // Triple bullets if weapon power-up is active if (player.powerUpActive) { var bulletCenter = new PlayerBullet(); bulletCenter.x = player.x; bulletCenter.y = player.y - 50; bulletCenter.direction = 'up'; game.addChild(bulletCenter); playerBullets.push(bulletCenter); } // Quad-directional bullets if quad bullets power-up is active if (player.quadBulletsActive) { // Up bullet already covered // Down bullet var bulletDown = new PlayerBullet(); bulletDown.x = player.x; bulletDown.y = player.y + 40; bulletDown.direction = 'down'; bulletDown.isPiercing = true; bulletDown.isBouncing = true; game.addChild(bulletDown); playerBullets.push(bulletDown); // Left bullet var bulletLeft = new PlayerBullet(); bulletLeft.x = player.x - 40; bulletLeft.y = player.y; bulletLeft.direction = 'left'; bulletLeft.isPiercing = true; bulletLeft.isBouncing = true; game.addChild(bulletLeft); playerBullets.push(bulletLeft); // Right bullet var bulletRight = new PlayerBullet(); bulletRight.x = player.x + 40; bulletRight.y = player.y; bulletRight.direction = 'right'; bulletRight.isPiercing = true; bulletRight.isBouncing = true; game.addChild(bulletRight); playerBullets.push(bulletRight); } // Play sound and reset shot timer LK.getSound('playerShoot').play(); player.lastShot = LK.ticks; } // Enemy shooting function enemyShoot(enemy) { if (LK.ticks - enemy.lastShot < enemy.fireRate) { return; } var bullet = new EnemyBullet(); bullet.x = enemy.x; bullet.y = enemy.y + 40; game.addChild(bullet); enemyBullets.push(bullet); LK.getSound('enemyShoot').play(); enemy.lastShot = LK.ticks; } // Spawn new enemy function spawnEnemy() { var enemy = new Enemy(); // Distribute enemies better across the screen width // Check if we have other enemies and avoid spawning too close to them if (enemies.length > 0) { // Find a position away from other enemies var attempts = 0; var potentialX; var validPosition = false; while (!validPosition && attempts < 10) { potentialX = Math.random() * (GAME_WIDTH - 100) + 50; validPosition = true; // Check distance from existing enemies for (var i = 0; i < enemies.length; i++) { if (Math.abs(enemies[i].x - potentialX) < 150) { validPosition = false; break; } } attempts++; } enemy.x = validPosition ? potentialX : Math.random() * (GAME_WIDTH - 100) + 50; } else { enemy.x = Math.random() * (GAME_WIDTH - 100) + 50; } enemy.y = -50; // Increase difficulty with levels if (gameLevel > 1) { enemy.speed = Math.min(2 + gameLevel * 0.5, 6); enemy.health = Math.min(1 + Math.floor(gameLevel / 3), 3); } if (gameLevel > 5) { enemy.fireRate = Math.max(120 - gameLevel * 5, 60); } game.addChild(enemy); enemies.push(enemy); } // Spawn power-up function spawnPowerUp() { var powerUp = new PowerUp(); powerUp.x = Math.random() * (GAME_WIDTH - 100) + 50; powerUp.y = -50; game.addChild(powerUp); powerUps.push(powerUp); } // Check level progress function checkLevelProgress() { // Advance level based on score var shouldAdvanceLevel = Math.floor(score / 1000) + 1; if (shouldAdvanceLevel > gameLevel) { gameLevel = shouldAdvanceLevel; updateLevelDisplay(); // Flash screen to indicate level up LK.effects.flashScreen(0x3498db, 500); } } // Handle collisions function handleCollisions() { // Check player bullets hitting enemies for (var i = playerBullets.length - 1; i >= 0; i--) { var bullet = playerBullets[i]; var bulletHit = false; // First check for player bullet hitting enemy bullets // Skip enemy bullet collision during quad bullets powerup to prevent lag if (!player.quadBulletsActive) { for (var j = enemyBullets.length - 1; j >= 0; j--) { if (bullet.intersects(enemyBullets[j])) { // Create explosion animation for the enemy bullet var explosion = new Explosion(); explosion.x = enemyBullets[j].x; explosion.y = enemyBullets[j].y; explosion.scale.x = 0.5; // Smaller explosion for bullets explosion.scale.y = 0.5; game.addChild(explosion); explosions.push(explosion); // Remove enemy bullet enemyBullets[j].destroy(); enemyBullets.splice(j, 1); // Increment hit counter for player bullet bullet.enemyBulletHits++; // Check if player bullet has reached its hit limit if (bullet.enemyBulletHits >= bullet.maxEnemyBulletHits) { // If hit limit reached, destroy the player bullet with explosion bullet.explode(); bullet.destroy(); playerBullets.splice(i, 1); // Create explosion animation at enemy position var explosion = new Explosion(); explosion.x = enemy.x; explosion.y = enemy.y; game.addChild(explosion); explosions.push(explosion); // Remove enemy bulletHit = true; break; } } } } // If player bullet was destroyed by enemy bullets, skip enemy collision check if (bulletHit) { continue; } // Now check for player bullet hitting enemies for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (bullet.intersects(enemy)) { // Enemy takes damage if (enemy.takeDamage(bullet.power)) { // Enemy destroyed score += enemy.scoreValue; LK.setScore(score); updateScoreDisplay(); LK.getSound('explosion').play(); LK.effects.flashObject(enemy, 0xff0000, 300); // Create explosion animation at enemy position var explosion = new Explosion(); explosion.x = enemy.x; explosion.y = enemy.y; game.addChild(explosion); explosions.push(explosion); // Remove enemy enemy.destroy(); enemies.splice(j, 1); } // Only remove non-piercing bullets on hit if (!bullet.isPiercing) { bullet.explode(); // Add explosion effect when bullet is destroyed bullet.destroy(); playerBullets.splice(i, 1); bulletHit = true; break; } } } // If a non-piercing bullet hit something, skip to the next bullet if (bulletHit) { continue; } } // Check enemy bullets hitting player for (var i = enemyBullets.length - 1; i >= 0; i--) { var bullet = enemyBullets[i]; if (bullet.intersects(player)) { // Player hit bullet.destroy(); enemyBullets.splice(i, 1); if (player.shieldActive) { // Shield absorbs the hit player.shieldActive = false; var shield = player.getChildAt(1); shield.alpha = 0; } else { // Reduce player health player.health--; player.healthBar.updateHealth(player.health); // Flash player to indicate damage LK.effects.flashObject(player, 0xff0000, 300); // Check if player health reaches 1 (final life) if (player.health === 1 && !player.finalLifeWarningActive) { player.finalLifeWarningActive = true; // Hide health bar completely player.healthBar.visible = false; // Show and animate "Final Life" text var finalLifeText = player.getChildAt(2); // Get the final life text finalLifeText.alpha = 1; // Create flashing effect for the text var _flashTween2 = function flashTween() { tween(finalLifeText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { tween(finalLifeText, { alpha: 1 }, { duration: 500, onFinish: _flashTween2 }); } }); }; _flashTween2(); // Start the flashing animation } // Check if player is out of health if (player.health <= 0) { // Game over LK.effects.flashScreen(0xff0000, 1000); isGameActive = false; LK.showGameOver(); break; } } } } // Check enemies colliding with player for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.intersects(player)) { if (player.shieldActive) { // Shield absorbs the hit enemy.destroy(); enemies.splice(i, 1); // Shield gets depleted player.shieldActive = false; var shield = player.getChildAt(1); shield.alpha = 0; score += enemy.scoreValue; LK.setScore(score); updateScoreDisplay(); } else { // Reduce player health by 2 (collision is more dangerous) player.health -= 2; player.healthBar.updateHealth(player.health); // Remove the enemy enemy.destroy(); enemies.splice(i, 1); // Flash player to indicate damage LK.effects.flashObject(player, 0xff0000, 300); // Check if player health reaches 1 (final life) if (player.health === 1 && !player.finalLifeWarningActive) { player.finalLifeWarningActive = true; // Hide health bar completely player.healthBar.visible = false; // Show and animate "Final Life" text var finalLifeText = player.getChildAt(2); // Get the final life text finalLifeText.alpha = 1; // Create flashing effect for the text var _flashTween2 = function _flashTween() { tween(finalLifeText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { tween(finalLifeText, { alpha: 1 }, { duration: 500, onFinish: _flashTween2 }); } }); }; _flashTween2(); // Start the flashing animation } // Check if player is out of health if (player.health <= 0) { // Game over LK.effects.flashScreen(0xff0000, 1000); isGameActive = false; LK.showGameOver(); break; } } } } // Check player collecting power-ups for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; if (powerUp.intersects(player)) { LK.getSound('powerUp').play(); // Apply power-up effect if (powerUp.type === 'health') { // Health regeneration logic if (player.health < player.healthBar.maxHealth) { // Add 1 health point player.health = Math.min(player.health + 1, player.healthBar.maxHealth); // If player was at final life, restore health bar and remove warning if (player.finalLifeWarningActive && player.health > 1) { player.finalLifeWarningActive = false; player.healthBar.visible = true; // Get the final life text and hide it var finalLifeText = player.getChildAt(2); // Stop any existing tweens on the final life text tween.stopTweens(finalLifeText); finalLifeText.alpha = 0; } // Update health bar player.healthBar.updateHealth(player.health); } } else if (powerUp.type === 'weapon') { player.activatePowerUp(); } else if (powerUp.type === 'quadBullets') { player.activateQuadBullets(); } // Remove power-up powerUp.destroy(); powerUps.splice(i, 1); } } } // Clean up off-screen objects function cleanupOffscreenObjects() { // Clean up missiles that went off screen for (var i = missiles.length - 1; i >= 0; i--) { var missile = missiles[i]; if (missile.y < -50 || missile.y > GAME_HEIGHT + 50 || missile.x < -50 || missile.x > GAME_WIDTH + 50) { missile.explode(); missile.destroy(); missiles.splice(i, 1); } } // Clean up bullets that went off screen for (var i = playerBullets.length - 1; i >= 0; i--) { var bullet = playerBullets[i]; // Check if bullet is off screen based on direction (unless it's bouncing) if (!bullet.isBouncing) { if (bullet.direction === 'up' && bullet.y < -50 || bullet.direction === 'down' && bullet.y > GAME_HEIGHT + 50 || bullet.direction === 'left' && bullet.x < -50 || bullet.direction === 'right' && bullet.x > GAME_WIDTH + 50) { bullet.destroy(); playerBullets.splice(i, 1); } } else if (bullet.bounceCount >= bullet.maxBounces) { // Add explosion for bouncing bullets that reached max bounce count bullet.explode(); bullet.destroy(); playerBullets.splice(i, 1); } } for (var i = enemyBullets.length - 1; i >= 0; i--) { if (enemyBullets[i].y > GAME_HEIGHT + 50) { enemyBullets[i].destroy(); enemyBullets.splice(i, 1); } } // Clean up enemies that went off screen for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i].y > GAME_HEIGHT + 100) { enemies[i].destroy(); enemies.splice(i, 1); } } // Clean up power-ups that went off screen for (var i = powerUps.length - 1; i >= 0; i--) { if (powerUps[i].y > GAME_HEIGHT + 50) { powerUps[i].destroy(); powerUps.splice(i, 1); } } } // Player movement var isDragging = false; game.down = function (x, y) { // Fire missile on left click if cooldown is ready if (LK.ticks - lastMissileTime >= 600) { // 10 seconds at 60fps // Find closest enemy as target first var closestEnemy = null; var closestDistance = Number.MAX_VALUE; var potentialTargets = []; // Find up to 6 potential targets for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - player.x; var dy = enemy.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } // Add to potential targets list (sort by proximity later) potentialTargets.push({ enemy: enemy, distance: distance }); } // Sort targets by distance potentialTargets.sort(function (a, b) { return a.distance - b.distance; }); // Launch three missiles from the same vertical position but different horizontal positions // Center missile - positioned exactly in the center of player ship var centerMissile = new Missile(); centerMissile.x = player.x; // Center horizontally with player centerMissile.y = player.y - 60; // Launch from front center of player ship centerMissile.turnSpeed = 0.05; // Very slight homing for center missile centerMissile.initialDirection = 'up'; // Make sure it exits straight up first // Left missile - positioned to the left of center missile var leftMissile = new Missile(); leftMissile.x = player.x - 30; // Position left of the player leftMissile.y = player.y - 60; // Same vertical position as center missile leftMissile.angle = -0.3; // Slight left angle for initial trajectory leftMissile.turnSpeed = 0.07; // Reduced homing for more separation leftMissile.initialDirection = 'left-up'; // Make sure it exits left-up first // Right missile - positioned to the right of center missile var rightMissile = new Missile(); rightMissile.x = player.x + 30; // Position right of the player rightMissile.y = player.y - 60; // Same vertical position as center missile rightMissile.angle = 0.3; // Slight right angle for initial trajectory rightMissile.turnSpeed = 0.07; // Reduced homing for more separation rightMissile.initialDirection = 'right-up'; // Make sure it exits right-up first // If we found a target, set it for missiles with different offsets to avoid pairing if (closestEnemy) { // Set slightly different targets for each missile to avoid pairing centerMissile.targetX = closestEnemy.x; centerMissile.targetY = closestEnemy.y; // For side missiles, either use different enemies or offset from main target if (potentialTargets.length > 1) { leftMissile.targetX = potentialTargets[Math.min(1, potentialTargets.length - 1)].enemy.x - 50; leftMissile.targetY = potentialTargets[Math.min(1, potentialTargets.length - 1)].enemy.y; } else { // If not enough targets, offset significantly from main target leftMissile.targetX = closestEnemy.x - 150; leftMissile.targetY = closestEnemy.y; } if (potentialTargets.length > 2) { rightMissile.targetX = potentialTargets[Math.min(2, potentialTargets.length - 1)].enemy.x + 50; rightMissile.targetY = potentialTargets[Math.min(2, potentialTargets.length - 1)].enemy.y; } else { // If not enough targets, offset significantly from main target rightMissile.targetX = closestEnemy.x + 150; rightMissile.targetY = closestEnemy.y; } } // Add first wave of missiles to game game.addChild(centerMissile); missiles.push(centerMissile); game.addChild(leftMissile); missiles.push(leftMissile); game.addChild(rightMissile); missiles.push(rightMissile); // Create a second wave of missiles with slightly different launch positions // Second center missile var centerMissile2 = new Missile(); centerMissile2.x = player.x; centerMissile2.y = player.y - 30; // Slightly higher than first wave centerMissile2.turnSpeed = 0.06; centerMissile2.initialDirection = 'up'; // Second left missile var leftMissile2 = new Missile(); leftMissile2.x = player.x - 40; // Different horizontal offset leftMissile2.y = player.y - 30; leftMissile2.angle = -0.4; // Different initial angle leftMissile2.turnSpeed = 0.08; leftMissile2.initialDirection = 'left-up'; // Second right missile var rightMissile2 = new Missile(); rightMissile2.x = player.x + 40; // Different horizontal offset rightMissile2.y = player.y - 30; rightMissile2.angle = 0.4; // Different initial angle rightMissile2.turnSpeed = 0.08; rightMissile2.initialDirection = 'right-up'; // Set targets for second wave if (closestEnemy) { // Set slightly different targets for second wave missiles if (potentialTargets.length > 3) { centerMissile2.targetX = potentialTargets[3].enemy.x; centerMissile2.targetY = potentialTargets[3].enemy.y; } else { centerMissile2.targetX = closestEnemy.x + 50; centerMissile2.targetY = closestEnemy.y + 50; } if (potentialTargets.length > 4) { leftMissile2.targetX = potentialTargets[4].enemy.x - 70; leftMissile2.targetY = potentialTargets[4].enemy.y; } else { leftMissile2.targetX = closestEnemy.x - 200; leftMissile2.targetY = closestEnemy.y + 50; } if (potentialTargets.length > 5) { rightMissile2.targetX = potentialTargets[5].enemy.x + 70; rightMissile2.targetY = potentialTargets[5].enemy.y; } else { rightMissile2.targetX = closestEnemy.x + 200; rightMissile2.targetY = closestEnemy.y + 50; } } // Add second wave of missiles to game game.addChild(centerMissile2); missiles.push(centerMissile2); game.addChild(leftMissile2); missiles.push(leftMissile2); game.addChild(rightMissile2); missiles.push(rightMissile2); // Play missile launch sound LK.getSound('missileLaunch').play(); // Reset cooldown lastMissileTime = LK.ticks; // Hide missile ready text and stop any active tweens tween.stop(missileReadyText); missileReadyText.visible = false; } // Handle regular player movement var targetX = x; var targetY = y; // Clamp target position to game bounds if (targetX < 50) { targetX = 50; } if (targetX > GAME_WIDTH - 50) { targetX = GAME_WIDTH - 50; } if (targetY < 50) { targetY = 50; } if (targetY > GAME_HEIGHT - 50) { targetY = GAME_HEIGHT - 50; } player.targetX = targetX; player.targetY = targetY; player.isMoving = true; // Also shoot when player taps playerShoot(); }; game.move = function (x, y) { if (isGameActive) { // Clamp target position to game bounds var targetX = x; var targetY = y; if (targetX < 50) { targetX = 50; } if (targetX > GAME_WIDTH - 50) { targetX = GAME_WIDTH - 50; } if (targetY < 50) { targetY = 50; } if (targetY > GAME_HEIGHT - 50) { targetY = GAME_HEIGHT - 50; } // Update target position player.targetX = targetX; player.targetY = targetY; // For better tracking during fast mouse movements, directly update position player.x = player.targetX; player.y = player.targetY; player.isMoving = true; //{2K}{2L}{2M}{2N} } }; game.up = function () { // Keep the ship moving toward last target point }; // Main game update loop game.update = function () { if (!isGameActive) { return; } // Update missile ready text if (LK.ticks - lastMissileTime >= 600) { // 10 seconds cooldown if (!missileReadyText.visible) { missileReadyText.visible = true; missileReadyText.x = player.x; missileReadyText.y = player.y + 110; missileReadyText.alpha = 1; // Flash the text when ready var _flashMissileText2 = function flashMissileText() { tween(missileReadyText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { tween(missileReadyText, { alpha: 1 }, { duration: 500, onFinish: _flashMissileText2 }); } }); }; _flashMissileText2(); } } // Spawn enemies spawnCounter++; var currentSpawnRate = Math.max(SPAWN_ENEMY_INTERVAL - gameLevel * 5, MIN_SPAWN_INTERVAL); if (spawnCounter >= currentSpawnRate) { spawnEnemy(); spawnCounter = 0; } // Spawn power-ups powerUpCounter++; if (powerUpCounter >= SPAWN_POWERUP_INTERVAL) { spawnPowerUp(); powerUpCounter = 0; } // Make player shoot automatically if (player.isMoving) { playerShoot(); } // Update enemy shooting for (var i = 0; i < enemies.length; i++) { enemyShoot(enemies[i]); } // Update missiles for (var i = missiles.length - 1; i >= 0; i--) { var missile = missiles[i]; // If missile doesn't have a target or target is destroyed, find new target if ((missile.targetX === null || missile.targetY === null) && enemies.length > 0) { // Find closest enemy as new target var closestEnemy = null; var closestDistance = Number.MAX_VALUE; // Check all enemies for potential targets for (var k = 0; k < enemies.length; k++) { var dx = enemies[k].x - missile.x; var dy = enemies[k].y - missile.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemies[k]; } } // If we found a target, set it with slight offset based on missile type if (closestEnemy) { // Add slightly different offsets to each missile's target to maintain separation // We can determine missile "type" by its position relative to player var offsetX = 0; var offsetY = 0; // Adding vertical offset for better separation // Using angle to identify missile type if (missile.angle < 0) { // Left missile offsetX = -100; // Increased horizontal offset offsetY = -20; // Slight vertical offset } else if (missile.angle > 0) { // Right missile offsetX = 100; // Increased horizontal offset offsetY = -20; // Slight vertical offset } else { // Center missile offsetY = -50; // Different vertical offset for center missile } missile.targetX = closestEnemy.x + offsetX; missile.targetY = closestEnemy.y + offsetY; } } // Check if missile hits an enemy var missileHit = false; for (var j = enemies.length - 1; j >= 0; j--) { if (missile.intersects(enemies[j])) { missile.explode(); missile.destroy(); missiles.splice(i, 1); missileHit = true; break; } } // Check if missile is off screen if (!missileHit && (missile.y < -50 || missile.y > GAME_HEIGHT + 50 || missile.x < -50 || missile.x > GAME_WIDTH + 50)) { missile.explode(); missile.destroy(); missiles.splice(i, 1); } } // Update explosions for (var i = explosions.length - 1; i >= 0; i--) { if (explosions[i].update()) { explosions.splice(i, 1); } } // Update fire pools for (var i = firePools.length - 1; i >= 0; i--) { if (firePools[i].update()) { firePools.splice(i, 1); } } // Check collisions handleCollisions(); // Clean up off-screen objects cleanupOffscreenObjects(); // Check level progression checkLevelProgress(); // Win condition check if (score >= 10000) { LK.showYouWin(); isGameActive = false; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Enemy properties
self.health = 1;
self.speed = 2;
self.scoreValue = 100;
self.fireRate = 120; // Ticks between shots
self.lastShot = Math.floor(Math.random() * 60); // Randomize initial shot timer
self.movementPattern = Math.floor(Math.random() * 3); // 0: straight, 1: zigzag, 2: circular
self.movementCounter = 0;
// Initial target position (will be set during movement)
self.targetX = null;
self.update = function () {
// Basic downward movement
self.y += self.speed;
// Apply movement pattern
if (self.movementPattern === 1) {
// Zigzag with occasional side movement
self.x += Math.sin(self.movementCounter * 0.05) * 3;
// Occasionally move to a different horizontal position
if (self.movementCounter % 500 === 0) {
// Choose a new target position on the opposite side
self.targetX = self.x < GAME_WIDTH / 2 ? GAME_WIDTH * 0.7 + Math.random() * GAME_WIDTH * 0.2 : GAME_WIDTH * 0.1 + Math.random() * GAME_WIDTH * 0.2;
}
if (self.targetX) {
var diffX = self.targetX - self.x;
if (Math.abs(diffX) > 10) {
self.x += diffX * 0.03;
}
}
self.movementCounter++;
} else if (self.movementPattern === 2) {
// Circular with occasional repositioning
self.x += Math.sin(self.movementCounter * 0.03) * 4;
// Occasionally reset position to avoid grouping
if (self.movementCounter % 700 === 0) {
// Move to random position on screen
self.targetX = Math.random() * GAME_WIDTH * 0.8 + GAME_WIDTH * 0.1;
}
// Move toward target if exists
if (self.targetX) {
var diffX = self.targetX - self.x;
if (Math.abs(diffX) > 10) {
self.x += diffX * 0.02;
}
}
self.movementCounter++;
} else {
// Add movement for straight pattern (pattern 0)
if (self.movementCounter % 900 === 0) {
// Occasional side movement
self.targetX = Math.random() * GAME_WIDTH * 0.8 + GAME_WIDTH * 0.1;
}
// Move toward target if exists
if (self.targetX) {
var diffX = self.targetX - self.x;
if (Math.abs(diffX) > 10) {
self.x += diffX * 0.01;
}
}
self.movementCounter++;
}
};
// Take damage and check if destroyed
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
return true; // Enemy is destroyed
}
return false;
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7; // Moving downward
self.update = function () {
self.y += self.speed;
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
// Explosion duration
self.duration = 30; // frames
self.currentFrame = 0;
// Scale explosion from small to large then fade
explosionGraphics.scale.x = 0.1;
explosionGraphics.scale.y = 0.1;
self.update = function () {
self.currentFrame++;
// Expand and then fade
if (self.currentFrame < 15) {
explosionGraphics.scale.x += 0.1;
explosionGraphics.scale.y += 0.1;
} else {
explosionGraphics.alpha -= 0.08;
}
// Remove when animation complete
if (self.currentFrame >= self.duration) {
self.destroy();
return true;
}
return false;
};
return self;
});
var FirePool = Container.expand(function () {
var self = Container.call(this);
var fireGraphics = self.attachAsset('firePool', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
// Fire pool duration (4 seconds at 60fps)
self.duration = 240;
self.currentFrame = 0;
// Pulsate effect
self.pulsateDirection = 1;
self.pulsateAmount = 0;
self.update = function () {
self.currentFrame++;
// Pulsate effect
self.pulsateAmount += 0.02 * self.pulsateDirection;
if (self.pulsateAmount > 0.2 || self.pulsateAmount < -0.2) {
self.pulsateDirection *= -1;
}
fireGraphics.scale.x = 1 + self.pulsateAmount;
fireGraphics.scale.y = 1 + self.pulsateAmount;
// Fade out at the end
if (self.currentFrame > 150) {
fireGraphics.alpha -= 0.03;
}
// Check for enemies touching the fire pool
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.intersects(self)) {
// Enemy is instantly destroyed by fire
score += enemy.scoreValue;
LK.setScore(score);
updateScoreDisplay();
LK.effects.flashObject(enemy, 0xff0000, 300);
enemy.destroy();
enemies.splice(i, 1);
}
}
// Remove when duration complete
if (self.currentFrame >= self.duration) {
self.destroy();
return true;
}
return false;
};
return self;
});
var HealthBar = Container.expand(function () {
var self = Container.call(this);
// Health bar properties
self.maxHealth = 4;
self.currentHealth = 4;
self.width = 140;
self.height = 20;
// Create background bar (gray)
var background = self.attachAsset('healthbar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
// Adjust to match bar width
scaleY: 1,
// Adjust to match bar height
tint: 0x666666 // Gray color
});
// Create foreground bar (health indicator - green)
var foreground = self.attachAsset('healthbar', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1,
// Adjust to match bar width
scaleY: 1,
// Adjust to match bar height
tint: 0x00FF00 // Green color
});
// Center the foreground bar
foreground.x = -background.width / 2;
// Update health bar visual based on current health
self.updateHealth = function (health) {
self.currentHealth = health;
// Update the scale of the foreground bar based on current health
var healthPercentage = Math.max(self.currentHealth / self.maxHealth, 0);
// Scale should decrease as health decreases, not increase
// Ensure that we don't go to 0 until health is actually 0
if (self.currentHealth > 0) {
// For non-zero health, ensure we have at least a small visible portion of health bar
foreground.scale.x = Math.max(healthPercentage, 0.05);
} else {
// Only when health is truly zero, set scale to zero
foreground.scale.x = 0;
}
// Change color based on health level
if (healthPercentage > 0.6) {
foreground.tint = 0x00FF00; // Green
} else if (healthPercentage > 0.3) {
foreground.tint = 0xFFFF00; // Yellow
} else {
foreground.tint = 0xFF0000; // Red
}
};
return self;
});
var Missile = Container.expand(function () {
var self = Container.call(this);
var missileGraphics = self.attachAsset('missile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.speed = -10; // Moving upward
self.targetX = null;
self.targetY = null; // Add targetY for better homing
self.turnSpeed = 0.25; // Significantly increased turn speed for better tracking
self.angle = 0; // For side missiles
self.update = function () {
// Basic upward movement
self.y += self.speed;
// Enhanced target tracking if we have a target
if (self.targetX && self.targetY) {
// Calculate distance to target
var diffX = self.targetX - self.x;
var diffY = self.targetY - self.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
// Track frames since launch for initial trajectory control
if (self.initialDirectionFrames === undefined) {
self.initialDirectionFrames = 0;
}
// For the first 20 frames, maintain initial trajectory with very minimal homing
// This ensures missiles start with distinctive paths
if (self.initialDirectionFrames < 20) {
self.initialDirectionFrames++;
// Initial direction based on missile type
if (self.initialDirection === 'left-up') {
// Left missile veers slightly left
self.x -= 2;
self.y += self.speed * 0.2; // Slow vertical movement slightly
} else if (self.initialDirection === 'right-up') {
// Right missile veers slightly right
self.x += 2;
self.y += self.speed * 0.2; // Slow vertical movement slightly
} else {
// Center missile goes straight up
// No horizontal movement, just straight up
}
} else {
// After initial frames, apply enhanced homing behavior
// Use a larger factor for more effective tracking while keeping realistic movement
var homingFactor = self.turnSpeed * 0.3; // Significantly increased factor for better homing
// Move more noticeably toward target x position
self.x += diffX * homingFactor;
// Also add vertical homing for more direct targeting
self.y += diffY * (homingFactor * 0.4); // Add vertical component to homing
// Apply slight angular adjustment to maintain separation
// This ensures missiles maintain some of their initial trajectory
if (self.angle) {
self.x += Math.sin(self.angle) * 0.2; // Further reduced angular influence for better homing
}
}
// Improved y-speed adjustment if target is far above or below
if (diffY < -150) {
// Increased speed adjustment for better vertical tracking
self.y += self.speed * 0.06; // More aggressive speed adjustment
} else if (diffY > 150) {
// Increased adjustment if target is below missile
self.y += self.speed * -0.04; // More noticeable slowdown
}
}
};
// Create explosion when hitting a target or edge
self.explode = function () {
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Create fire pool after explosion
var firePool = new FirePool();
firePool.x = self.x;
firePool.y = self.y;
game.addChild(firePool);
firePools.push(firePool);
// Play explosion sound
LK.getSound('explosion').play();
// Damage enemies in area
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If enemy is within explosion radius
if (distance < 150) {
// Enemy is destroyed by area damage
score += enemy.scoreValue;
LK.setScore(score);
updateScoreDisplay();
LK.effects.flashObject(enemy, 0xff0000, 300);
enemy.destroy();
enemies.splice(i, 1);
}
}
return true;
};
return self;
});
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -20; // Moving upward (increased speed)
self.power = 1;
self.direction = 'up'; // up, down, left, right
self.isPiercing = false;
self.isBouncing = false;
self.bounceCount = 0;
self.maxBounces = 3;
self.enemyBulletHits = 0; // Track how many enemy bullets this player bullet has hit
self.maxEnemyBulletHits = 2; // Maximum enemy bullets that can be destroyed before exploding
self.update = function () {
// Move bullet based on direction
if (self.direction === 'up') {
self.y += self.speed;
// Check for bouncing against top edge
if (self.isBouncing && self.y < 0 && self.bounceCount < self.maxBounces) {
self.direction = 'down';
self.bounceCount++;
}
} else if (self.direction === 'down') {
self.y -= self.speed; // Note: speed is negative, so this is adding a positive value
// Check for bouncing against bottom edge
if (self.isBouncing && self.y > GAME_HEIGHT && self.bounceCount < self.maxBounces) {
self.direction = 'up';
self.bounceCount++;
}
} else if (self.direction === 'left') {
self.x += self.speed;
// Check for bouncing against left edge
if (self.isBouncing && self.x < 0 && self.bounceCount < self.maxBounces) {
self.direction = 'right';
self.bounceCount++;
}
} else if (self.direction === 'right') {
self.x -= self.speed; // Note: speed is negative, so this is adding a positive value
// Check for bouncing against right edge
if (self.isBouncing && self.x > GAME_WIDTH && self.bounceCount < self.maxBounces) {
self.direction = 'left';
self.bounceCount++;
}
}
};
// Method to create explosion when player bullet is destroyed
self.explode = function () {
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = self.y;
// Make player bullet explosion slightly smaller
explosion.scale.x = 0.6;
explosion.scale.y = 0.6;
game.addChild(explosion);
explosions.push(explosion);
// Play explosion sound
LK.getSound('explosion').play();
};
return self;
});
var PlayerShip = Container.expand(function () {
var self = Container.call(this);
// Visual representation
var shipGraphics = self.attachAsset('playerShip', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Health visual (initially invisible)
var shieldGraphics = self.attachAsset('health', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
});
// Final life warning text
var finalLifeText = new Text2('FINAL LIFE', {
size: 50,
fill: 0xFF0000
});
finalLifeText.anchor.set(0.5, 0.5);
finalLifeText.y = -110;
finalLifeText.alpha = 0;
self.addChild(finalLifeText);
// Player properties
self.speed = 10;
self.fireRate = 20; // Ticks between shots
self.lastShot = 0;
self.health = 4; // Player starts with 4 health
self.healthBar = new HealthBar();
self.healthBar.y = -80; // Position above player
self.healthBar.x = 0; // Perfectly center the healthbar with the ship
self.addChild(self.healthBar);
self.shieldActive = false;
self.powerUpActive = false;
self.quadBulletsActive = false;
self.rotationSpeed = 0;
self.finalLifeWarningActive = false;
// Physics properties for smooth movement
self.targetX = null;
self.targetY = null;
self.isMoving = false;
self.moveTween = null;
// Update method for rotation and missile text positioning
self.update = function () {
if (self.quadBulletsActive) {
shipGraphics.rotation += self.rotationSpeed;
}
// Keep missile ready text below the ship
if (missileReadyText && missileReadyText.visible) {
missileReadyText.x = self.x;
missileReadyText.y = self.y + 110;
}
};
// Shield activation
self.activateShield = function () {
self.shieldActive = true;
shieldGraphics.alpha = 0.5;
// Shield times out after 5 seconds
LK.setTimeout(function () {
self.shieldActive = false;
shieldGraphics.alpha = 0;
}, 5000);
};
// Power-up activation
self.activatePowerUp = function () {
self.powerUpActive = true;
shipGraphics.tint = 0xf1c40f; // Yellow tint for power-up
self.fireRate = 10; // Faster firing
// Power-up times out after 7 seconds
LK.setTimeout(function () {
self.powerUpActive = false;
shipGraphics.tint = 0xFFFFFF;
self.fireRate = 20;
}, 7000);
};
// Quad bullets activation
self.activateQuadBullets = function () {
self.quadBulletsActive = true;
shipGraphics.tint = 0xe74c3c; // Red tint for quad bullets
self.fireRate = 15; // Medium firing speed
self.rotationSpeed = 0.05; // Start spinning
// Quad bullets time out after 8 seconds
LK.setTimeout(function () {
self.quadBulletsActive = false;
shipGraphics.tint = 0xFFFFFF;
self.fireRate = 20;
self.rotationSpeed = 0;
shipGraphics.rotation = 0; // Reset rotation
}, 8000);
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.speed = 3;
// Add quad bullet type with 1/3 chance
var rand = Math.random();
if (rand < 0.33) {
self.type = 'health';
} else if (rand < 0.66) {
self.type = 'weapon';
} else {
self.type = 'quadBullets';
}
// Set color based on type
if (self.type === 'health') {
powerUpGraphics.tint = 0x2ecc71; // Green for health
} else if (self.type === 'weapon') {
powerUpGraphics.tint = 0xf1c40f; // Yellow for weapon
} else {
powerUpGraphics.tint = 0xe74c3c; // Red for quad bullets
}
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var SPAWN_ENEMY_INTERVAL = 60; // Spawn enemy every 60 ticks initially
var SPAWN_POWERUP_INTERVAL = 600; // Spawn power-up every 600 ticks
var MIN_SPAWN_INTERVAL = 20; // Minimum spawn rate as difficulty increases
// Game state variables
var player;
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerUps = [];
var missiles = [];
var explosions = [];
var firePools = [];
var gameLevel = 1;
var spawnCounter = 0;
var powerUpCounter = 0;
var isGameActive = true;
var score = 0;
var lastMissileTime = 0;
var missileReadyText = new Text2('MISSILE READY', {
size: 40,
fill: 0xFF0000
});
// Initialize score display
var scoreTxt = new Text2('SCORE: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 30;
// Initialize level display
var levelTxt = new Text2('LEVEL: 1', {
size: 60,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
levelTxt.y = 100;
// Create player ship
function initializeGame() {
player = new PlayerShip();
player.x = GAME_WIDTH / 2;
player.y = GAME_HEIGHT - 200;
game.addChild(player);
// Reset arrays
playerBullets = [];
enemies = [];
enemyBullets = [];
powerUps = [];
missiles = [];
explosions = [];
firePools = [];
// Reset game state
gameLevel = 1;
spawnCounter = 0;
powerUpCounter = 0;
isGameActive = true;
lastMissileTime = 0;
// Initialize missile ready text
missileReadyText.anchor.set(0.5, 0.5);
missileReadyText.alpha = 1;
missileReadyText.visible = true;
// Set initial missile time to be ready at game start
lastMissileTime = LK.ticks - 600;
// Position text below the player ship
game.addChild(missileReadyText);
// Flash the text immediately
var _flashMissileText = function flashMissileText() {
tween(missileReadyText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
tween(missileReadyText, {
alpha: 1
}, {
duration: 500,
onFinish: _flashMissileText
});
}
});
};
_flashMissileText();
// Reset score and update display
LK.setScore(0);
score = 0;
updateScoreDisplay();
updateLevelDisplay();
// Play background music
LK.playMusic('gameMusic');
}
// Initialize the game
initializeGame();
// Update score display
function updateScoreDisplay() {
scoreTxt.setText('SCORE: ' + score);
}
// Update level display
function updateLevelDisplay() {
levelTxt.setText('LEVEL: ' + gameLevel);
}
// Player shooting
function playerShoot() {
// Check fire rate cooldown
if (LK.ticks - player.lastShot < player.fireRate) {
return;
}
// Always fire dual cannons (left and right)
var bulletLeft = new PlayerBullet();
bulletLeft.x = player.x - 20;
bulletLeft.y = player.y - 40;
bulletLeft.direction = 'up';
game.addChild(bulletLeft);
playerBullets.push(bulletLeft);
var bulletRight = new PlayerBullet();
bulletRight.x = player.x + 20;
bulletRight.y = player.y - 40;
bulletRight.direction = 'up';
game.addChild(bulletRight);
playerBullets.push(bulletRight);
// Triple bullets if weapon power-up is active
if (player.powerUpActive) {
var bulletCenter = new PlayerBullet();
bulletCenter.x = player.x;
bulletCenter.y = player.y - 50;
bulletCenter.direction = 'up';
game.addChild(bulletCenter);
playerBullets.push(bulletCenter);
}
// Quad-directional bullets if quad bullets power-up is active
if (player.quadBulletsActive) {
// Up bullet already covered
// Down bullet
var bulletDown = new PlayerBullet();
bulletDown.x = player.x;
bulletDown.y = player.y + 40;
bulletDown.direction = 'down';
bulletDown.isPiercing = true;
bulletDown.isBouncing = true;
game.addChild(bulletDown);
playerBullets.push(bulletDown);
// Left bullet
var bulletLeft = new PlayerBullet();
bulletLeft.x = player.x - 40;
bulletLeft.y = player.y;
bulletLeft.direction = 'left';
bulletLeft.isPiercing = true;
bulletLeft.isBouncing = true;
game.addChild(bulletLeft);
playerBullets.push(bulletLeft);
// Right bullet
var bulletRight = new PlayerBullet();
bulletRight.x = player.x + 40;
bulletRight.y = player.y;
bulletRight.direction = 'right';
bulletRight.isPiercing = true;
bulletRight.isBouncing = true;
game.addChild(bulletRight);
playerBullets.push(bulletRight);
}
// Play sound and reset shot timer
LK.getSound('playerShoot').play();
player.lastShot = LK.ticks;
}
// Enemy shooting
function enemyShoot(enemy) {
if (LK.ticks - enemy.lastShot < enemy.fireRate) {
return;
}
var bullet = new EnemyBullet();
bullet.x = enemy.x;
bullet.y = enemy.y + 40;
game.addChild(bullet);
enemyBullets.push(bullet);
LK.getSound('enemyShoot').play();
enemy.lastShot = LK.ticks;
}
// Spawn new enemy
function spawnEnemy() {
var enemy = new Enemy();
// Distribute enemies better across the screen width
// Check if we have other enemies and avoid spawning too close to them
if (enemies.length > 0) {
// Find a position away from other enemies
var attempts = 0;
var potentialX;
var validPosition = false;
while (!validPosition && attempts < 10) {
potentialX = Math.random() * (GAME_WIDTH - 100) + 50;
validPosition = true;
// Check distance from existing enemies
for (var i = 0; i < enemies.length; i++) {
if (Math.abs(enemies[i].x - potentialX) < 150) {
validPosition = false;
break;
}
}
attempts++;
}
enemy.x = validPosition ? potentialX : Math.random() * (GAME_WIDTH - 100) + 50;
} else {
enemy.x = Math.random() * (GAME_WIDTH - 100) + 50;
}
enemy.y = -50;
// Increase difficulty with levels
if (gameLevel > 1) {
enemy.speed = Math.min(2 + gameLevel * 0.5, 6);
enemy.health = Math.min(1 + Math.floor(gameLevel / 3), 3);
}
if (gameLevel > 5) {
enemy.fireRate = Math.max(120 - gameLevel * 5, 60);
}
game.addChild(enemy);
enemies.push(enemy);
}
// Spawn power-up
function spawnPowerUp() {
var powerUp = new PowerUp();
powerUp.x = Math.random() * (GAME_WIDTH - 100) + 50;
powerUp.y = -50;
game.addChild(powerUp);
powerUps.push(powerUp);
}
// Check level progress
function checkLevelProgress() {
// Advance level based on score
var shouldAdvanceLevel = Math.floor(score / 1000) + 1;
if (shouldAdvanceLevel > gameLevel) {
gameLevel = shouldAdvanceLevel;
updateLevelDisplay();
// Flash screen to indicate level up
LK.effects.flashScreen(0x3498db, 500);
}
}
// Handle collisions
function handleCollisions() {
// Check player bullets hitting enemies
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
var bulletHit = false;
// First check for player bullet hitting enemy bullets
// Skip enemy bullet collision during quad bullets powerup to prevent lag
if (!player.quadBulletsActive) {
for (var j = enemyBullets.length - 1; j >= 0; j--) {
if (bullet.intersects(enemyBullets[j])) {
// Create explosion animation for the enemy bullet
var explosion = new Explosion();
explosion.x = enemyBullets[j].x;
explosion.y = enemyBullets[j].y;
explosion.scale.x = 0.5; // Smaller explosion for bullets
explosion.scale.y = 0.5;
game.addChild(explosion);
explosions.push(explosion);
// Remove enemy bullet
enemyBullets[j].destroy();
enemyBullets.splice(j, 1);
// Increment hit counter for player bullet
bullet.enemyBulletHits++;
// Check if player bullet has reached its hit limit
if (bullet.enemyBulletHits >= bullet.maxEnemyBulletHits) {
// If hit limit reached, destroy the player bullet with explosion
bullet.explode();
bullet.destroy();
playerBullets.splice(i, 1);
// Create explosion animation at enemy position
var explosion = new Explosion();
explosion.x = enemy.x;
explosion.y = enemy.y;
game.addChild(explosion);
explosions.push(explosion);
// Remove enemy
bulletHit = true;
break;
}
}
}
}
// If player bullet was destroyed by enemy bullets, skip enemy collision check
if (bulletHit) {
continue;
}
// Now check for player bullet hitting enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Enemy takes damage
if (enemy.takeDamage(bullet.power)) {
// Enemy destroyed
score += enemy.scoreValue;
LK.setScore(score);
updateScoreDisplay();
LK.getSound('explosion').play();
LK.effects.flashObject(enemy, 0xff0000, 300);
// Create explosion animation at enemy position
var explosion = new Explosion();
explosion.x = enemy.x;
explosion.y = enemy.y;
game.addChild(explosion);
explosions.push(explosion);
// Remove enemy
enemy.destroy();
enemies.splice(j, 1);
}
// Only remove non-piercing bullets on hit
if (!bullet.isPiercing) {
bullet.explode(); // Add explosion effect when bullet is destroyed
bullet.destroy();
playerBullets.splice(i, 1);
bulletHit = true;
break;
}
}
}
// If a non-piercing bullet hit something, skip to the next bullet
if (bulletHit) {
continue;
}
}
// Check enemy bullets hitting player
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
if (bullet.intersects(player)) {
// Player hit
bullet.destroy();
enemyBullets.splice(i, 1);
if (player.shieldActive) {
// Shield absorbs the hit
player.shieldActive = false;
var shield = player.getChildAt(1);
shield.alpha = 0;
} else {
// Reduce player health
player.health--;
player.healthBar.updateHealth(player.health);
// Flash player to indicate damage
LK.effects.flashObject(player, 0xff0000, 300);
// Check if player health reaches 1 (final life)
if (player.health === 1 && !player.finalLifeWarningActive) {
player.finalLifeWarningActive = true;
// Hide health bar completely
player.healthBar.visible = false;
// Show and animate "Final Life" text
var finalLifeText = player.getChildAt(2); // Get the final life text
finalLifeText.alpha = 1;
// Create flashing effect for the text
var _flashTween2 = function flashTween() {
tween(finalLifeText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
tween(finalLifeText, {
alpha: 1
}, {
duration: 500,
onFinish: _flashTween2
});
}
});
};
_flashTween2(); // Start the flashing animation
}
// Check if player is out of health
if (player.health <= 0) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
isGameActive = false;
LK.showGameOver();
break;
}
}
}
}
// Check enemies colliding with player
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.intersects(player)) {
if (player.shieldActive) {
// Shield absorbs the hit
enemy.destroy();
enemies.splice(i, 1);
// Shield gets depleted
player.shieldActive = false;
var shield = player.getChildAt(1);
shield.alpha = 0;
score += enemy.scoreValue;
LK.setScore(score);
updateScoreDisplay();
} else {
// Reduce player health by 2 (collision is more dangerous)
player.health -= 2;
player.healthBar.updateHealth(player.health);
// Remove the enemy
enemy.destroy();
enemies.splice(i, 1);
// Flash player to indicate damage
LK.effects.flashObject(player, 0xff0000, 300);
// Check if player health reaches 1 (final life)
if (player.health === 1 && !player.finalLifeWarningActive) {
player.finalLifeWarningActive = true;
// Hide health bar completely
player.healthBar.visible = false;
// Show and animate "Final Life" text
var finalLifeText = player.getChildAt(2); // Get the final life text
finalLifeText.alpha = 1;
// Create flashing effect for the text
var _flashTween2 = function _flashTween() {
tween(finalLifeText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
tween(finalLifeText, {
alpha: 1
}, {
duration: 500,
onFinish: _flashTween2
});
}
});
};
_flashTween2(); // Start the flashing animation
}
// Check if player is out of health
if (player.health <= 0) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
isGameActive = false;
LK.showGameOver();
break;
}
}
}
}
// Check player collecting power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
if (powerUp.intersects(player)) {
LK.getSound('powerUp').play();
// Apply power-up effect
if (powerUp.type === 'health') {
// Health regeneration logic
if (player.health < player.healthBar.maxHealth) {
// Add 1 health point
player.health = Math.min(player.health + 1, player.healthBar.maxHealth);
// If player was at final life, restore health bar and remove warning
if (player.finalLifeWarningActive && player.health > 1) {
player.finalLifeWarningActive = false;
player.healthBar.visible = true;
// Get the final life text and hide it
var finalLifeText = player.getChildAt(2);
// Stop any existing tweens on the final life text
tween.stopTweens(finalLifeText);
finalLifeText.alpha = 0;
}
// Update health bar
player.healthBar.updateHealth(player.health);
}
} else if (powerUp.type === 'weapon') {
player.activatePowerUp();
} else if (powerUp.type === 'quadBullets') {
player.activateQuadBullets();
}
// Remove power-up
powerUp.destroy();
powerUps.splice(i, 1);
}
}
}
// Clean up off-screen objects
function cleanupOffscreenObjects() {
// Clean up missiles that went off screen
for (var i = missiles.length - 1; i >= 0; i--) {
var missile = missiles[i];
if (missile.y < -50 || missile.y > GAME_HEIGHT + 50 || missile.x < -50 || missile.x > GAME_WIDTH + 50) {
missile.explode();
missile.destroy();
missiles.splice(i, 1);
}
}
// Clean up bullets that went off screen
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
// Check if bullet is off screen based on direction (unless it's bouncing)
if (!bullet.isBouncing) {
if (bullet.direction === 'up' && bullet.y < -50 || bullet.direction === 'down' && bullet.y > GAME_HEIGHT + 50 || bullet.direction === 'left' && bullet.x < -50 || bullet.direction === 'right' && bullet.x > GAME_WIDTH + 50) {
bullet.destroy();
playerBullets.splice(i, 1);
}
} else if (bullet.bounceCount >= bullet.maxBounces) {
// Add explosion for bouncing bullets that reached max bounce count
bullet.explode();
bullet.destroy();
playerBullets.splice(i, 1);
}
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
if (enemyBullets[i].y > GAME_HEIGHT + 50) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
// Clean up enemies that went off screen
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].y > GAME_HEIGHT + 100) {
enemies[i].destroy();
enemies.splice(i, 1);
}
}
// Clean up power-ups that went off screen
for (var i = powerUps.length - 1; i >= 0; i--) {
if (powerUps[i].y > GAME_HEIGHT + 50) {
powerUps[i].destroy();
powerUps.splice(i, 1);
}
}
}
// Player movement
var isDragging = false;
game.down = function (x, y) {
// Fire missile on left click if cooldown is ready
if (LK.ticks - lastMissileTime >= 600) {
// 10 seconds at 60fps
// Find closest enemy as target first
var closestEnemy = null;
var closestDistance = Number.MAX_VALUE;
var potentialTargets = [];
// Find up to 6 potential targets
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
// Add to potential targets list (sort by proximity later)
potentialTargets.push({
enemy: enemy,
distance: distance
});
}
// Sort targets by distance
potentialTargets.sort(function (a, b) {
return a.distance - b.distance;
});
// Launch three missiles from the same vertical position but different horizontal positions
// Center missile - positioned exactly in the center of player ship
var centerMissile = new Missile();
centerMissile.x = player.x; // Center horizontally with player
centerMissile.y = player.y - 60; // Launch from front center of player ship
centerMissile.turnSpeed = 0.05; // Very slight homing for center missile
centerMissile.initialDirection = 'up'; // Make sure it exits straight up first
// Left missile - positioned to the left of center missile
var leftMissile = new Missile();
leftMissile.x = player.x - 30; // Position left of the player
leftMissile.y = player.y - 60; // Same vertical position as center missile
leftMissile.angle = -0.3; // Slight left angle for initial trajectory
leftMissile.turnSpeed = 0.07; // Reduced homing for more separation
leftMissile.initialDirection = 'left-up'; // Make sure it exits left-up first
// Right missile - positioned to the right of center missile
var rightMissile = new Missile();
rightMissile.x = player.x + 30; // Position right of the player
rightMissile.y = player.y - 60; // Same vertical position as center missile
rightMissile.angle = 0.3; // Slight right angle for initial trajectory
rightMissile.turnSpeed = 0.07; // Reduced homing for more separation
rightMissile.initialDirection = 'right-up'; // Make sure it exits right-up first
// If we found a target, set it for missiles with different offsets to avoid pairing
if (closestEnemy) {
// Set slightly different targets for each missile to avoid pairing
centerMissile.targetX = closestEnemy.x;
centerMissile.targetY = closestEnemy.y;
// For side missiles, either use different enemies or offset from main target
if (potentialTargets.length > 1) {
leftMissile.targetX = potentialTargets[Math.min(1, potentialTargets.length - 1)].enemy.x - 50;
leftMissile.targetY = potentialTargets[Math.min(1, potentialTargets.length - 1)].enemy.y;
} else {
// If not enough targets, offset significantly from main target
leftMissile.targetX = closestEnemy.x - 150;
leftMissile.targetY = closestEnemy.y;
}
if (potentialTargets.length > 2) {
rightMissile.targetX = potentialTargets[Math.min(2, potentialTargets.length - 1)].enemy.x + 50;
rightMissile.targetY = potentialTargets[Math.min(2, potentialTargets.length - 1)].enemy.y;
} else {
// If not enough targets, offset significantly from main target
rightMissile.targetX = closestEnemy.x + 150;
rightMissile.targetY = closestEnemy.y;
}
}
// Add first wave of missiles to game
game.addChild(centerMissile);
missiles.push(centerMissile);
game.addChild(leftMissile);
missiles.push(leftMissile);
game.addChild(rightMissile);
missiles.push(rightMissile);
// Create a second wave of missiles with slightly different launch positions
// Second center missile
var centerMissile2 = new Missile();
centerMissile2.x = player.x;
centerMissile2.y = player.y - 30; // Slightly higher than first wave
centerMissile2.turnSpeed = 0.06;
centerMissile2.initialDirection = 'up';
// Second left missile
var leftMissile2 = new Missile();
leftMissile2.x = player.x - 40; // Different horizontal offset
leftMissile2.y = player.y - 30;
leftMissile2.angle = -0.4; // Different initial angle
leftMissile2.turnSpeed = 0.08;
leftMissile2.initialDirection = 'left-up';
// Second right missile
var rightMissile2 = new Missile();
rightMissile2.x = player.x + 40; // Different horizontal offset
rightMissile2.y = player.y - 30;
rightMissile2.angle = 0.4; // Different initial angle
rightMissile2.turnSpeed = 0.08;
rightMissile2.initialDirection = 'right-up';
// Set targets for second wave
if (closestEnemy) {
// Set slightly different targets for second wave missiles
if (potentialTargets.length > 3) {
centerMissile2.targetX = potentialTargets[3].enemy.x;
centerMissile2.targetY = potentialTargets[3].enemy.y;
} else {
centerMissile2.targetX = closestEnemy.x + 50;
centerMissile2.targetY = closestEnemy.y + 50;
}
if (potentialTargets.length > 4) {
leftMissile2.targetX = potentialTargets[4].enemy.x - 70;
leftMissile2.targetY = potentialTargets[4].enemy.y;
} else {
leftMissile2.targetX = closestEnemy.x - 200;
leftMissile2.targetY = closestEnemy.y + 50;
}
if (potentialTargets.length > 5) {
rightMissile2.targetX = potentialTargets[5].enemy.x + 70;
rightMissile2.targetY = potentialTargets[5].enemy.y;
} else {
rightMissile2.targetX = closestEnemy.x + 200;
rightMissile2.targetY = closestEnemy.y + 50;
}
}
// Add second wave of missiles to game
game.addChild(centerMissile2);
missiles.push(centerMissile2);
game.addChild(leftMissile2);
missiles.push(leftMissile2);
game.addChild(rightMissile2);
missiles.push(rightMissile2);
// Play missile launch sound
LK.getSound('missileLaunch').play();
// Reset cooldown
lastMissileTime = LK.ticks;
// Hide missile ready text and stop any active tweens
tween.stop(missileReadyText);
missileReadyText.visible = false;
}
// Handle regular player movement
var targetX = x;
var targetY = y;
// Clamp target position to game bounds
if (targetX < 50) {
targetX = 50;
}
if (targetX > GAME_WIDTH - 50) {
targetX = GAME_WIDTH - 50;
}
if (targetY < 50) {
targetY = 50;
}
if (targetY > GAME_HEIGHT - 50) {
targetY = GAME_HEIGHT - 50;
}
player.targetX = targetX;
player.targetY = targetY;
player.isMoving = true;
// Also shoot when player taps
playerShoot();
};
game.move = function (x, y) {
if (isGameActive) {
// Clamp target position to game bounds
var targetX = x;
var targetY = y;
if (targetX < 50) {
targetX = 50;
}
if (targetX > GAME_WIDTH - 50) {
targetX = GAME_WIDTH - 50;
}
if (targetY < 50) {
targetY = 50;
}
if (targetY > GAME_HEIGHT - 50) {
targetY = GAME_HEIGHT - 50;
}
// Update target position
player.targetX = targetX;
player.targetY = targetY;
// For better tracking during fast mouse movements, directly update position
player.x = player.targetX;
player.y = player.targetY;
player.isMoving = true; //{2K}{2L}{2M}{2N}
}
};
game.up = function () {
// Keep the ship moving toward last target point
};
// Main game update loop
game.update = function () {
if (!isGameActive) {
return;
}
// Update missile ready text
if (LK.ticks - lastMissileTime >= 600) {
// 10 seconds cooldown
if (!missileReadyText.visible) {
missileReadyText.visible = true;
missileReadyText.x = player.x;
missileReadyText.y = player.y + 110;
missileReadyText.alpha = 1;
// Flash the text when ready
var _flashMissileText2 = function flashMissileText() {
tween(missileReadyText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
tween(missileReadyText, {
alpha: 1
}, {
duration: 500,
onFinish: _flashMissileText2
});
}
});
};
_flashMissileText2();
}
}
// Spawn enemies
spawnCounter++;
var currentSpawnRate = Math.max(SPAWN_ENEMY_INTERVAL - gameLevel * 5, MIN_SPAWN_INTERVAL);
if (spawnCounter >= currentSpawnRate) {
spawnEnemy();
spawnCounter = 0;
}
// Spawn power-ups
powerUpCounter++;
if (powerUpCounter >= SPAWN_POWERUP_INTERVAL) {
spawnPowerUp();
powerUpCounter = 0;
}
// Make player shoot automatically
if (player.isMoving) {
playerShoot();
}
// Update enemy shooting
for (var i = 0; i < enemies.length; i++) {
enemyShoot(enemies[i]);
}
// Update missiles
for (var i = missiles.length - 1; i >= 0; i--) {
var missile = missiles[i];
// If missile doesn't have a target or target is destroyed, find new target
if ((missile.targetX === null || missile.targetY === null) && enemies.length > 0) {
// Find closest enemy as new target
var closestEnemy = null;
var closestDistance = Number.MAX_VALUE;
// Check all enemies for potential targets
for (var k = 0; k < enemies.length; k++) {
var dx = enemies[k].x - missile.x;
var dy = enemies[k].y - missile.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemies[k];
}
}
// If we found a target, set it with slight offset based on missile type
if (closestEnemy) {
// Add slightly different offsets to each missile's target to maintain separation
// We can determine missile "type" by its position relative to player
var offsetX = 0;
var offsetY = 0; // Adding vertical offset for better separation
// Using angle to identify missile type
if (missile.angle < 0) {
// Left missile
offsetX = -100; // Increased horizontal offset
offsetY = -20; // Slight vertical offset
} else if (missile.angle > 0) {
// Right missile
offsetX = 100; // Increased horizontal offset
offsetY = -20; // Slight vertical offset
} else {
// Center missile
offsetY = -50; // Different vertical offset for center missile
}
missile.targetX = closestEnemy.x + offsetX;
missile.targetY = closestEnemy.y + offsetY;
}
}
// Check if missile hits an enemy
var missileHit = false;
for (var j = enemies.length - 1; j >= 0; j--) {
if (missile.intersects(enemies[j])) {
missile.explode();
missile.destroy();
missiles.splice(i, 1);
missileHit = true;
break;
}
}
// Check if missile is off screen
if (!missileHit && (missile.y < -50 || missile.y > GAME_HEIGHT + 50 || missile.x < -50 || missile.x > GAME_WIDTH + 50)) {
missile.explode();
missile.destroy();
missiles.splice(i, 1);
}
}
// Update explosions
for (var i = explosions.length - 1; i >= 0; i--) {
if (explosions[i].update()) {
explosions.splice(i, 1);
}
}
// Update fire pools
for (var i = firePools.length - 1; i >= 0; i--) {
if (firePools[i].update()) {
firePools.splice(i, 1);
}
}
// Check collisions
handleCollisions();
// Clean up off-screen objects
cleanupOffscreenObjects();
// Check level progression
checkLevelProgress();
// Win condition check
if (score >= 10000) {
LK.showYouWin();
isGameActive = false;
}
};
An enemy spaceship in a topdown shooter. In-Game asset. 2d. High contrast. No shadows
A player's spaceship in a topdown shooter (mainly blue in colour). In-Game asset. 2d. High contrast. No shadows
A powerup in a top-down shooter (icon of powerup no text). In-Game asset. 2d. High contrast. No shadows
Health Powerup in 2d space shooter (top down). In-Game asset. 2d. High contrast. No shadows
An explosion which contains debris of a spaceship. In-Game asset. 2d. High contrast. No shadows
pool of fire with top down view. In-Game asset. 2d. High contrast. No shadows
Make a small circle that looks like a laser (red colour). In-Game asset. 2d. High contrast. No shadows
Make a very small circle that looks like a laser (blue colour). In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
A missile. In-Game asset. 2d. High contrast. No shadows