/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Boss = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); // Add thrusters to boss var thruster1 = self.attachAsset('bossThruster', { anchorX: 0.5, anchorY: 0.0 }); var thruster2 = self.attachAsset('bossThruster', { anchorX: 0.5, anchorY: 0.0 }); // Position thrusters at boss top corners thruster1.x = -graphics.width / 4; thruster1.y = -graphics.height / 2; thruster2.x = graphics.width / 4; thruster2.y = -graphics.height / 2; self.health = 20 + waveLevel * 5; // Scales with wave level self.maxHealth = self.health; self.speedX = 2; self.speedY = 1; self.shootCooldown = 0; self.directionChangeTimer = 0; self.invulnerable = 0; self.canBeTargeted = true; // Allow missiles to target boss self.update = function () { // Direction change logic self.directionChangeTimer--; if (self.directionChangeTimer <= 0) { self.speedX = (Math.random() - 0.5) * 4; // Random horizontal movement self.speedY = Math.random() * 2 + 0.5; // Slow downward movement self.directionChangeTimer = 60 + Math.random() * 120; // 1-3 seconds } // Movement with screen bounds checking self.x += self.speedX; self.y += self.speedY; // Keep boss on screen horizontally if (self.x < 100) { self.x = 100; self.speedX = Math.abs(self.speedX); } if (self.x > 1948) { self.x = 1948; self.speedX = -Math.abs(self.speedX); } // Keep boss in upper portion of screen if (self.y < 100) { self.y = 100; self.speedY = Math.abs(self.speedY); } if (self.y > 800) { self.y = 800; self.speedY = -Math.abs(self.speedY); } // Shooting behavior self.shootCooldown--; if (self.shootCooldown <= 0) { self.shoot(); self.shootCooldown = 90 + Math.random() * 60; // 1.5-2.5 seconds } // Thruster flickering effects thruster1.scaleX = 0.6 + Math.random() * 0.8; thruster1.scaleY = 0.6 + Math.random() * 0.8; thruster1.alpha = 0.6 + Math.random() * 0.4; thruster1.x = -graphics.width / 4 + (Math.random() - 0.5) * 8; thruster2.scaleX = 0.6 + Math.random() * 0.8; thruster2.scaleY = 0.6 + Math.random() * 0.8; thruster2.alpha = 0.6 + Math.random() * 0.4; thruster2.x = graphics.width / 4 + (Math.random() - 0.5) * 8; // Invulnerability flashing if (self.invulnerable > 0) { self.invulnerable--; graphics.alpha = self.invulnerable % 8 < 4 ? 0.3 : 1.0; } else { graphics.alpha = 1.0; } // Health-based tint var healthRatio = self.health / self.maxHealth; if (healthRatio < 0.3) { graphics.tint = 0xff4444; // Red when low health } else if (healthRatio < 0.6) { graphics.tint = 0xffaa44; // Orange when medium health } else { graphics.tint = 0xffffff; // White when healthy } }; self.shoot = function () { // Create multiple bullets in a spread pattern var bulletCount = 3 + Math.floor(waveLevel / 5); // More bullets at higher waves for (var i = 0; i < bulletCount; i++) { var bullet = new BossBullet(); bullet.x = self.x + (Math.random() - 0.5) * 40; bullet.y = self.y + graphics.height / 2; // Aim towards player with some spread var dx = player.x - bullet.x; var dy = player.y - bullet.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); angle += (Math.random() - 0.5) * 0.6; // Add spread bullet.speedX = Math.cos(angle) * 4; bullet.speedY = Math.sin(angle) * 4; bossBullets.push(bullet); game.addChild(bullet); } LK.getSound('boss_shoot').play(); }; self.takeDamage = function () { if (self.invulnerable <= 0) { self.health--; self.invulnerable = 30; LK.effects.flashObject(self, 0xff0000, 200); } }; return self; }); var BossBullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bossbullet', { anchorX: 0.5, anchorY: 0.5 }); graphics.tint = 0xff4444; // Red tint for boss bullets self.speedX = 0; self.speedY = 4; self.update = function () { self.x += self.speedX; self.y += self.speedY; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speedX = 0; self.speedY = 2; self.health = 1; self.update = function () { // Calculate direction toward player var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Normalize direction and apply stronger homing behavior if (distance > 0) { var homingStrength = 0.3; // How much enemies home toward player var targetSpeedX = dx / distance * 3; // Target speed toward player var targetSpeedY = dy / distance * 3; // Blend current speed with homing direction self.speedX = self.speedX * (1 - homingStrength) + targetSpeedX * homingStrength; self.speedY = self.speedY * (1 - homingStrength) + targetSpeedY * homingStrength; } self.x += self.speedX; self.y += self.speedY; // Apply isometric movement effect self.x += self.speedY * 0.5; }; return self; }); var EnemyBullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); graphics.tint = 0xff4444; // Red tint for enemy bullets self.speedX = 0; self.speedY = 4; self.update = function () { self.x += self.speedX; self.y += self.speedY; }; return self; }); var HomingMissile = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('missile', { anchorX: 0.5, anchorY: 0.5 }); var thruster = self.attachAsset('missileThrust', { anchorX: 0.5, anchorY: 0.0 }); thruster.x = 0; // Center horizontally on missile thruster.y = graphics.height / 2; // Position at missile bottom self.target = null; self.speed = 6; self.turnSpeed = 0.1; self.life = 300; // 5 seconds at 60fps self.update = function () { self.life--; if (self.life <= 0) { return; } // Thruster flickering effect thruster.scaleX = 0.6 + Math.random() * 0.8; thruster.scaleY = 0.6 + Math.random() * 0.8; thruster.alpha = 0.6 + Math.random() * 0.4; thruster.x = (Math.random() - 0.5) * 4; // Find target if we don't have one if (!self.target || !self.target.parent) { var availableTargets = []; // Prioritize boss if active if (bossActive && boss && boss.parent && boss.canBeTargeted) { availableTargets.push(boss); } // Also add enemies as potential targets for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var alreadyTargeted = false; // Check if this enemy is already targeted by another missile for (var m = 0; m < missiles.length; m++) { if (missiles[m] !== self && missiles[m].target === enemy) { alreadyTargeted = true; break; } } if (!alreadyTargeted) { availableTargets.push(enemy); } } // If no untargeted enemies available, allow targeting any enemy if (availableTargets.length === 0) { availableTargets = enemies.slice(); // Copy all enemies // Also add boss if no enemies available if (bossActive && boss && boss.parent && boss.canBeTargeted) { availableTargets.push(boss); } } // Select target - prioritize boss if available if (availableTargets.length > 0) { // Check if boss is in available targets and prioritize it var bossInTargets = false; for (var t = 0; t < availableTargets.length; t++) { if (availableTargets[t] === boss) { self.target = boss; bossInTargets = true; break; } } // If boss not available, select random target if (!bossInTargets) { var randomIndex = Math.floor(Math.random() * availableTargets.length); self.target = availableTargets[randomIndex]; } // Add random pattern properties for approach self.patternTime = 0; self.patternAmplitude = 50 + Math.random() * 100; // Random curve amplitude self.patternFrequency = 0.05 + Math.random() * 0.1; // Random curve frequency self.patternOffset = Math.random() * Math.PI * 2; // Random phase offset } } // Move towards target if (self.target && self.target.parent) { self.patternTime = (self.patternTime || 0) + 1; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { var targetAngle = Math.atan2(dy, dx); var currentAngle = Math.atan2(self.speedY || 0, self.speedX || -self.speed); // Add random pattern curve to the approach var patternCurve = Math.sin(self.patternTime * self.patternFrequency + self.patternOffset) * self.patternAmplitude; var perpAngle = targetAngle + Math.PI / 2; // Perpendicular to target direction var curveX = Math.cos(perpAngle) * patternCurve / distance; var curveY = Math.sin(perpAngle) * patternCurve / distance; // Apply curve offset to target direction var adjustedTargetX = self.target.x + curveX; var adjustedTargetY = self.target.y + curveY; var adjustedDx = adjustedTargetX - self.x; var adjustedDy = adjustedTargetY - self.y; var adjustedTargetAngle = Math.atan2(adjustedDy, adjustedDx); // Smooth turning towards adjusted target var angleDiff = adjustedTargetAngle - currentAngle; if (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } if (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } currentAngle += angleDiff * self.turnSpeed; self.speedX = Math.cos(currentAngle) * self.speed; self.speedY = Math.sin(currentAngle) * self.speed; graphics.rotation = currentAngle + Math.PI / 2; // Position thruster at missile bottom with correct rotation thruster.rotation = graphics.rotation; var thrusterDistance = graphics.height / 2; thruster.x = -Math.sin(graphics.rotation) * thrusterDistance; thruster.y = Math.cos(graphics.rotation) * thrusterDistance; } else { // Close to target, move directly self.speedX = dx / distance * self.speed; self.speedY = dy / distance * self.speed; } } else { // No target, move up if (!self.speedX) { self.speedX = 0; } if (!self.speedY) { self.speedY = -self.speed; } } self.x += self.speedX; self.y += self.speedY; }; return self; }); var LaserBeam = Container.expand(function () { var self = Container.call(this); // Create laser beam visual using shape var graphics = self.attachAsset('healthbar', { anchorX: 0.0, anchorY: 0.5, scaleX: 1, scaleY: 1 }); graphics.tint = 0xff0000; // Red laser self.isHorizontal = true; // Direction of laser self.thickness = 80; // Laser thickness self.chargeTime = 180; // 3 seconds charge time self.fireTime = 60; // 1 second fire duration self.currentPhase = 'charging'; // 'charging', 'firing', 'done' self.warningAlpha = 0; // Warning line var warningLine = self.attachAsset('healthbar', { anchorX: 0.0, anchorY: 0.5, scaleX: 1, scaleY: 0.2 }); warningLine.tint = 0xffff00; // Yellow warning warningLine.alpha = 0; self.update = function () { if (self.currentPhase === 'charging') { self.chargeTime--; // Pulsing warning effect self.warningAlpha += 0.1; warningLine.alpha = 0.5 + Math.sin(self.warningAlpha) * 0.5; // Set warning line size and position if (self.isHorizontal) { warningLine.scaleX = 20.48; // Full screen width warningLine.scaleY = 0.4; warningLine.x = 0; warningLine.y = 0; } else { warningLine.scaleX = 0.4; warningLine.scaleY = 27.32; // Full screen height warningLine.x = 0; warningLine.y = -1366; } if (self.chargeTime <= 0) { self.currentPhase = 'firing'; warningLine.alpha = 0; LK.getSound('laser_fire').play(); // Set actual laser size if (self.isHorizontal) { graphics.scaleX = 20.48; // Full screen width graphics.scaleY = self.thickness / 100; graphics.x = 0; graphics.y = 0; } else { graphics.scaleX = self.thickness / 100; graphics.scaleY = 27.32; // Full screen height graphics.x = 0; graphics.y = -1366; } graphics.alpha = 1; } } else if (self.currentPhase === 'firing') { self.fireTime--; // Flickering laser effect graphics.alpha = 0.8 + Math.random() * 0.2; if (self.fireTime <= 0) { self.currentPhase = 'done'; graphics.alpha = 0; } } }; self.checkPlayerCollision = function (playerObj) { if (self.currentPhase !== 'firing') return false; if (self.isHorizontal) { // Horizontal laser - check Y position return Math.abs(playerObj.y - self.y) < self.thickness / 2; } else { // Vertical laser - check X position return Math.abs(playerObj.x - self.x) < self.thickness / 2; } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); var thruster = self.attachAsset('thruster', { anchorX: 0.5, anchorY: 0.0 }); thruster.y = graphics.height / 2; self.health = 3; self.maxHealth = 3; self.shootCooldown = 0; self.invulnerable = 0; self.skillCooldown = 0; self.skillMaxCooldown = 420; // 7 seconds at 60fps self.update = function () { if (self.shootCooldown > 0) { self.shootCooldown--; } if (self.skillCooldown > 0) { self.skillCooldown--; } if (self.invulnerable > 0) { self.invulnerable--; graphics.alpha = self.invulnerable % 10 < 5 ? 0.5 : 1.0; } else { graphics.alpha = 1.0; } // Thruster flickering effect thruster.scaleX = 0.8 + Math.random() * 0.4; thruster.scaleY = 0.8 + Math.random() * 0.4; thruster.alpha = 0.7 + Math.random() * 0.3; thruster.x = (Math.random() - 0.5) * 8; }; self.shoot = function () { if (self.shootCooldown <= 0 && bullets.length < 20) { var bullet = new PlayerBullet(); bullet.x = self.x; bullet.y = self.y - graphics.height / 2; bullets.push(bullet); game.addChild(bullet); self.shootCooldown = playerFireRate; LK.getSound('shoot').play(); } }; self.useSpecialSkill = function () { if (self.skillCooldown <= 0 && (enemies.length > 0 || bossActive)) { var missileCount = playerMissileCount; var baseDelay = 0; for (var i = 0; i < missileCount; i++) { // Create missile with delay for sequence effect LK.setTimeout(function (index) { return function () { var missile = new HomingMissile(); // Spread missiles horizontally var spreadWidth = 200; var xOffset = (index - (missileCount - 1) / 2) * (spreadWidth / (missileCount - 1)); missile.x = self.x + xOffset; missile.y = self.y - graphics.height / 2; missiles.push(missile); game.addChild(missile); LK.getSound('missile_launch').play(); }; }(i), baseDelay + i * 150); // 150ms delay between each missile } self.skillCooldown = self.skillMaxCooldown; LK.effects.flashObject(self, 0x00ffff, 300); } }; self.takeDamage = function () { if (self.invulnerable <= 0) { self.health--; self.invulnerable = 120; LK.effects.flashObject(self, 0xff0000, 500); if (self.health <= 0) { LK.showGameOver(); } } }; return self; }); var PlayerBullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -8; self.update = function () { self.y += self.speed; }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.speedY = 3; self.bobOffset = Math.random() * Math.PI * 2; self.startY = 0; self.update = function () { self.y += self.speedY; // Apply isometric movement effect self.x += self.speedY * 0.5; // Bobbing animation graphics.y = Math.sin(LK.ticks * 0.1 + self.bobOffset) * 10; }; return self; }); var Star = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('star', { anchorX: 0.5, anchorY: 0.5 }); self.speedY = 1 + Math.random() * 3; self.twinkleSpeed = 0.02 + Math.random() * 0.08; self.twinkleOffset = Math.random() * Math.PI * 2; self.update = function () { self.y += self.speedY; // Twinkling effect graphics.alpha = 0.3 + Math.sin(LK.ticks * self.twinkleSpeed + self.twinkleOffset) * 0.7; graphics.scaleX = graphics.scaleY = 0.5 + Math.sin(LK.ticks * self.twinkleSpeed + self.twinkleOffset) * 0.5; // Reset position when off screen if (self.y > 2800) { self.y = -50; self.x = Math.random() * 2048; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Menu system variables var gameStarted = false; var mainMenu = null; var logoDisplay = null; var playButton = null; var subtitle = null; var menuBackground = null; // Game variables var player; var enemies = []; var bullets = []; var missiles = []; var powerups = []; var enemyBullets = []; var bossBullets = []; var boss = null; var bossActive = false; var enemiesKilledInWave = 0; var enemySpawnTimer = 0; var powerupSpawnTimer = 0; var gameSpeed = 1; var waveLevel = 1; var lastHealthDisplay = 3; var bossHealthBar = null; var bossHealthBarBg = null; var bossHealthText = null; // Upgrade system variables var upgradeMenuActive = false; var upgradeOption1 = null; var upgradeOption2 = null; var upgradeBackground = null; var upgradeTitle = null; var playerFireRate = 15; // Current fire rate cooldown var playerMissileCount = 3; // Current missile count // Wave system variables var enemiesInCurrentWave = 0; var enemiesSpawnedInWave = 0; var maxEnemiesForWave = 5; var lastBossWaveEnemies = 5; var waveComplete = false; // Movement variables var moveLeft = false; var moveRight = false; var moveUp = false; var moveDown = false; var dragNode = null; // Performance optimization - object pools var explosionPool = []; var fragmentPool = []; var maxPoolSize = 50; // Background elements var stars = []; // Laser phase variables var laserPhaseActive = false; var laserBeam = null; var laserPhaseTimer = 0; var laserWarningText = null; // Create main menu function function createMainMenu() { // Create semi-transparent background menuBackground = LK.getAsset('logo', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.5, scaleY: 13.6 }); menuBackground.tint = 0x000000; menuBackground.alpha = 0.8; menuBackground.x = 1024; menuBackground.y = 1366; game.addChild(menuBackground); // Create game logo logoDisplay = new Text2('SHOT SHOT IN SPACE', { size: 150, fill: 0x00FFFF }); logoDisplay.anchor.set(0.5, 0.5); logoDisplay.x = 1024; logoDisplay.y = 800; game.addChild(logoDisplay); // Create subtitle subtitle = new Text2('Survive endless waves!', { size: 80, fill: 0xFFFFFF }); subtitle.anchor.set(0.5, 0.5); subtitle.x = 1024; subtitle.y = 950; game.addChild(subtitle); // Create play button playButton = new Text2('PLAY', { size: 120, fill: 0xFFD700 }); playButton.anchor.set(0.5, 0.5); playButton.x = 1024; playButton.y = 1400; playButton.interactive = true; playButton.down = function () { startGame(); }; game.addChild(playButton); // Add pulsing effect to logo tween(logoDisplay, { scaleX: 1.1, scaleY: 1.1 }, { duration: 2000, easing: tween.easeInOut, loop: true, reverse: true }); // Add pulsing effect to play button tween(playButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 1500, easing: tween.easeInOut, loop: true, reverse: true }); mainMenu = true; } // Start game function function startGame() { if (!mainMenu) { return; } // Remove menu elements if (menuBackground) { menuBackground.destroy(); menuBackground = null; } if (logoDisplay) { logoDisplay.destroy(); logoDisplay = null; } if (playButton) { playButton.destroy(); playButton = null; } if (subtitle) { subtitle.destroy(); subtitle = null; } mainMenu = false; gameStarted = true; // Flash effect when starting LK.effects.flashScreen(0x00FFFF, 500); } // Create initial background stars for (var i = 0; i < 100; i++) { var star = new Star(); star.x = Math.random() * 2048; star.y = Math.random() * 2732; stars.push(star); game.addChild(star); } // Show main menu first createMainMenu(); // Initialize player (but don't show until game starts) player = new Player(); player.x = 1024; player.y = 2200; // Score display var scoreTxt = new Text2('Score: 0', { size: 60, fill: 0x00FFFF }); scoreTxt.anchor.set(0, 0); scoreTxt.x = 120; scoreTxt.y = 50; LK.gui.topLeft.addChild(scoreTxt); // Health display var healthTxt = new Text2('Health: 3', { size: 60, fill: 0xFF0000 }); healthTxt.anchor.set(0, 0); healthTxt.x = 120; healthTxt.y = 120; LK.gui.topLeft.addChild(healthTxt); // Wave display var waveTxt = new Text2('Wave: 1', { size: 60, fill: 0xFFFF00 }); waveTxt.anchor.set(1, 0); LK.gui.topRight.addChild(waveTxt); // Skill display var skillTxt = new Text2('Skill: Ready', { size: 60, fill: 0x00FFFF }); skillTxt.anchor.set(0.5, 0); skillTxt.y = 50; LK.gui.top.addChild(skillTxt); // Touch controls game.down = function (x, y, obj) { if (!gameStarted || mainMenu) { return; } dragNode = true; player.shoot(); // Immediate response - move player closer to touch point instantly var gamePos = game.toLocal({ x: x, y: y }); var targetX = Math.max(50, Math.min(1998, gamePos.x)); var targetY = Math.max(100, Math.min(2650, gamePos.y)); var dx = targetX - player.x; var dy = targetY - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Instant partial movement for immediate feedback if (distance > 30) { var instantSpeed = 25; player.x += dx / distance * instantSpeed; player.y += dy / distance * instantSpeed; } handleMove(x, y, obj); }; game.up = function (x, y, obj) { dragNode = null; }; function handleMove(x, y, obj) { if (!gameStarted || mainMenu) { return; } if (dragNode) { // Convert screen coordinates to game coordinates var gamePos = game.toLocal({ x: x, y: y }); // Smooth movement towards touch position with larger movement area var targetX = Math.max(50, Math.min(1998, gamePos.x)); var targetY = Math.max(100, Math.min(2650, gamePos.y)); var dx = targetX - player.x; var dy = targetY - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 10) { var speed = 12; player.x += dx / distance * speed; player.y += dy / distance * speed; } else { // When very close to target, snap to position for precise control player.x = targetX; player.y = targetY; } } } game.move = handleMove; // Spawn enemy function function spawnEnemy() { var enemy = new Enemy(); // Random spawn position from edges with isometric consideration var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top enemy.x = Math.random() * 2048; enemy.y = -50; enemy.speedY = 3 + Math.random() * 3; break; case 1: // Right enemy.x = 2098; enemy.y = Math.random() * 1000; enemy.speedX = -3 - Math.random() * 3; enemy.speedY = 2 + Math.random() * 2; break; case 2: // Left enemy.x = -50; enemy.y = Math.random() * 1000; enemy.speedX = 3 + Math.random() * 3; enemy.speedY = 2 + Math.random() * 2; break; case 3: // Top-left diagonal (isometric style) enemy.x = -50; enemy.y = -50; enemy.speedX = 4 + Math.random() * 3; enemy.speedY = 4 + Math.random() * 3; break; } // Apply game speed multiplier enemy.speedX *= gameSpeed; enemy.speedY *= gameSpeed; enemies.push(enemy); game.addChild(enemy); } // Spawn powerup function function spawnPowerup() { var powerup = new PowerUp(); powerup.x = Math.random() * 1800 + 124; powerup.y = -50; powerup.startY = powerup.y; powerups.push(powerup); game.addChild(powerup); } // Optimized explosion function with pooling function createExplosion(x, y, count, scale, duration) { count = Math.min(count || 5, 8); // Limit explosion count for (var i = 0; i < count; i++) { var explosion; if (explosionPool.length > 0) { explosion = explosionPool.pop(); explosion.alpha = 1; explosion.scaleX = scale || 0.4; explosion.scaleY = scale || 0.4; explosion.rotation = 0; } else { explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: scale || 0.4, scaleY: scale || 0.4 }); } explosion.x = x + (Math.random() - 0.5) * 40; explosion.y = y + (Math.random() - 0.5) * 40; game.addChild(explosion); var angle = i / count * Math.PI * 2; var distance = 30 + Math.random() * 20; var targetX = explosion.x + Math.cos(angle) * distance; var targetY = explosion.y + Math.sin(angle) * distance; tween(explosion, { x: targetX, y: targetY, alpha: 0, scaleX: (scale || 0.4) * 1.5, scaleY: (scale || 0.4) * 1.5, rotation: Math.random() * Math.PI * 2 }, { duration: duration || 400, easing: tween.easeOut, onFinish: function (exp) { return function () { if (exp.parent) { exp.parent.removeChild(exp); if (explosionPool.length < maxPoolSize) { explosionPool.push(exp); } } }; }(explosion) }); } } // Optimized fragment function with pooling function createFragments(x, y, count, assetId, scale) { count = Math.min(count || 4, 6); // Limit fragment count for (var i = 0; i < count; i++) { var fragment; if (fragmentPool.length > 0) { fragment = fragmentPool.pop(); fragment.alpha = 1; fragment.scaleX = scale || 0.2; fragment.scaleY = scale || 0.2; fragment.rotation = 0; } else { fragment = LK.getAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: scale || 0.2, scaleY: scale || 0.2 }); } fragment.x = x + (Math.random() - 0.5) * 20; fragment.y = y + (Math.random() - 0.5) * 20; game.addChild(fragment); var angle = i / count * Math.PI * 2; var distance = 40 + Math.random() * 30; var targetX = fragment.x + Math.cos(angle) * distance; var targetY = fragment.y + Math.sin(angle) * distance; tween(fragment, { x: targetX, y: targetY, alpha: 0, rotation: Math.random() * Math.PI * 2 }, { duration: 400, easing: tween.easeOut, onFinish: function (frag) { return function () { if (frag.parent) { frag.parent.removeChild(frag); if (fragmentPool.length < maxPoolSize) { fragmentPool.push(frag); } } }; }(fragment) }); } } // Create upgrade selection menu function createUpgradeMenu() { upgradeMenuActive = true; // Create semi-transparent background upgradeBackground = LK.getAsset('healthbar', { anchorX: 0.5, anchorY: 0.5, scaleX: 25, scaleY: 35 }); upgradeBackground.tint = 0x000000; upgradeBackground.alpha = 0.8; upgradeBackground.x = 1024; upgradeBackground.y = 1366; game.addChild(upgradeBackground); // Create title upgradeTitle = new Text2('CHOOSE UPGRADE', { size: 120, fill: 0xFFD700 }); upgradeTitle.anchor.set(0.5, 0.5); upgradeTitle.x = 1024; upgradeTitle.y = 800; game.addChild(upgradeTitle); // Create option 1: Fire Rate upgradeOption1 = new Text2('+1 FIRE RATE', { size: 80, fill: 0x00FFFF }); upgradeOption1.anchor.set(0.5, 0.5); upgradeOption1.x = 700; upgradeOption1.y = 1200; upgradeOption1.interactive = true; upgradeOption1.down = function () { selectFireRateUpgrade(); }; game.addChild(upgradeOption1); // Create option 2: Missile Count upgradeOption2 = new Text2('+1 MISSILE', { size: 80, fill: 0x00FFFF }); upgradeOption2.anchor.set(0.5, 0.5); upgradeOption2.x = 1348; upgradeOption2.y = 1200; upgradeOption2.interactive = true; upgradeOption2.down = function () { selectMissileUpgrade(); }; game.addChild(upgradeOption2); // Add visual effects tween(upgradeTitle, { scaleX: 1.1, scaleY: 1.1 }, { duration: 1000, easing: tween.easeInOut, loop: true, reverse: true }); tween(upgradeOption1, { scaleX: 1.05, scaleY: 1.05 }, { duration: 800, easing: tween.easeInOut, loop: true, reverse: true }); tween(upgradeOption2, { scaleX: 1.05, scaleY: 1.05 }, { duration: 800, easing: tween.easeInOut, loop: true, reverse: true, delay: 400 }); } // Select fire rate upgrade function selectFireRateUpgrade() { if (!upgradeMenuActive) { return; } playerFireRate = Math.max(5, playerFireRate - 3); // Increase fire rate (decrease cooldown) closeUpgradeMenu(); // Show confirmation effect LK.effects.flashScreen(0x00FFFF, 500); } // Select missile upgrade function selectMissileUpgrade() { if (!upgradeMenuActive) { return; } playerMissileCount = Math.min(7, playerMissileCount + 1); // Increase missile count (max 7) closeUpgradeMenu(); // Show confirmation effect LK.effects.flashScreen(0xFF6600, 500); } // Close upgrade menu and resume game function closeUpgradeMenu() { upgradeMenuActive = false; if (upgradeBackground) { upgradeBackground.destroy(); upgradeBackground = null; } if (upgradeTitle) { upgradeTitle.destroy(); upgradeTitle = null; } if (upgradeOption1) { upgradeOption1.destroy(); upgradeOption1 = null; } if (upgradeOption2) { upgradeOption2.destroy(); upgradeOption2 = null; } } // Main game update game.update = function () { // Skip game updates when in menu or upgrade menu is active if (mainMenu || upgradeMenuActive) { return; } // Add player to game when first started if (gameStarted && !player.parent) { game.addChild(player); } // Update background elements for (var i = 0; i < stars.length; i++) { // Stars update automatically through their update method } // Auto-shoot if (LK.ticks % 20 === 0) { player.shoot(); } // Auto-use special skill if (player.skillCooldown <= 0 && (enemies.length > 0 || bossActive)) { player.useSpecialSkill(); } // Update skill display if (player.skillCooldown > 0) { skillTxt.setText('Skill: ' + Math.ceil(player.skillCooldown / 60) + 's'); skillTxt.fill = 0x888888; } else { skillTxt.setText('Skill: Ready'); skillTxt.fill = 0x00FFFF; } // Calculate max enemies for current wave if (waveLevel % 5 === 0) { // Boss wave (every 5th wave) maxEnemiesForWave = 0; } else { // Regular wave: 5 * wave number, but after boss waves add bonus var waveInCycle = waveLevel % 5; if (waveLevel > 5) { // After first boss, add bonus based on last boss wave maxEnemiesForWave = (5 + lastBossWaveEnemies) * waveInCycle; } else { // First 4 waves: simple 5 * wave maxEnemiesForWave = 5 * waveLevel; } } // Boss spawning logic for waves 5, 10, 15, etc. if (waveLevel % 5 === 0 && !bossActive && enemies.length === 0 && enemiesSpawnedInWave >= maxEnemiesForWave) { // Kill all remaining enemies and spawn boss for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].destroy(); enemies.splice(i, 1); } // Remember enemy count from this boss wave for next cycle lastBossWaveEnemies = enemiesKilledInWave; // Spawn boss boss = new Boss(); boss.x = 1024; boss.y = 200; game.addChild(boss); bossActive = true; enemiesKilledInWave = 0; // Create boss health bar (full screen width) bossHealthBarBg = LK.getAsset('healthbar', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 0.8 }); bossHealthBarBg.tint = 0x333333; bossHealthBarBg.x = 1024; bossHealthBarBg.y = 350; game.addChild(bossHealthBarBg); bossHealthBar = LK.getAsset('healthbar', { anchorX: 0.0, anchorY: 0.5, scaleX: 20, scaleY: 0.6 }); bossHealthBar.tint = 0xff0000; bossHealthBar.x = 24; bossHealthBar.y = 350; game.addChild(bossHealthBar); // Create boss health text display bossHealthText = new Text2(boss.health + '/' + boss.maxHealth, { size: 80, fill: 0xFFFFFF }); bossHealthText.anchor.set(0.5, 0.5); bossHealthText.x = 1024; bossHealthText.y = 350; game.addChild(bossHealthText); // Boss intro effect LK.effects.flashScreen(0x8800ff, 1000); var bossTxt = new Text2('BOSS WAVE!', { size: 150, fill: 0xFF0000 }); bossTxt.anchor.set(0.5, 0.5); bossTxt.x = 1024; bossTxt.y = 1366; bossTxt.alpha = 0; bossTxt.scaleX = 0.1; bossTxt.scaleY = 0.1; game.addChild(bossTxt); tween(bossTxt, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 800, easing: tween.elasticOut, onFinish: function onFinish() { tween(bossTxt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (bossTxt.parent) { bossTxt.destroy(); } } }); } }); } else if (!bossActive && enemiesSpawnedInWave < maxEnemiesForWave) { // Normal enemy spawning - only spawn if we haven't reached the wave limit enemySpawnTimer++; var spawnRate = Math.max(30 - Math.floor(waveLevel * 2), 10); if (enemySpawnTimer >= spawnRate && enemies.length < 15) { spawnEnemy(); enemiesSpawnedInWave++; enemySpawnTimer = 0; } } // Powerup spawning powerupSpawnTimer++; if (powerupSpawnTimer >= 600) { // Every 10 seconds spawnPowerup(); powerupSpawnTimer = 0; } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (bullet.lastY === undefined) { bullet.lastY = bullet.y; } // Remove bullets that go off screen (expanded bounds check) if (bullet.y < -100 || bullet.y > 2800 || bullet.x < -100 || bullet.x > 2148) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check bullet-enemy collisions var hitEnemy = false; for (var j = enemies.length - 1; j >= 0; j--) { if (bullet.intersects(enemies[j])) { // Enemy hit LK.setScore(LK.getScore() + 10); scoreTxt.setText('Score: ' + LK.getScore()); enemiesKilledInWave++; // Create optimized effects var enemy = enemies[j]; createFragments(enemy.x, enemy.y, 4, 'enemy', 0.2); enemy.destroy(); enemies.splice(j, 1); // Destroy bullet bullet.destroy(); bullets.splice(i, 1); LK.getSound('hit').play(); hitEnemy = true; break; } } // Check bullet-boss collision if (bossActive && boss && bullet.intersects(boss)) { boss.takeDamage(); // No score gain from hitting boss with bullets bullet.destroy(); bullets.splice(i, 1); LK.getSound('hit').play(); hitEnemy = true; // Check if boss is dead if (boss.health <= 0) { // Boss death effect createExplosion(boss.x, boss.y, 8, 0.6, 600); LK.setScore(LK.getScore() + 500); scoreTxt.setText('Score: ' + LK.getScore()); // Remove boss health bar if (bossHealthBar) { bossHealthBar.destroy(); bossHealthBar = null; } if (bossHealthBarBg) { bossHealthBarBg.destroy(); bossHealthBarBg = null; } if (bossHealthText) { bossHealthText.destroy(); bossHealthText = null; } boss.destroy(); boss = null; bossActive = false; // Show upgrade selection menu createUpgradeMenu(); } } if (!hitEnemy) { bullet.lastY = bullet.y; } } // Update missiles for (var i = missiles.length - 1; i >= 0; i--) { var missile = missiles[i]; // Remove missiles that expire or go off screen if (missile.life <= 0 || missile.y < -100 || missile.y > 2800 || missile.x < -100 || missile.x > 2148) { // Create optimized explosion effect createExplosion(missile.x, missile.y, 4, 0.3, 300); missile.destroy(); missiles.splice(i, 1); continue; } // Check missile-enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { if (missile.intersects(enemies[j])) { // Enemy hit by missile LK.setScore(LK.getScore() + 15); scoreTxt.setText('Score: ' + LK.getScore()); enemiesKilledInWave++; // Create optimized explosion and fragment effects var enemy = enemies[j]; createExplosion(missile.x, missile.y, 5, 0.4, 400); createFragments(enemy.x, enemy.y, 4, 'enemy', 0.15); enemy.destroy(); enemies.splice(j, 1); missile.destroy(); missiles.splice(i, 1); LK.getSound('missile_hit').play(); break; } } // Check missile-boss collision if (bossActive && boss && missile.intersects(boss)) { boss.takeDamage(); // No score gain from hitting boss with missiles // Create explosion effect var explosionCount = 8; for (var e = 0; e < explosionCount; e++) { var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); explosion.x = missile.x + (Math.random() - 0.5) * 50; explosion.y = missile.y + (Math.random() - 0.5) * 50; game.addChild(explosion); var angle = e / explosionCount * Math.PI * 2; var distance = 60 + Math.random() * 50; var targetX = explosion.x + Math.cos(angle) * distance; var targetY = explosion.y + Math.sin(angle) * distance; tween(explosion, { x: targetX, y: targetY, alpha: 0, scaleX: 1.0, scaleY: 1.0, rotation: Math.random() * Math.PI * 2 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (explosion.parent) { explosion.destroy(); } } }); } missile.destroy(); missiles.splice(i, 1); LK.getSound('missile_hit').play(); // Check if boss is dead if (boss.health <= 0) { // Boss death effect var explosionCount = 12; for (var e = 0; e < explosionCount; e++) { var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); explosion.x = boss.x + (Math.random() - 0.5) * 100; explosion.y = boss.y + (Math.random() - 0.5) * 100; game.addChild(explosion); var angle = e / explosionCount * Math.PI * 2; var distance = 80 + Math.random() * 60; var targetX = explosion.x + Math.cos(angle) * distance; var targetY = explosion.y + Math.sin(angle) * distance; tween(explosion, { x: targetX, y: targetY, alpha: 0, scaleX: 1.2, scaleY: 1.2, rotation: Math.random() * Math.PI * 2 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (explosion.parent) { explosion.destroy(); } } }); } LK.setScore(LK.getScore() + 500); scoreTxt.setText('Score: ' + LK.getScore()); // Remove boss health bar if (bossHealthBar) { bossHealthBar.destroy(); bossHealthBar = null; } if (bossHealthBarBg) { bossHealthBarBg.destroy(); bossHealthBarBg = null; } if (bossHealthText) { bossHealthText.destroy(); bossHealthText = null; } boss.destroy(); boss = null; bossActive = false; // Show upgrade selection menu createUpgradeMenu(); } break; } } // Update enemies (reduce collision check frequency) for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; // Remove enemies that go off screen if (enemy.y > 2800 || enemy.x < -100 || enemy.x > 2148) { enemy.destroy(); enemies.splice(i, 1); continue; } // Check player-enemy collision (only every 3rd frame for performance) // Use smaller hitbox for player by checking distance instead of full intersects if (LK.ticks % 3 === 0) { var dx = enemy.x - player.x; var dy = enemy.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Reduced hitbox: use 60% of normal collision area if (distance < 60) { player.takeDamage(); // Create optimized fragmentation effect createFragments(enemy.x, enemy.y, 4, 'enemy', 0.2); enemy.destroy(); enemies.splice(i, 1); } } } // Update enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var enemyBullet = enemyBullets[i]; // Remove bullets that go off screen if (enemyBullet.y > 2800 || enemyBullet.x < -100 || enemyBullet.x > 2148 || enemyBullet.y < -100) { enemyBullet.destroy(); enemyBullets.splice(i, 1); continue; } // Check enemy bullet-player collision with smaller player hitbox var dx = enemyBullet.x - player.x; var dy = enemyBullet.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Reduced hitbox: use 50% of normal collision area for bullets if (distance < 50) { player.takeDamage(); enemyBullet.destroy(); enemyBullets.splice(i, 1); } } // Update boss bullets for (var i = bossBullets.length - 1; i >= 0; i--) { var bossBullet = bossBullets[i]; // Remove bullets that go off screen if (bossBullet.y > 2800 || bossBullet.x < -100 || bossBullet.x > 2148 || bossBullet.y < -100) { bossBullet.destroy(); bossBullets.splice(i, 1); continue; } // Check boss bullet-player collision with smaller player hitbox var dx = bossBullet.x - player.x; var dy = bossBullet.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Reduced hitbox: use 50% of normal collision area for bullets if (distance < 50) { player.takeDamage(); bossBullet.destroy(); bossBullets.splice(i, 1); } } // Update boss if active if (bossActive && boss) { // Update boss health bar if (bossHealthBar && bossHealthBarBg && bossHealthText) { var healthRatio = boss.health / boss.maxHealth; bossHealthBar.scaleX = healthRatio * 20; // Update health text bossHealthText.setText(boss.health + '/' + boss.maxHealth); // Change color based on health if (healthRatio > 0.6) { bossHealthBar.tint = 0x00ff00; // Green } else if (healthRatio > 0.3) { bossHealthBar.tint = 0xffff00; // Yellow } else { bossHealthBar.tint = 0xff0000; // Red } } // Check boss-player collision with smaller player hitbox var dx = boss.x - player.x; var dy = boss.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Reduced hitbox: use 70% of normal collision area for boss if (distance < 70) { player.takeDamage(); } } // Update powerups for (var i = powerups.length - 1; i >= 0; i--) { var powerup = powerups[i]; // Remove powerups that go off screen if (powerup.y > 2800) { powerup.destroy(); powerups.splice(i, 1); continue; } // Check player-powerup collision if (powerup.intersects(player)) { player.health = Math.min(player.health + 1, player.maxHealth); LK.getSound('powerup').play(); LK.effects.flashObject(player, 0x00ff00, 300); // Create red spread effect var spreadEffectCount = 8; for (var s = 0; s < spreadEffectCount; s++) { var redSpread = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); redSpread.tint = 0xff0000; // Red color redSpread.x = powerup.x; redSpread.y = powerup.y; redSpread.alpha = 0.8; game.addChild(redSpread); var angle = s / spreadEffectCount * Math.PI * 2; var distance = 150 + Math.random() * 100; var targetX = redSpread.x + Math.cos(angle) * distance; var targetY = redSpread.y + Math.sin(angle) * distance; tween(redSpread, { x: targetX, y: targetY, alpha: 0, scaleX: 1.2, scaleY: 1.2, rotation: Math.random() * Math.PI * 2 }, { duration: 800, easing: tween.easeOut, onFinish: function (spread) { return function () { if (spread.parent) { spread.destroy(); } }; }(redSpread) }); } powerup.destroy(); powerups.splice(i, 1); } } // Update health display if (player.health !== lastHealthDisplay) { healthTxt.setText('Health: ' + player.health); lastHealthDisplay = player.health; } // Check wave completion based on enemy count if (!bossActive && enemies.length === 0 && enemiesSpawnedInWave >= maxEnemiesForWave && !waveComplete) { // Wave completed - all enemies spawned and killed waveComplete = true; // Advance to next wave waveLevel++; gameSpeed += 0.1; waveTxt.setText('Wave: ' + waveLevel); // Reset wave tracking variables enemiesSpawnedInWave = 0; enemiesKilledInWave = 0; // Create stylish "WAVE COMPLETED" text var waveCompletedTxt = new Text2('WAVE COMPLETED', { size: 120, fill: 0xFFD700 }); waveCompletedTxt.anchor.set(0.5, 0.5); waveCompletedTxt.x = 1024; waveCompletedTxt.y = 1366; waveCompletedTxt.alpha = 0; waveCompletedTxt.scaleX = 0.1; waveCompletedTxt.scaleY = 0.1; game.addChild(waveCompletedTxt); // Animate the text with a stylish effect tween(waveCompletedTxt, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.elasticOut, onFinish: function onFinish() { // Hold for a moment then fade out tween(waveCompletedTxt, { alpha: 0, scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { if (waveCompletedTxt.parent) { waveCompletedTxt.destroy(); } // Allow next wave to start waveComplete = false; } }); } }); } // Boss wave completion - start laser phase if (bossActive && !boss && !laserPhaseActive) { // Start laser phase after boss defeat laserPhaseActive = true; laserPhaseTimer = 0; // Create warning text laserWarningText = new Text2('DODGE THE LASER!', { size: 100, fill: 0xFF0000 }); laserWarningText.anchor.set(0.5, 0.5); laserWarningText.x = 1024; laserWarningText.y = 1000; laserWarningText.alpha = 0; game.addChild(laserWarningText); // Animate warning text tween(laserWarningText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 1000, easing: tween.easeOut }); // Create laser beam after short delay LK.setTimeout(function () { // Random position and direction var isHorizontal = Math.random() < 0.5; laserBeam = new LaserBeam(); laserBeam.isHorizontal = isHorizontal; if (isHorizontal) { // Horizontal laser at random Y position laserBeam.x = 0; laserBeam.y = 200 + Math.random() * 2000; // Random Y between 200-2200 } else { // Vertical laser at random X position laserBeam.x = 200 + Math.random() * 1648; // Random X between 200-1848 laserBeam.y = 1366; } game.addChild(laserBeam); LK.getSound('laser_charge').play(); }, 1500); } // Handle laser phase if (laserPhaseActive) { laserPhaseTimer++; // Check laser collision with player if (laserBeam && laserBeam.checkPlayerCollision(player)) { // Player hit by laser - game over LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check if laser phase is complete if (laserBeam && laserBeam.currentPhase === 'done') { // Laser phase completed successfully laserPhaseActive = false; // Clean up laser elements if (laserBeam) { laserBeam.destroy(); laserBeam = null; } if (laserWarningText) { laserWarningText.destroy(); laserWarningText = null; } // Continue to next wave waveLevel++; gameSpeed += 0.1; waveTxt.setText('Wave: ' + waveLevel); enemiesSpawnedInWave = 0; enemiesKilledInWave = 0; waveComplete = false; // Success effect LK.effects.flashScreen(0x00FF00, 500); var successTxt = new Text2('LASER DODGED!', { size: 120, fill: 0x00FF00 }); successTxt.anchor.set(0.5, 0.5); successTxt.x = 1024; successTxt.y = 1366; successTxt.alpha = 0; game.addChild(successTxt); tween(successTxt, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 800, easing: tween.elasticOut, onFinish: function onFinish() { tween(successTxt, { alpha: 0, scaleY: 1.5 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (successTxt.parent) { successTxt.destroy(); } } }); } }); } } // Boss wave completion (moved after laser phase) if (bossActive && !boss && !laserPhaseActive) { // This will now only trigger after laser phase is complete // The wave progression is handled in the laser phase completion above } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Boss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Add thrusters to boss
var thruster1 = self.attachAsset('bossThruster', {
anchorX: 0.5,
anchorY: 0.0
});
var thruster2 = self.attachAsset('bossThruster', {
anchorX: 0.5,
anchorY: 0.0
});
// Position thrusters at boss top corners
thruster1.x = -graphics.width / 4;
thruster1.y = -graphics.height / 2;
thruster2.x = graphics.width / 4;
thruster2.y = -graphics.height / 2;
self.health = 20 + waveLevel * 5; // Scales with wave level
self.maxHealth = self.health;
self.speedX = 2;
self.speedY = 1;
self.shootCooldown = 0;
self.directionChangeTimer = 0;
self.invulnerable = 0;
self.canBeTargeted = true; // Allow missiles to target boss
self.update = function () {
// Direction change logic
self.directionChangeTimer--;
if (self.directionChangeTimer <= 0) {
self.speedX = (Math.random() - 0.5) * 4; // Random horizontal movement
self.speedY = Math.random() * 2 + 0.5; // Slow downward movement
self.directionChangeTimer = 60 + Math.random() * 120; // 1-3 seconds
}
// Movement with screen bounds checking
self.x += self.speedX;
self.y += self.speedY;
// Keep boss on screen horizontally
if (self.x < 100) {
self.x = 100;
self.speedX = Math.abs(self.speedX);
}
if (self.x > 1948) {
self.x = 1948;
self.speedX = -Math.abs(self.speedX);
}
// Keep boss in upper portion of screen
if (self.y < 100) {
self.y = 100;
self.speedY = Math.abs(self.speedY);
}
if (self.y > 800) {
self.y = 800;
self.speedY = -Math.abs(self.speedY);
}
// Shooting behavior
self.shootCooldown--;
if (self.shootCooldown <= 0) {
self.shoot();
self.shootCooldown = 90 + Math.random() * 60; // 1.5-2.5 seconds
}
// Thruster flickering effects
thruster1.scaleX = 0.6 + Math.random() * 0.8;
thruster1.scaleY = 0.6 + Math.random() * 0.8;
thruster1.alpha = 0.6 + Math.random() * 0.4;
thruster1.x = -graphics.width / 4 + (Math.random() - 0.5) * 8;
thruster2.scaleX = 0.6 + Math.random() * 0.8;
thruster2.scaleY = 0.6 + Math.random() * 0.8;
thruster2.alpha = 0.6 + Math.random() * 0.4;
thruster2.x = graphics.width / 4 + (Math.random() - 0.5) * 8;
// Invulnerability flashing
if (self.invulnerable > 0) {
self.invulnerable--;
graphics.alpha = self.invulnerable % 8 < 4 ? 0.3 : 1.0;
} else {
graphics.alpha = 1.0;
}
// Health-based tint
var healthRatio = self.health / self.maxHealth;
if (healthRatio < 0.3) {
graphics.tint = 0xff4444; // Red when low health
} else if (healthRatio < 0.6) {
graphics.tint = 0xffaa44; // Orange when medium health
} else {
graphics.tint = 0xffffff; // White when healthy
}
};
self.shoot = function () {
// Create multiple bullets in a spread pattern
var bulletCount = 3 + Math.floor(waveLevel / 5); // More bullets at higher waves
for (var i = 0; i < bulletCount; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (Math.random() - 0.5) * 40;
bullet.y = self.y + graphics.height / 2;
// Aim towards player with some spread
var dx = player.x - bullet.x;
var dy = player.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
angle += (Math.random() - 0.5) * 0.6; // Add spread
bullet.speedX = Math.cos(angle) * 4;
bullet.speedY = Math.sin(angle) * 4;
bossBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('boss_shoot').play();
};
self.takeDamage = function () {
if (self.invulnerable <= 0) {
self.health--;
self.invulnerable = 30;
LK.effects.flashObject(self, 0xff0000, 200);
}
};
return self;
});
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossbullet', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0xff4444; // Red tint for boss bullets
self.speedX = 0;
self.speedY = 4;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 2;
self.health = 1;
self.update = function () {
// Calculate direction toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Normalize direction and apply stronger homing behavior
if (distance > 0) {
var homingStrength = 0.3; // How much enemies home toward player
var targetSpeedX = dx / distance * 3; // Target speed toward player
var targetSpeedY = dy / distance * 3;
// Blend current speed with homing direction
self.speedX = self.speedX * (1 - homingStrength) + targetSpeedX * homingStrength;
self.speedY = self.speedY * (1 - homingStrength) + targetSpeedY * homingStrength;
}
self.x += self.speedX;
self.y += self.speedY;
// Apply isometric movement effect
self.x += self.speedY * 0.5;
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0xff4444; // Red tint for enemy bullets
self.speedX = 0;
self.speedY = 4;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
};
return self;
});
var HomingMissile = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('missile', {
anchorX: 0.5,
anchorY: 0.5
});
var thruster = self.attachAsset('missileThrust', {
anchorX: 0.5,
anchorY: 0.0
});
thruster.x = 0; // Center horizontally on missile
thruster.y = graphics.height / 2; // Position at missile bottom
self.target = null;
self.speed = 6;
self.turnSpeed = 0.1;
self.life = 300; // 5 seconds at 60fps
self.update = function () {
self.life--;
if (self.life <= 0) {
return;
}
// Thruster flickering effect
thruster.scaleX = 0.6 + Math.random() * 0.8;
thruster.scaleY = 0.6 + Math.random() * 0.8;
thruster.alpha = 0.6 + Math.random() * 0.4;
thruster.x = (Math.random() - 0.5) * 4;
// Find target if we don't have one
if (!self.target || !self.target.parent) {
var availableTargets = [];
// Prioritize boss if active
if (bossActive && boss && boss.parent && boss.canBeTargeted) {
availableTargets.push(boss);
}
// Also add enemies as potential targets
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var alreadyTargeted = false;
// Check if this enemy is already targeted by another missile
for (var m = 0; m < missiles.length; m++) {
if (missiles[m] !== self && missiles[m].target === enemy) {
alreadyTargeted = true;
break;
}
}
if (!alreadyTargeted) {
availableTargets.push(enemy);
}
}
// If no untargeted enemies available, allow targeting any enemy
if (availableTargets.length === 0) {
availableTargets = enemies.slice(); // Copy all enemies
// Also add boss if no enemies available
if (bossActive && boss && boss.parent && boss.canBeTargeted) {
availableTargets.push(boss);
}
}
// Select target - prioritize boss if available
if (availableTargets.length > 0) {
// Check if boss is in available targets and prioritize it
var bossInTargets = false;
for (var t = 0; t < availableTargets.length; t++) {
if (availableTargets[t] === boss) {
self.target = boss;
bossInTargets = true;
break;
}
}
// If boss not available, select random target
if (!bossInTargets) {
var randomIndex = Math.floor(Math.random() * availableTargets.length);
self.target = availableTargets[randomIndex];
}
// Add random pattern properties for approach
self.patternTime = 0;
self.patternAmplitude = 50 + Math.random() * 100; // Random curve amplitude
self.patternFrequency = 0.05 + Math.random() * 0.1; // Random curve frequency
self.patternOffset = Math.random() * Math.PI * 2; // Random phase offset
}
}
// Move towards target
if (self.target && self.target.parent) {
self.patternTime = (self.patternTime || 0) + 1;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var targetAngle = Math.atan2(dy, dx);
var currentAngle = Math.atan2(self.speedY || 0, self.speedX || -self.speed);
// Add random pattern curve to the approach
var patternCurve = Math.sin(self.patternTime * self.patternFrequency + self.patternOffset) * self.patternAmplitude;
var perpAngle = targetAngle + Math.PI / 2; // Perpendicular to target direction
var curveX = Math.cos(perpAngle) * patternCurve / distance;
var curveY = Math.sin(perpAngle) * patternCurve / distance;
// Apply curve offset to target direction
var adjustedTargetX = self.target.x + curveX;
var adjustedTargetY = self.target.y + curveY;
var adjustedDx = adjustedTargetX - self.x;
var adjustedDy = adjustedTargetY - self.y;
var adjustedTargetAngle = Math.atan2(adjustedDy, adjustedDx);
// Smooth turning towards adjusted target
var angleDiff = adjustedTargetAngle - currentAngle;
if (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
if (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
currentAngle += angleDiff * self.turnSpeed;
self.speedX = Math.cos(currentAngle) * self.speed;
self.speedY = Math.sin(currentAngle) * self.speed;
graphics.rotation = currentAngle + Math.PI / 2;
// Position thruster at missile bottom with correct rotation
thruster.rotation = graphics.rotation;
var thrusterDistance = graphics.height / 2;
thruster.x = -Math.sin(graphics.rotation) * thrusterDistance;
thruster.y = Math.cos(graphics.rotation) * thrusterDistance;
} else {
// Close to target, move directly
self.speedX = dx / distance * self.speed;
self.speedY = dy / distance * self.speed;
}
} else {
// No target, move up
if (!self.speedX) {
self.speedX = 0;
}
if (!self.speedY) {
self.speedY = -self.speed;
}
}
self.x += self.speedX;
self.y += self.speedY;
};
return self;
});
var LaserBeam = Container.expand(function () {
var self = Container.call(this);
// Create laser beam visual using shape
var graphics = self.attachAsset('healthbar', {
anchorX: 0.0,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
graphics.tint = 0xff0000; // Red laser
self.isHorizontal = true; // Direction of laser
self.thickness = 80; // Laser thickness
self.chargeTime = 180; // 3 seconds charge time
self.fireTime = 60; // 1 second fire duration
self.currentPhase = 'charging'; // 'charging', 'firing', 'done'
self.warningAlpha = 0;
// Warning line
var warningLine = self.attachAsset('healthbar', {
anchorX: 0.0,
anchorY: 0.5,
scaleX: 1,
scaleY: 0.2
});
warningLine.tint = 0xffff00; // Yellow warning
warningLine.alpha = 0;
self.update = function () {
if (self.currentPhase === 'charging') {
self.chargeTime--;
// Pulsing warning effect
self.warningAlpha += 0.1;
warningLine.alpha = 0.5 + Math.sin(self.warningAlpha) * 0.5;
// Set warning line size and position
if (self.isHorizontal) {
warningLine.scaleX = 20.48; // Full screen width
warningLine.scaleY = 0.4;
warningLine.x = 0;
warningLine.y = 0;
} else {
warningLine.scaleX = 0.4;
warningLine.scaleY = 27.32; // Full screen height
warningLine.x = 0;
warningLine.y = -1366;
}
if (self.chargeTime <= 0) {
self.currentPhase = 'firing';
warningLine.alpha = 0;
LK.getSound('laser_fire').play();
// Set actual laser size
if (self.isHorizontal) {
graphics.scaleX = 20.48; // Full screen width
graphics.scaleY = self.thickness / 100;
graphics.x = 0;
graphics.y = 0;
} else {
graphics.scaleX = self.thickness / 100;
graphics.scaleY = 27.32; // Full screen height
graphics.x = 0;
graphics.y = -1366;
}
graphics.alpha = 1;
}
} else if (self.currentPhase === 'firing') {
self.fireTime--;
// Flickering laser effect
graphics.alpha = 0.8 + Math.random() * 0.2;
if (self.fireTime <= 0) {
self.currentPhase = 'done';
graphics.alpha = 0;
}
}
};
self.checkPlayerCollision = function (playerObj) {
if (self.currentPhase !== 'firing') return false;
if (self.isHorizontal) {
// Horizontal laser - check Y position
return Math.abs(playerObj.y - self.y) < self.thickness / 2;
} else {
// Vertical laser - check X position
return Math.abs(playerObj.x - self.x) < self.thickness / 2;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
var thruster = self.attachAsset('thruster', {
anchorX: 0.5,
anchorY: 0.0
});
thruster.y = graphics.height / 2;
self.health = 3;
self.maxHealth = 3;
self.shootCooldown = 0;
self.invulnerable = 0;
self.skillCooldown = 0;
self.skillMaxCooldown = 420; // 7 seconds at 60fps
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.skillCooldown > 0) {
self.skillCooldown--;
}
if (self.invulnerable > 0) {
self.invulnerable--;
graphics.alpha = self.invulnerable % 10 < 5 ? 0.5 : 1.0;
} else {
graphics.alpha = 1.0;
}
// Thruster flickering effect
thruster.scaleX = 0.8 + Math.random() * 0.4;
thruster.scaleY = 0.8 + Math.random() * 0.4;
thruster.alpha = 0.7 + Math.random() * 0.3;
thruster.x = (Math.random() - 0.5) * 8;
};
self.shoot = function () {
if (self.shootCooldown <= 0 && bullets.length < 20) {
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y - graphics.height / 2;
bullets.push(bullet);
game.addChild(bullet);
self.shootCooldown = playerFireRate;
LK.getSound('shoot').play();
}
};
self.useSpecialSkill = function () {
if (self.skillCooldown <= 0 && (enemies.length > 0 || bossActive)) {
var missileCount = playerMissileCount;
var baseDelay = 0;
for (var i = 0; i < missileCount; i++) {
// Create missile with delay for sequence effect
LK.setTimeout(function (index) {
return function () {
var missile = new HomingMissile();
// Spread missiles horizontally
var spreadWidth = 200;
var xOffset = (index - (missileCount - 1) / 2) * (spreadWidth / (missileCount - 1));
missile.x = self.x + xOffset;
missile.y = self.y - graphics.height / 2;
missiles.push(missile);
game.addChild(missile);
LK.getSound('missile_launch').play();
};
}(i), baseDelay + i * 150); // 150ms delay between each missile
}
self.skillCooldown = self.skillMaxCooldown;
LK.effects.flashObject(self, 0x00ffff, 300);
}
};
self.takeDamage = function () {
if (self.invulnerable <= 0) {
self.health--;
self.invulnerable = 120;
LK.effects.flashObject(self, 0xff0000, 500);
if (self.health <= 0) {
LK.showGameOver();
}
}
};
return self;
});
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -8;
self.update = function () {
self.y += self.speed;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedY = 3;
self.bobOffset = Math.random() * Math.PI * 2;
self.startY = 0;
self.update = function () {
self.y += self.speedY;
// Apply isometric movement effect
self.x += self.speedY * 0.5;
// Bobbing animation
graphics.y = Math.sin(LK.ticks * 0.1 + self.bobOffset) * 10;
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedY = 1 + Math.random() * 3;
self.twinkleSpeed = 0.02 + Math.random() * 0.08;
self.twinkleOffset = Math.random() * Math.PI * 2;
self.update = function () {
self.y += self.speedY;
// Twinkling effect
graphics.alpha = 0.3 + Math.sin(LK.ticks * self.twinkleSpeed + self.twinkleOffset) * 0.7;
graphics.scaleX = graphics.scaleY = 0.5 + Math.sin(LK.ticks * self.twinkleSpeed + self.twinkleOffset) * 0.5;
// Reset position when off screen
if (self.y > 2800) {
self.y = -50;
self.x = Math.random() * 2048;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Menu system variables
var gameStarted = false;
var mainMenu = null;
var logoDisplay = null;
var playButton = null;
var subtitle = null;
var menuBackground = null;
// Game variables
var player;
var enemies = [];
var bullets = [];
var missiles = [];
var powerups = [];
var enemyBullets = [];
var bossBullets = [];
var boss = null;
var bossActive = false;
var enemiesKilledInWave = 0;
var enemySpawnTimer = 0;
var powerupSpawnTimer = 0;
var gameSpeed = 1;
var waveLevel = 1;
var lastHealthDisplay = 3;
var bossHealthBar = null;
var bossHealthBarBg = null;
var bossHealthText = null;
// Upgrade system variables
var upgradeMenuActive = false;
var upgradeOption1 = null;
var upgradeOption2 = null;
var upgradeBackground = null;
var upgradeTitle = null;
var playerFireRate = 15; // Current fire rate cooldown
var playerMissileCount = 3; // Current missile count
// Wave system variables
var enemiesInCurrentWave = 0;
var enemiesSpawnedInWave = 0;
var maxEnemiesForWave = 5;
var lastBossWaveEnemies = 5;
var waveComplete = false;
// Movement variables
var moveLeft = false;
var moveRight = false;
var moveUp = false;
var moveDown = false;
var dragNode = null;
// Performance optimization - object pools
var explosionPool = [];
var fragmentPool = [];
var maxPoolSize = 50;
// Background elements
var stars = [];
// Laser phase variables
var laserPhaseActive = false;
var laserBeam = null;
var laserPhaseTimer = 0;
var laserWarningText = null;
// Create main menu function
function createMainMenu() {
// Create semi-transparent background
menuBackground = LK.getAsset('logo', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 13.6
});
menuBackground.tint = 0x000000;
menuBackground.alpha = 0.8;
menuBackground.x = 1024;
menuBackground.y = 1366;
game.addChild(menuBackground);
// Create game logo
logoDisplay = new Text2('SHOT SHOT IN SPACE', {
size: 150,
fill: 0x00FFFF
});
logoDisplay.anchor.set(0.5, 0.5);
logoDisplay.x = 1024;
logoDisplay.y = 800;
game.addChild(logoDisplay);
// Create subtitle
subtitle = new Text2('Survive endless waves!', {
size: 80,
fill: 0xFFFFFF
});
subtitle.anchor.set(0.5, 0.5);
subtitle.x = 1024;
subtitle.y = 950;
game.addChild(subtitle);
// Create play button
playButton = new Text2('PLAY', {
size: 120,
fill: 0xFFD700
});
playButton.anchor.set(0.5, 0.5);
playButton.x = 1024;
playButton.y = 1400;
playButton.interactive = true;
playButton.down = function () {
startGame();
};
game.addChild(playButton);
// Add pulsing effect to logo
tween(logoDisplay, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 2000,
easing: tween.easeInOut,
loop: true,
reverse: true
});
// Add pulsing effect to play button
tween(playButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1500,
easing: tween.easeInOut,
loop: true,
reverse: true
});
mainMenu = true;
}
// Start game function
function startGame() {
if (!mainMenu) {
return;
}
// Remove menu elements
if (menuBackground) {
menuBackground.destroy();
menuBackground = null;
}
if (logoDisplay) {
logoDisplay.destroy();
logoDisplay = null;
}
if (playButton) {
playButton.destroy();
playButton = null;
}
if (subtitle) {
subtitle.destroy();
subtitle = null;
}
mainMenu = false;
gameStarted = true;
// Flash effect when starting
LK.effects.flashScreen(0x00FFFF, 500);
}
// Create initial background stars
for (var i = 0; i < 100; i++) {
var star = new Star();
star.x = Math.random() * 2048;
star.y = Math.random() * 2732;
stars.push(star);
game.addChild(star);
}
// Show main menu first
createMainMenu();
// Initialize player (but don't show until game starts)
player = new Player();
player.x = 1024;
player.y = 2200;
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0x00FFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120;
scoreTxt.y = 50;
LK.gui.topLeft.addChild(scoreTxt);
// Health display
var healthTxt = new Text2('Health: 3', {
size: 60,
fill: 0xFF0000
});
healthTxt.anchor.set(0, 0);
healthTxt.x = 120;
healthTxt.y = 120;
LK.gui.topLeft.addChild(healthTxt);
// Wave display
var waveTxt = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFF00
});
waveTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(waveTxt);
// Skill display
var skillTxt = new Text2('Skill: Ready', {
size: 60,
fill: 0x00FFFF
});
skillTxt.anchor.set(0.5, 0);
skillTxt.y = 50;
LK.gui.top.addChild(skillTxt);
// Touch controls
game.down = function (x, y, obj) {
if (!gameStarted || mainMenu) {
return;
}
dragNode = true;
player.shoot();
// Immediate response - move player closer to touch point instantly
var gamePos = game.toLocal({
x: x,
y: y
});
var targetX = Math.max(50, Math.min(1998, gamePos.x));
var targetY = Math.max(100, Math.min(2650, gamePos.y));
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Instant partial movement for immediate feedback
if (distance > 30) {
var instantSpeed = 25;
player.x += dx / distance * instantSpeed;
player.y += dy / distance * instantSpeed;
}
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
dragNode = null;
};
function handleMove(x, y, obj) {
if (!gameStarted || mainMenu) {
return;
}
if (dragNode) {
// Convert screen coordinates to game coordinates
var gamePos = game.toLocal({
x: x,
y: y
});
// Smooth movement towards touch position with larger movement area
var targetX = Math.max(50, Math.min(1998, gamePos.x));
var targetY = Math.max(100, Math.min(2650, gamePos.y));
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
var speed = 12;
player.x += dx / distance * speed;
player.y += dy / distance * speed;
} else {
// When very close to target, snap to position for precise control
player.x = targetX;
player.y = targetY;
}
}
}
game.move = handleMove;
// Spawn enemy function
function spawnEnemy() {
var enemy = new Enemy();
// Random spawn position from edges with isometric consideration
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -50;
enemy.speedY = 3 + Math.random() * 3;
break;
case 1:
// Right
enemy.x = 2098;
enemy.y = Math.random() * 1000;
enemy.speedX = -3 - Math.random() * 3;
enemy.speedY = 2 + Math.random() * 2;
break;
case 2:
// Left
enemy.x = -50;
enemy.y = Math.random() * 1000;
enemy.speedX = 3 + Math.random() * 3;
enemy.speedY = 2 + Math.random() * 2;
break;
case 3:
// Top-left diagonal (isometric style)
enemy.x = -50;
enemy.y = -50;
enemy.speedX = 4 + Math.random() * 3;
enemy.speedY = 4 + Math.random() * 3;
break;
}
// Apply game speed multiplier
enemy.speedX *= gameSpeed;
enemy.speedY *= gameSpeed;
enemies.push(enemy);
game.addChild(enemy);
}
// Spawn powerup function
function spawnPowerup() {
var powerup = new PowerUp();
powerup.x = Math.random() * 1800 + 124;
powerup.y = -50;
powerup.startY = powerup.y;
powerups.push(powerup);
game.addChild(powerup);
}
// Optimized explosion function with pooling
function createExplosion(x, y, count, scale, duration) {
count = Math.min(count || 5, 8); // Limit explosion count
for (var i = 0; i < count; i++) {
var explosion;
if (explosionPool.length > 0) {
explosion = explosionPool.pop();
explosion.alpha = 1;
explosion.scaleX = scale || 0.4;
explosion.scaleY = scale || 0.4;
explosion.rotation = 0;
} else {
explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale || 0.4,
scaleY: scale || 0.4
});
}
explosion.x = x + (Math.random() - 0.5) * 40;
explosion.y = y + (Math.random() - 0.5) * 40;
game.addChild(explosion);
var angle = i / count * Math.PI * 2;
var distance = 30 + Math.random() * 20;
var targetX = explosion.x + Math.cos(angle) * distance;
var targetY = explosion.y + Math.sin(angle) * distance;
tween(explosion, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: (scale || 0.4) * 1.5,
scaleY: (scale || 0.4) * 1.5,
rotation: Math.random() * Math.PI * 2
}, {
duration: duration || 400,
easing: tween.easeOut,
onFinish: function (exp) {
return function () {
if (exp.parent) {
exp.parent.removeChild(exp);
if (explosionPool.length < maxPoolSize) {
explosionPool.push(exp);
}
}
};
}(explosion)
});
}
}
// Optimized fragment function with pooling
function createFragments(x, y, count, assetId, scale) {
count = Math.min(count || 4, 6); // Limit fragment count
for (var i = 0; i < count; i++) {
var fragment;
if (fragmentPool.length > 0) {
fragment = fragmentPool.pop();
fragment.alpha = 1;
fragment.scaleX = scale || 0.2;
fragment.scaleY = scale || 0.2;
fragment.rotation = 0;
} else {
fragment = LK.getAsset(assetId || 'enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale || 0.2,
scaleY: scale || 0.2
});
}
fragment.x = x + (Math.random() - 0.5) * 20;
fragment.y = y + (Math.random() - 0.5) * 20;
game.addChild(fragment);
var angle = i / count * Math.PI * 2;
var distance = 40 + Math.random() * 30;
var targetX = fragment.x + Math.cos(angle) * distance;
var targetY = fragment.y + Math.sin(angle) * distance;
tween(fragment, {
x: targetX,
y: targetY,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function (frag) {
return function () {
if (frag.parent) {
frag.parent.removeChild(frag);
if (fragmentPool.length < maxPoolSize) {
fragmentPool.push(frag);
}
}
};
}(fragment)
});
}
}
// Create upgrade selection menu
function createUpgradeMenu() {
upgradeMenuActive = true;
// Create semi-transparent background
upgradeBackground = LK.getAsset('healthbar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 25,
scaleY: 35
});
upgradeBackground.tint = 0x000000;
upgradeBackground.alpha = 0.8;
upgradeBackground.x = 1024;
upgradeBackground.y = 1366;
game.addChild(upgradeBackground);
// Create title
upgradeTitle = new Text2('CHOOSE UPGRADE', {
size: 120,
fill: 0xFFD700
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.x = 1024;
upgradeTitle.y = 800;
game.addChild(upgradeTitle);
// Create option 1: Fire Rate
upgradeOption1 = new Text2('+1 FIRE RATE', {
size: 80,
fill: 0x00FFFF
});
upgradeOption1.anchor.set(0.5, 0.5);
upgradeOption1.x = 700;
upgradeOption1.y = 1200;
upgradeOption1.interactive = true;
upgradeOption1.down = function () {
selectFireRateUpgrade();
};
game.addChild(upgradeOption1);
// Create option 2: Missile Count
upgradeOption2 = new Text2('+1 MISSILE', {
size: 80,
fill: 0x00FFFF
});
upgradeOption2.anchor.set(0.5, 0.5);
upgradeOption2.x = 1348;
upgradeOption2.y = 1200;
upgradeOption2.interactive = true;
upgradeOption2.down = function () {
selectMissileUpgrade();
};
game.addChild(upgradeOption2);
// Add visual effects
tween(upgradeTitle, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
loop: true,
reverse: true
});
tween(upgradeOption1, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 800,
easing: tween.easeInOut,
loop: true,
reverse: true
});
tween(upgradeOption2, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 800,
easing: tween.easeInOut,
loop: true,
reverse: true,
delay: 400
});
}
// Select fire rate upgrade
function selectFireRateUpgrade() {
if (!upgradeMenuActive) {
return;
}
playerFireRate = Math.max(5, playerFireRate - 3); // Increase fire rate (decrease cooldown)
closeUpgradeMenu();
// Show confirmation effect
LK.effects.flashScreen(0x00FFFF, 500);
}
// Select missile upgrade
function selectMissileUpgrade() {
if (!upgradeMenuActive) {
return;
}
playerMissileCount = Math.min(7, playerMissileCount + 1); // Increase missile count (max 7)
closeUpgradeMenu();
// Show confirmation effect
LK.effects.flashScreen(0xFF6600, 500);
}
// Close upgrade menu and resume game
function closeUpgradeMenu() {
upgradeMenuActive = false;
if (upgradeBackground) {
upgradeBackground.destroy();
upgradeBackground = null;
}
if (upgradeTitle) {
upgradeTitle.destroy();
upgradeTitle = null;
}
if (upgradeOption1) {
upgradeOption1.destroy();
upgradeOption1 = null;
}
if (upgradeOption2) {
upgradeOption2.destroy();
upgradeOption2 = null;
}
}
// Main game update
game.update = function () {
// Skip game updates when in menu or upgrade menu is active
if (mainMenu || upgradeMenuActive) {
return;
}
// Add player to game when first started
if (gameStarted && !player.parent) {
game.addChild(player);
}
// Update background elements
for (var i = 0; i < stars.length; i++) {
// Stars update automatically through their update method
}
// Auto-shoot
if (LK.ticks % 20 === 0) {
player.shoot();
}
// Auto-use special skill
if (player.skillCooldown <= 0 && (enemies.length > 0 || bossActive)) {
player.useSpecialSkill();
}
// Update skill display
if (player.skillCooldown > 0) {
skillTxt.setText('Skill: ' + Math.ceil(player.skillCooldown / 60) + 's');
skillTxt.fill = 0x888888;
} else {
skillTxt.setText('Skill: Ready');
skillTxt.fill = 0x00FFFF;
}
// Calculate max enemies for current wave
if (waveLevel % 5 === 0) {
// Boss wave (every 5th wave)
maxEnemiesForWave = 0;
} else {
// Regular wave: 5 * wave number, but after boss waves add bonus
var waveInCycle = waveLevel % 5;
if (waveLevel > 5) {
// After first boss, add bonus based on last boss wave
maxEnemiesForWave = (5 + lastBossWaveEnemies) * waveInCycle;
} else {
// First 4 waves: simple 5 * wave
maxEnemiesForWave = 5 * waveLevel;
}
}
// Boss spawning logic for waves 5, 10, 15, etc.
if (waveLevel % 5 === 0 && !bossActive && enemies.length === 0 && enemiesSpawnedInWave >= maxEnemiesForWave) {
// Kill all remaining enemies and spawn boss
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Remember enemy count from this boss wave for next cycle
lastBossWaveEnemies = enemiesKilledInWave;
// Spawn boss
boss = new Boss();
boss.x = 1024;
boss.y = 200;
game.addChild(boss);
bossActive = true;
enemiesKilledInWave = 0;
// Create boss health bar (full screen width)
bossHealthBarBg = LK.getAsset('healthbar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 20,
scaleY: 0.8
});
bossHealthBarBg.tint = 0x333333;
bossHealthBarBg.x = 1024;
bossHealthBarBg.y = 350;
game.addChild(bossHealthBarBg);
bossHealthBar = LK.getAsset('healthbar', {
anchorX: 0.0,
anchorY: 0.5,
scaleX: 20,
scaleY: 0.6
});
bossHealthBar.tint = 0xff0000;
bossHealthBar.x = 24;
bossHealthBar.y = 350;
game.addChild(bossHealthBar);
// Create boss health text display
bossHealthText = new Text2(boss.health + '/' + boss.maxHealth, {
size: 80,
fill: 0xFFFFFF
});
bossHealthText.anchor.set(0.5, 0.5);
bossHealthText.x = 1024;
bossHealthText.y = 350;
game.addChild(bossHealthText);
// Boss intro effect
LK.effects.flashScreen(0x8800ff, 1000);
var bossTxt = new Text2('BOSS WAVE!', {
size: 150,
fill: 0xFF0000
});
bossTxt.anchor.set(0.5, 0.5);
bossTxt.x = 1024;
bossTxt.y = 1366;
bossTxt.alpha = 0;
bossTxt.scaleX = 0.1;
bossTxt.scaleY = 0.1;
game.addChild(bossTxt);
tween(bossTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(bossTxt, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (bossTxt.parent) {
bossTxt.destroy();
}
}
});
}
});
} else if (!bossActive && enemiesSpawnedInWave < maxEnemiesForWave) {
// Normal enemy spawning - only spawn if we haven't reached the wave limit
enemySpawnTimer++;
var spawnRate = Math.max(30 - Math.floor(waveLevel * 2), 10);
if (enemySpawnTimer >= spawnRate && enemies.length < 15) {
spawnEnemy();
enemiesSpawnedInWave++;
enemySpawnTimer = 0;
}
}
// Powerup spawning
powerupSpawnTimer++;
if (powerupSpawnTimer >= 600) {
// Every 10 seconds
spawnPowerup();
powerupSpawnTimer = 0;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.lastY === undefined) {
bullet.lastY = bullet.y;
}
// Remove bullets that go off screen (expanded bounds check)
if (bullet.y < -100 || bullet.y > 2800 || bullet.x < -100 || bullet.x > 2148) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-enemy collisions
var hitEnemy = false;
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
// Enemy hit
LK.setScore(LK.getScore() + 10);
scoreTxt.setText('Score: ' + LK.getScore());
enemiesKilledInWave++;
// Create optimized effects
var enemy = enemies[j];
createFragments(enemy.x, enemy.y, 4, 'enemy', 0.2);
enemy.destroy();
enemies.splice(j, 1);
// Destroy bullet
bullet.destroy();
bullets.splice(i, 1);
LK.getSound('hit').play();
hitEnemy = true;
break;
}
}
// Check bullet-boss collision
if (bossActive && boss && bullet.intersects(boss)) {
boss.takeDamage();
// No score gain from hitting boss with bullets
bullet.destroy();
bullets.splice(i, 1);
LK.getSound('hit').play();
hitEnemy = true;
// Check if boss is dead
if (boss.health <= 0) {
// Boss death effect
createExplosion(boss.x, boss.y, 8, 0.6, 600);
LK.setScore(LK.getScore() + 500);
scoreTxt.setText('Score: ' + LK.getScore());
// Remove boss health bar
if (bossHealthBar) {
bossHealthBar.destroy();
bossHealthBar = null;
}
if (bossHealthBarBg) {
bossHealthBarBg.destroy();
bossHealthBarBg = null;
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
boss.destroy();
boss = null;
bossActive = false;
// Show upgrade selection menu
createUpgradeMenu();
}
}
if (!hitEnemy) {
bullet.lastY = bullet.y;
}
}
// Update missiles
for (var i = missiles.length - 1; i >= 0; i--) {
var missile = missiles[i];
// Remove missiles that expire or go off screen
if (missile.life <= 0 || missile.y < -100 || missile.y > 2800 || missile.x < -100 || missile.x > 2148) {
// Create optimized explosion effect
createExplosion(missile.x, missile.y, 4, 0.3, 300);
missile.destroy();
missiles.splice(i, 1);
continue;
}
// Check missile-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (missile.intersects(enemies[j])) {
// Enemy hit by missile
LK.setScore(LK.getScore() + 15);
scoreTxt.setText('Score: ' + LK.getScore());
enemiesKilledInWave++;
// Create optimized explosion and fragment effects
var enemy = enemies[j];
createExplosion(missile.x, missile.y, 5, 0.4, 400);
createFragments(enemy.x, enemy.y, 4, 'enemy', 0.15);
enemy.destroy();
enemies.splice(j, 1);
missile.destroy();
missiles.splice(i, 1);
LK.getSound('missile_hit').play();
break;
}
}
// Check missile-boss collision
if (bossActive && boss && missile.intersects(boss)) {
boss.takeDamage();
// No score gain from hitting boss with missiles
// Create explosion effect
var explosionCount = 8;
for (var e = 0; e < explosionCount; e++) {
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
explosion.x = missile.x + (Math.random() - 0.5) * 50;
explosion.y = missile.y + (Math.random() - 0.5) * 50;
game.addChild(explosion);
var angle = e / explosionCount * Math.PI * 2;
var distance = 60 + Math.random() * 50;
var targetX = explosion.x + Math.cos(angle) * distance;
var targetY = explosion.y + Math.sin(angle) * distance;
tween(explosion, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 1.0,
scaleY: 1.0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (explosion.parent) {
explosion.destroy();
}
}
});
}
missile.destroy();
missiles.splice(i, 1);
LK.getSound('missile_hit').play();
// Check if boss is dead
if (boss.health <= 0) {
// Boss death effect
var explosionCount = 12;
for (var e = 0; e < explosionCount; e++) {
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
explosion.x = boss.x + (Math.random() - 0.5) * 100;
explosion.y = boss.y + (Math.random() - 0.5) * 100;
game.addChild(explosion);
var angle = e / explosionCount * Math.PI * 2;
var distance = 80 + Math.random() * 60;
var targetX = explosion.x + Math.cos(angle) * distance;
var targetY = explosion.y + Math.sin(angle) * distance;
tween(explosion, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 1.2,
scaleY: 1.2,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (explosion.parent) {
explosion.destroy();
}
}
});
}
LK.setScore(LK.getScore() + 500);
scoreTxt.setText('Score: ' + LK.getScore());
// Remove boss health bar
if (bossHealthBar) {
bossHealthBar.destroy();
bossHealthBar = null;
}
if (bossHealthBarBg) {
bossHealthBarBg.destroy();
bossHealthBarBg = null;
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
boss.destroy();
boss = null;
bossActive = false;
// Show upgrade selection menu
createUpgradeMenu();
}
break;
}
}
// Update enemies (reduce collision check frequency)
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Remove enemies that go off screen
if (enemy.y > 2800 || enemy.x < -100 || enemy.x > 2148) {
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check player-enemy collision (only every 3rd frame for performance)
// Use smaller hitbox for player by checking distance instead of full intersects
if (LK.ticks % 3 === 0) {
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Reduced hitbox: use 60% of normal collision area
if (distance < 60) {
player.takeDamage();
// Create optimized fragmentation effect
createFragments(enemy.x, enemy.y, 4, 'enemy', 0.2);
enemy.destroy();
enemies.splice(i, 1);
}
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var enemyBullet = enemyBullets[i];
// Remove bullets that go off screen
if (enemyBullet.y > 2800 || enemyBullet.x < -100 || enemyBullet.x > 2148 || enemyBullet.y < -100) {
enemyBullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check enemy bullet-player collision with smaller player hitbox
var dx = enemyBullet.x - player.x;
var dy = enemyBullet.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Reduced hitbox: use 50% of normal collision area for bullets
if (distance < 50) {
player.takeDamage();
enemyBullet.destroy();
enemyBullets.splice(i, 1);
}
}
// Update boss bullets
for (var i = bossBullets.length - 1; i >= 0; i--) {
var bossBullet = bossBullets[i];
// Remove bullets that go off screen
if (bossBullet.y > 2800 || bossBullet.x < -100 || bossBullet.x > 2148 || bossBullet.y < -100) {
bossBullet.destroy();
bossBullets.splice(i, 1);
continue;
}
// Check boss bullet-player collision with smaller player hitbox
var dx = bossBullet.x - player.x;
var dy = bossBullet.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Reduced hitbox: use 50% of normal collision area for bullets
if (distance < 50) {
player.takeDamage();
bossBullet.destroy();
bossBullets.splice(i, 1);
}
}
// Update boss if active
if (bossActive && boss) {
// Update boss health bar
if (bossHealthBar && bossHealthBarBg && bossHealthText) {
var healthRatio = boss.health / boss.maxHealth;
bossHealthBar.scaleX = healthRatio * 20;
// Update health text
bossHealthText.setText(boss.health + '/' + boss.maxHealth);
// Change color based on health
if (healthRatio > 0.6) {
bossHealthBar.tint = 0x00ff00; // Green
} else if (healthRatio > 0.3) {
bossHealthBar.tint = 0xffff00; // Yellow
} else {
bossHealthBar.tint = 0xff0000; // Red
}
}
// Check boss-player collision with smaller player hitbox
var dx = boss.x - player.x;
var dy = boss.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Reduced hitbox: use 70% of normal collision area for boss
if (distance < 70) {
player.takeDamage();
}
}
// Update powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var powerup = powerups[i];
// Remove powerups that go off screen
if (powerup.y > 2800) {
powerup.destroy();
powerups.splice(i, 1);
continue;
}
// Check player-powerup collision
if (powerup.intersects(player)) {
player.health = Math.min(player.health + 1, player.maxHealth);
LK.getSound('powerup').play();
LK.effects.flashObject(player, 0x00ff00, 300);
// Create red spread effect
var spreadEffectCount = 8;
for (var s = 0; s < spreadEffectCount; s++) {
var redSpread = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
redSpread.tint = 0xff0000; // Red color
redSpread.x = powerup.x;
redSpread.y = powerup.y;
redSpread.alpha = 0.8;
game.addChild(redSpread);
var angle = s / spreadEffectCount * Math.PI * 2;
var distance = 150 + Math.random() * 100;
var targetX = redSpread.x + Math.cos(angle) * distance;
var targetY = redSpread.y + Math.sin(angle) * distance;
tween(redSpread, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 1.2,
scaleY: 1.2,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function (spread) {
return function () {
if (spread.parent) {
spread.destroy();
}
};
}(redSpread)
});
}
powerup.destroy();
powerups.splice(i, 1);
}
}
// Update health display
if (player.health !== lastHealthDisplay) {
healthTxt.setText('Health: ' + player.health);
lastHealthDisplay = player.health;
}
// Check wave completion based on enemy count
if (!bossActive && enemies.length === 0 && enemiesSpawnedInWave >= maxEnemiesForWave && !waveComplete) {
// Wave completed - all enemies spawned and killed
waveComplete = true;
// Advance to next wave
waveLevel++;
gameSpeed += 0.1;
waveTxt.setText('Wave: ' + waveLevel);
// Reset wave tracking variables
enemiesSpawnedInWave = 0;
enemiesKilledInWave = 0;
// Create stylish "WAVE COMPLETED" text
var waveCompletedTxt = new Text2('WAVE COMPLETED', {
size: 120,
fill: 0xFFD700
});
waveCompletedTxt.anchor.set(0.5, 0.5);
waveCompletedTxt.x = 1024;
waveCompletedTxt.y = 1366;
waveCompletedTxt.alpha = 0;
waveCompletedTxt.scaleX = 0.1;
waveCompletedTxt.scaleY = 0.1;
game.addChild(waveCompletedTxt);
// Animate the text with a stylish effect
tween(waveCompletedTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Hold for a moment then fade out
tween(waveCompletedTxt, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
if (waveCompletedTxt.parent) {
waveCompletedTxt.destroy();
}
// Allow next wave to start
waveComplete = false;
}
});
}
});
}
// Boss wave completion - start laser phase
if (bossActive && !boss && !laserPhaseActive) {
// Start laser phase after boss defeat
laserPhaseActive = true;
laserPhaseTimer = 0;
// Create warning text
laserWarningText = new Text2('DODGE THE LASER!', {
size: 100,
fill: 0xFF0000
});
laserWarningText.anchor.set(0.5, 0.5);
laserWarningText.x = 1024;
laserWarningText.y = 1000;
laserWarningText.alpha = 0;
game.addChild(laserWarningText);
// Animate warning text
tween(laserWarningText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeOut
});
// Create laser beam after short delay
LK.setTimeout(function () {
// Random position and direction
var isHorizontal = Math.random() < 0.5;
laserBeam = new LaserBeam();
laserBeam.isHorizontal = isHorizontal;
if (isHorizontal) {
// Horizontal laser at random Y position
laserBeam.x = 0;
laserBeam.y = 200 + Math.random() * 2000; // Random Y between 200-2200
} else {
// Vertical laser at random X position
laserBeam.x = 200 + Math.random() * 1648; // Random X between 200-1848
laserBeam.y = 1366;
}
game.addChild(laserBeam);
LK.getSound('laser_charge').play();
}, 1500);
}
// Handle laser phase
if (laserPhaseActive) {
laserPhaseTimer++;
// Check laser collision with player
if (laserBeam && laserBeam.checkPlayerCollision(player)) {
// Player hit by laser - game over
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check if laser phase is complete
if (laserBeam && laserBeam.currentPhase === 'done') {
// Laser phase completed successfully
laserPhaseActive = false;
// Clean up laser elements
if (laserBeam) {
laserBeam.destroy();
laserBeam = null;
}
if (laserWarningText) {
laserWarningText.destroy();
laserWarningText = null;
}
// Continue to next wave
waveLevel++;
gameSpeed += 0.1;
waveTxt.setText('Wave: ' + waveLevel);
enemiesSpawnedInWave = 0;
enemiesKilledInWave = 0;
waveComplete = false;
// Success effect
LK.effects.flashScreen(0x00FF00, 500);
var successTxt = new Text2('LASER DODGED!', {
size: 120,
fill: 0x00FF00
});
successTxt.anchor.set(0.5, 0.5);
successTxt.x = 1024;
successTxt.y = 1366;
successTxt.alpha = 0;
game.addChild(successTxt);
tween(successTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(successTxt, {
alpha: 0,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (successTxt.parent) {
successTxt.destroy();
}
}
});
}
});
}
}
// Boss wave completion (moved after laser phase)
if (bossActive && !boss && !laserPhaseActive) {
// This will now only trigger after laser phase is complete
// The wave progression is handled in the laser phase completion above
}
};
uzayda boş boş gezen bir meteor. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
oyuncunun dokunduğu bir can puanı kazandığı bir kalp. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
roket mermisi dik bir şekilde 90 derece sci-Fİ. In-Game asset. 2d. High contrast. No shadows
space shooter oyununda ki uzay gemisinin aynısı. In-Game asset. 2d. High contrast. No shadows
ProjectileSpace shooter oyununda, uzay gemisinin ucundan çıkan, ileriye doğru düz bir hat üzerinde giden ışıklı mermi (projectile) çizer misin? Görsel olarak enerjik, fütüristik ve renkli olabilir. Arka planda uzay boşluğu olabilir. transparent. In-Game asset. 2d. High contrast. No shadows