User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.moveTween = tween.to(player, {' Line Number: 470
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.moveTween = tween.to(player, {' Line Number: 470
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.moveTween = tween.to(player, {' Line Number: 470
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.moveTween = tween.to(player, {' Line Number: 470
User prompt
Now it is way too slow. Make it speed up when far away from the mouse, and slow down when near the mouse. It should be fast enough for the player to make quick escapes. The fatrther the mouse, the more the ship speed.
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.moveTween = tween.to(player, {' Line Number: 458 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.moveTween = tween.to(player, {' Line Number: 458
User prompt
No make it still move slowly while dragging but slower than before, but keep the delay instantaneous for the ship.
User prompt
The takes time to start moving, make that instantaneous.
User prompt
Make it even slower, and also if the ship is not where the mouse is at any time and the mouse is down, the ship immediately tries to stay on top of the mouse always.
User prompt
It still moves very fast, make it move much slower.
User prompt
Make it so that when the mouse is clicked/screen is touched in a specific spot, the Player's Ship moves towards the mouse instead of teleporting to it. This movement should have realistic physics. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Cosmic Defender: Space Assault
Initial prompt
Lets make a 2d Top down shooter game
/**** * 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