/**** * 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 }); // Use default hitbox // 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; self.lastY = 0; // Track last Y position to detect reaching bottom self.firingMode = false; // Track if enemy is in enhanced firing mode self.firingModeStartTime = 0; // When enhanced firing mode started // Dodging properties self.canDodge = true; self.lastDodgeTime = 0; self.isDodging = false; self.dodgeCooldown = 300; // 5 seconds at 60fps self.dodgeDuration = 30; // Half a second dodge movement self.dodgeCounter = 0; self.dodgeDirection = 0; // Will be set when dodging self.dodgeSpeed = 10; // Fast dodge movement // Initial target position (will be set during movement) self.targetX = null; self.update = function () { // Store last Y position for edge detection if (self.lastY === undefined) { self.lastY = self.y; } // Basic downward movement self.y += self.speed; // Check if enemy just reached bottom edge of game area var bottomThreshold = GAME_HEIGHT - 200; if (self.lastY < bottomThreshold && self.y >= bottomThreshold && !self.firingMode) { // Enemy reached bottom edge - activate enhanced firing mode self.firingMode = true; self.firingModeStartTime = LK.ticks; // Store original fire rate before enhancement self.originalFireRate = self.fireRate; // Increase fire rate and set player as target self.fireRate = 30; // Much faster fire rate // Flash red to indicate enhanced firing mode LK.effects.flashObject(self, 0xff0000, 500); // Start aiming at player self.targetPlayer = true; } // Handle enhanced firing mode behavior if (self.firingMode) { // Track number of shots in enhanced mode if (self.enhancedShotsCount === undefined) { self.enhancedShotsCount = 0; } // Self-destruct after 10 shots instead of time-based if (self.enhancedShotsCount >= 10) { // Create explosion effect var explosion = new Explosion(); explosion.x = self.x; explosion.y = self.y; game.addChild(explosion); explosions.push(explosion); // Play explosion sound LK.getSound('explosion').play(); // Add score for self-destruction score += self.scoreValue; LK.setScore(score); updateScoreDisplay(); // Remove the enemy self.destroy(); return true; // Signal to remove from enemies array } // Aim at player during enhanced firing mode if (player && self.targetPlayer) { // Calculate direction to player var diffX = player.x - self.x; var diffY = player.y - self.y; // Move toward player with increased precision self.x += diffX * 0.02; // Apply a pulsating movement for visual effect self.y += Math.sin(LK.ticks * 0.1) * 1.5; return; } } // Apply regular movement pattern when not in enhanced firing mode 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++; } // Check for nearby bullets and dodge if possible if (self.canDodge && !self.isDodging && LK.ticks - self.lastDodgeTime >= self.dodgeCooldown) { // Look for nearby bullets for (var i = 0; i < playerBullets.length; i++) { var bullet = playerBullets[i]; // Skip bullets not moving toward the enemy (horizontal bullets, or downward bullets) if (bullet.direction !== 'up') continue; // Calculate distance between bullet and enemy var dx = bullet.x - self.x; var dy = bullet.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If bullet is close but not too close, and is above the enemy (coming toward it) if (distance < 150 && Math.abs(dx) < 100 && bullet.y < self.y && bullet.y > self.y - 300) { // Start dodging! self.isDodging = true; self.dodgeCounter = 0; // Determine dodge direction (away from bullet) self.dodgeDirection = dx > 0 ? -1 : 1; // Move left if bullet is on right, right if bullet is on left self.lastDodgeTime = LK.ticks; // Flash enemy green to indicate dodge LK.effects.flashObject(self, 0x00ff00, 300); break; } } } // Handle active dodging movement if (self.isDodging) { self.dodgeCounter++; // Move rapidly in dodge direction self.x += self.dodgeDirection * self.dodgeSpeed; // Constrain to screen bounds if (self.x < 50) { self.x = 50; self.dodgeDirection = 1; // Reverse direction if hitting edge } else if (self.x > GAME_WIDTH - 50) { self.x = GAME_WIDTH - 50; self.dodgeDirection = -1; // Reverse direction if hitting edge } // End dodge after duration if (self.dodgeCounter >= self.dodgeDuration) { self.isDodging = false; } } // Update lastY for next frame self.lastY = self.y; }; // Take damage and check if destroyed self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { return true; // Enemy is destroyed } return false; }; // Use default intersection method 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 () { if (self.isAimed && self.speedX !== undefined && self.speedY !== undefined) { // Move along calculated trajectory for aimed bullets self.x += self.speedX; self.y += self.speedY; } else { // Default downward movement self.y += self.speed; } }; // Use default intersection method 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); // Play explosion sound when enemy is destroyed by fire pool LK.getSound('firePoolDestroy').play(); 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; // Enemy bullet destruction functionality removed 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(); }; // Use default intersection method 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 }); // Use default hitbox // 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; } }; // Use default intersection method // Shield activation self.activateShield = function () { self.shieldActive = true; shieldGraphics.alpha = 0.5; // Play shield activation sound effect LK.getSound('shieldActivation').play(); // 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 when game starts LK.playMusic('gameMusic', { fade: { start: 0, end: 1, duration: 1000 } }); } // 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; // Check if enemy is in enhanced firing mode if (enemy.firingMode) { // Enhanced bullets are faster and aim at player bullet.speed = 12; // Increased speed // Calculate direction to player for aiming if (player) { // Get angle to player var dx = player.x - enemy.x; var dy = player.y - enemy.y; var angle = Math.atan2(dy, dx); // Set bullet velocity components based on angle bullet.speedX = Math.cos(angle) * bullet.speed; bullet.speedY = Math.sin(angle) * bullet.speed; bullet.isAimed = true; } // Count shots in enhanced mode if (enemy.enhancedShotsCount === undefined) { enemy.enhancedShotsCount = 0; } enemy.enhancedShotsCount++; } 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); // Play level up sound LK.getSound('levelUp').play(); } } // 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; // Enemy bullet destruction completely removed // Skip all bullet collision detection logic // 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]; // Get center points of both objects for more precise collision detection var bulletCenterX = bullet.x; var bulletCenterY = bullet.y; var playerCenterX = player.x; var playerCenterY = player.y; // Calculate distance between centers var dx = bulletCenterX - playerCenterX; var dy = bulletCenterY - playerCenterY; var distance = Math.sqrt(dx * dx + dy * dy); // Get the combined radius (half the width of both objects for a more precise collision) var bulletRadius = bullet.width / 3.5; // Much smaller hitbox for accurate collision var playerRadius = player.width / 3.5; // Much smaller hitbox for accurate collision var combinedRadius = bulletRadius + playerRadius; // Only register hit if the bullet is actually touching the player (more precise collision) if (distance < combinedRadius) { // Player hit bullet.destroy(); enemyBullets.splice(i, 1); if (player.shieldActive) { // Shield absorbs the hit // Play explosion sound for shield hit LK.getSound('explosion').play(); player.shieldActive = false; var shield = player.getChildAt(1); shield.alpha = 0; } else { // Play sound when player gets hit by enemy bullet LK.getSound('playerHit').play(); // 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; // Stop music with fade out LK.playMusic('gameMusic', { fade: { start: 1, end: 0, duration: 800 } }); // Play game over sound effect LK.getSound('gameOver').play(); 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 // Play collision sound when enemy collides with shield LK.getSound('shieldCollision').play(); 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); // Play collision sound when player collides with enemy LK.getSound('playerCollision').play(); // 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.stop(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(); // Play explosion sound when bullet is removed at max bounce count LK.getSound('bulletExplosion').play(); 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; } //[8F] // 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 enemies and their shooting for (var i = enemies.length - 1; i >= 0; i--) { // Check if the enemy should be removed (returned true from update) if (enemies[i].update && enemies[i].update() === true) { enemies.splice(i, 1); continue; } 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) { // Play win sound effect LK.getSound('winSound').play(); // Stop music with fade out LK.playMusic('gameMusic', { fade: { start: 1, end: 0, duration: 800 } }); 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
});
// Use default hitbox
// 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;
self.lastY = 0; // Track last Y position to detect reaching bottom
self.firingMode = false; // Track if enemy is in enhanced firing mode
self.firingModeStartTime = 0; // When enhanced firing mode started
// Dodging properties
self.canDodge = true;
self.lastDodgeTime = 0;
self.isDodging = false;
self.dodgeCooldown = 300; // 5 seconds at 60fps
self.dodgeDuration = 30; // Half a second dodge movement
self.dodgeCounter = 0;
self.dodgeDirection = 0; // Will be set when dodging
self.dodgeSpeed = 10; // Fast dodge movement
// Initial target position (will be set during movement)
self.targetX = null;
self.update = function () {
// Store last Y position for edge detection
if (self.lastY === undefined) {
self.lastY = self.y;
}
// Basic downward movement
self.y += self.speed;
// Check if enemy just reached bottom edge of game area
var bottomThreshold = GAME_HEIGHT - 200;
if (self.lastY < bottomThreshold && self.y >= bottomThreshold && !self.firingMode) {
// Enemy reached bottom edge - activate enhanced firing mode
self.firingMode = true;
self.firingModeStartTime = LK.ticks;
// Store original fire rate before enhancement
self.originalFireRate = self.fireRate;
// Increase fire rate and set player as target
self.fireRate = 30; // Much faster fire rate
// Flash red to indicate enhanced firing mode
LK.effects.flashObject(self, 0xff0000, 500);
// Start aiming at player
self.targetPlayer = true;
}
// Handle enhanced firing mode behavior
if (self.firingMode) {
// Track number of shots in enhanced mode
if (self.enhancedShotsCount === undefined) {
self.enhancedShotsCount = 0;
}
// Self-destruct after 10 shots instead of time-based
if (self.enhancedShotsCount >= 10) {
// Create explosion effect
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
explosions.push(explosion);
// Play explosion sound
LK.getSound('explosion').play();
// Add score for self-destruction
score += self.scoreValue;
LK.setScore(score);
updateScoreDisplay();
// Remove the enemy
self.destroy();
return true; // Signal to remove from enemies array
}
// Aim at player during enhanced firing mode
if (player && self.targetPlayer) {
// Calculate direction to player
var diffX = player.x - self.x;
var diffY = player.y - self.y;
// Move toward player with increased precision
self.x += diffX * 0.02;
// Apply a pulsating movement for visual effect
self.y += Math.sin(LK.ticks * 0.1) * 1.5;
return;
}
}
// Apply regular movement pattern when not in enhanced firing mode
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++;
}
// Check for nearby bullets and dodge if possible
if (self.canDodge && !self.isDodging && LK.ticks - self.lastDodgeTime >= self.dodgeCooldown) {
// Look for nearby bullets
for (var i = 0; i < playerBullets.length; i++) {
var bullet = playerBullets[i];
// Skip bullets not moving toward the enemy (horizontal bullets, or downward bullets)
if (bullet.direction !== 'up') continue;
// Calculate distance between bullet and enemy
var dx = bullet.x - self.x;
var dy = bullet.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If bullet is close but not too close, and is above the enemy (coming toward it)
if (distance < 150 && Math.abs(dx) < 100 && bullet.y < self.y && bullet.y > self.y - 300) {
// Start dodging!
self.isDodging = true;
self.dodgeCounter = 0;
// Determine dodge direction (away from bullet)
self.dodgeDirection = dx > 0 ? -1 : 1; // Move left if bullet is on right, right if bullet is on left
self.lastDodgeTime = LK.ticks;
// Flash enemy green to indicate dodge
LK.effects.flashObject(self, 0x00ff00, 300);
break;
}
}
}
// Handle active dodging movement
if (self.isDodging) {
self.dodgeCounter++;
// Move rapidly in dodge direction
self.x += self.dodgeDirection * self.dodgeSpeed;
// Constrain to screen bounds
if (self.x < 50) {
self.x = 50;
self.dodgeDirection = 1; // Reverse direction if hitting edge
} else if (self.x > GAME_WIDTH - 50) {
self.x = GAME_WIDTH - 50;
self.dodgeDirection = -1; // Reverse direction if hitting edge
}
// End dodge after duration
if (self.dodgeCounter >= self.dodgeDuration) {
self.isDodging = false;
}
}
// Update lastY for next frame
self.lastY = self.y;
};
// Take damage and check if destroyed
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
return true; // Enemy is destroyed
}
return false;
};
// Use default intersection method
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 () {
if (self.isAimed && self.speedX !== undefined && self.speedY !== undefined) {
// Move along calculated trajectory for aimed bullets
self.x += self.speedX;
self.y += self.speedY;
} else {
// Default downward movement
self.y += self.speed;
}
};
// Use default intersection method
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);
// Play explosion sound when enemy is destroyed by fire pool
LK.getSound('firePoolDestroy').play();
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;
// Enemy bullet destruction functionality removed
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();
};
// Use default intersection method
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
});
// Use default hitbox
// 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;
}
};
// Use default intersection method
// Shield activation
self.activateShield = function () {
self.shieldActive = true;
shieldGraphics.alpha = 0.5;
// Play shield activation sound effect
LK.getSound('shieldActivation').play();
// 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 when game starts
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
// 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;
// Check if enemy is in enhanced firing mode
if (enemy.firingMode) {
// Enhanced bullets are faster and aim at player
bullet.speed = 12; // Increased speed
// Calculate direction to player for aiming
if (player) {
// Get angle to player
var dx = player.x - enemy.x;
var dy = player.y - enemy.y;
var angle = Math.atan2(dy, dx);
// Set bullet velocity components based on angle
bullet.speedX = Math.cos(angle) * bullet.speed;
bullet.speedY = Math.sin(angle) * bullet.speed;
bullet.isAimed = true;
}
// Count shots in enhanced mode
if (enemy.enhancedShotsCount === undefined) {
enemy.enhancedShotsCount = 0;
}
enemy.enhancedShotsCount++;
}
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);
// Play level up sound
LK.getSound('levelUp').play();
}
}
// 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;
// Enemy bullet destruction completely removed
// Skip all bullet collision detection logic
// 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];
// Get center points of both objects for more precise collision detection
var bulletCenterX = bullet.x;
var bulletCenterY = bullet.y;
var playerCenterX = player.x;
var playerCenterY = player.y;
// Calculate distance between centers
var dx = bulletCenterX - playerCenterX;
var dy = bulletCenterY - playerCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Get the combined radius (half the width of both objects for a more precise collision)
var bulletRadius = bullet.width / 3.5; // Much smaller hitbox for accurate collision
var playerRadius = player.width / 3.5; // Much smaller hitbox for accurate collision
var combinedRadius = bulletRadius + playerRadius;
// Only register hit if the bullet is actually touching the player (more precise collision)
if (distance < combinedRadius) {
// Player hit
bullet.destroy();
enemyBullets.splice(i, 1);
if (player.shieldActive) {
// Shield absorbs the hit
// Play explosion sound for shield hit
LK.getSound('explosion').play();
player.shieldActive = false;
var shield = player.getChildAt(1);
shield.alpha = 0;
} else {
// Play sound when player gets hit by enemy bullet
LK.getSound('playerHit').play();
// 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;
// Stop music with fade out
LK.playMusic('gameMusic', {
fade: {
start: 1,
end: 0,
duration: 800
}
});
// Play game over sound effect
LK.getSound('gameOver').play();
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
// Play collision sound when enemy collides with shield
LK.getSound('shieldCollision').play();
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);
// Play collision sound when player collides with enemy
LK.getSound('playerCollision').play();
// 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.stop(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();
// Play explosion sound when bullet is removed at max bounce count
LK.getSound('bulletExplosion').play();
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;
} //[8F]
// 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 enemies and their shooting
for (var i = enemies.length - 1; i >= 0; i--) {
// Check if the enemy should be removed (returned true from update)
if (enemies[i].update && enemies[i].update() === true) {
enemies.splice(i, 1);
continue;
}
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) {
// Play win sound effect
LK.getSound('winSound').play();
// Stop music with fade out
LK.playMusic('gameMusic', {
fade: {
start: 1,
end: 0,
duration: 800
}
});
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