User prompt
Please fix the bug: 'ReferenceError: Crystal is not defined' in or related to this line: 'var crystal = new Crystal();' Line Number: 317
User prompt
Please fix the bug: 'ReferenceError: Laser is not defined' in or related to this line: 'var laser = new Laser();' Line Number: 437
User prompt
Please fix the bug: 'ReferenceError: Alien is not defined' in or related to this line: 'var alien = new Alien();' Line Number: 425
User prompt
Please fix the bug: 'Player is not defined' in or related to this line: 'player = new Player();' Line Number: 112
User prompt
her dalga sonrası büyük bir boss gelsin
User prompt
enemies come from top to bottom and after each wave there will be 5 more enemies for the next wave
User prompt
Let the boxes falling from the enemy come towards us
User prompt
Let there be 3 roads in front of us and these enemies will come at us randomly from these roads.
User prompt
Sets the enemies to come from the opposite side of the character
Code edit (1 edits merged)
Please save this source code
User prompt
Crystal Defense: Alien Waves
Initial prompt
You are a man You can move left and right on the screen. You move with your finger or A/D keys. Aliens are coming Aliens are walking in waves from the right of the screen. They are trying to reach you. You fire automatically When the enemy approaches, your character shoots lasers at himself. Just get in a good position. When you shoot the enemies, the crystal falls You cannot pass over them. You can increase the limits with the crystal once. A new wave starts every 10 enemies The characteristics of the waves, the enemies are faster, stronger. A bonus box drops every 3 waves Go to the box → opens → gives you a random power. Objective: Stay alive as long as you can! The more waves you pass, the better.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Alien class var Alien = Container.expand(function () { var self = Container.call(this); // Attach a default sprite, but allow it to be replaced after construction var alienSprite = self.attachAsset('alien', { anchorX: 0.5, anchorY: 0.5 }); self.width = alienSprite.width; self.height = alienSprite.height; self.speed = 2.5; self.hp = 1; self.wave = 1; self.lastX = self.x; self.lastY = self.y; self.lastIntersecting = false; self.isDead = false; self.update = function () { self.lastY = self.y; self.y += self.speed; }; return self; }); // Bonus box class var BonusBox = Container.expand(function () { var self = Container.call(this); var bonusSprite = self.attachAsset('bonus', { anchorX: 0.5, anchorY: 0.5 }); self.width = bonusSprite.width; self.height = bonusSprite.height; self.lastY = self.y; self.lastIntersecting = false; self.type = null; // Set on spawn self.fallSpeed = 10; // Speed at which the bonus moves toward the player self.update = function () { // Move toward the player's current position if (typeof player !== "undefined" && player !== null) { var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 2) { // Normalize and move toward player self.x += dx / dist * self.fallSpeed; self.y += dy / dist * self.fallSpeed; } } }; return self; }); // Boss class var Boss = Container.expand(function () { var self = Container.call(this); var bossSprite = self.attachAsset('alien4', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); self.width = bossSprite.width * 2; self.height = bossSprite.height * 2; self.speed = 4; // Use global bossBaseHp for initial boss health self.hp = typeof bossBaseHp !== "undefined" ? bossBaseHp : 10; self.lastY = self.y; self.lastIntersecting = false; self.isDead = false; self.update = function () { self.lastY = self.y; self.y += self.speed; }; return self; }); // Crystal class var Crystal = Container.expand(function () { var self = Container.call(this); var crystalSprite = self.attachAsset('crystal', { anchorX: 0.5, anchorY: 0.5 }); self.width = crystalSprite.width; self.height = crystalSprite.height; self.lastY = self.y; self.lastIntersecting = false; self.fallSpeed = 12; // Speed at which the crystal falls toward the player self.update = function () { // Move toward the player's current position if (typeof player !== "undefined" && player !== null) { var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 2) { // Normalize and move toward player self.x += dx / dist * self.fallSpeed; self.y += dy / dist * self.fallSpeed; } } }; return self; }); // Laser class var Laser = Container.expand(function () { var self = Container.call(this); var laserSprite = self.attachAsset('laser', { anchorX: 0.5, anchorY: 1 }); self.width = laserSprite.width; self.height = laserSprite.height; self.speed = 32; self.pierce = false; self.lastY = self.y; self.lastIntersecting = false; self.update = function () { self.y -= self.speed; }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); var playerSprite = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.width = playerSprite.width; self.height = playerSprite.height; self.shootCooldown = 0; self.moveSpeed = 32; // px per tick when dragged self.leftLimit = 0 + self.width / 2 + 40; self.rightLimit = 2048 - self.width / 2 - 40; self.powered = false; self.powerTimer = 0; self.powerType = null; // For powerup visuals self.setPower = function (type, duration) { self.powered = true; self.powerType = type; self.powerTimer = duration; if (type === 'rapid') { playerSprite.tint = 0xffe100; } else if (type === 'pierce') { playerSprite.tint = 0x00ffb0; } else if (type === 'shield') { playerSprite.tint = 0x00eaff; } }; self.clearPower = function () { self.powered = false; self.powerType = null; self.powerTimer = 0; playerSprite.tint = 0xffffff; }; self.update = function () { if (self.powered) { self.powerTimer--; if (self.powerTimer <= 0) { self.clearPower(); } } }; return self; }); // Star class for background stars var Star = Container.expand(function () { var self = Container.call(this); // Use a white ellipse as a star, randomize size (smaller, more variety) // Reduced min/max size for smaller stars var size = randInt(1, 2); var starSprite = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: size / 120, scaleY: size / 120, tint: 0xffffff }); self.starSprite = starSprite; // Store for tinting self.width = size; self.height = size; // More parallax: slower stars are smaller, faster are bigger // Speed: 0.2 to 2.2, with more slow stars var speedBase = Math.pow(Math.random(), 2.2); // bias toward 0 self.speed = 0.2 + speedBase * 2.0; self.alpha = 0.4 + Math.random() * 0.6; self.setColor = function (color) { if (self.starSprite) self.starSprite.tint = color; }; self.update = function () { // Only update if star is visible or about to be visible if (self.y > -self.height && self.y < GAME_HEIGHT + self.height * 2) { self.y += self.speed; } else { self.y = -self.height; self.x = randInt(0, GAME_WIDTH); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Music // Sound effects // Bonus box // Crystal (collectible) // Player bullet (laser) // Alien (enemy) // Player // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; // --- Starfield background --- // Star color palette (cycle every 2 waves) var STAR_COLORS = [0xffffff, // white 0xffe100, // yellow 0x00eaff, // cyan 0xff00cc, // magenta 0x00ffb0, // green 0xffa500, // orange 0xAAFFFF, // light blue 0xff0000 // red ]; function getStarColorForWave(wave) { var idx = Math.floor((wave - 1) / 2) % STAR_COLORS.length; return STAR_COLORS[idx]; } var NUM_STARS = 320; var stars = []; for (var i = 0; i < NUM_STARS; i++) { var star = new Star(); star.x = randInt(0, GAME_WIDTH); star.y = randInt(0, GAME_HEIGHT); stars.push(star); game.addChild(star); } var GROUND_Y = GAME_HEIGHT - 220; var PLAYER_START_X = GAME_WIDTH / 2; var PLAYER_START_Y = GROUND_Y; var PLAYER_MIN_X = 160 + 40; var PLAYER_MAX_X = GAME_WIDTH - 160 - 40; var ENEMY_START_X_MIN = 160; var ENEMY_START_X_MAX = GAME_WIDTH - 160; // Define Y spawn range for enemies (top margin to bottom margin for bonus box spawn) var ENEMY_START_Y_MIN = 0; var ENEMY_START_Y_MAX = GAME_HEIGHT - 400; // Define 3 fixed road X positions for aliens to spawn and move along (top to bottom) var ROAD_X_POSITIONS = [ENEMY_START_X_MIN + Math.floor((ENEMY_START_X_MAX - ENEMY_START_X_MIN) * 0.2), ENEMY_START_X_MIN + Math.floor((ENEMY_START_X_MAX - ENEMY_START_X_MIN) * 0.5), ENEMY_START_X_MIN + Math.floor((ENEMY_START_X_MAX - ENEMY_START_X_MIN) * 0.8)]; var LASER_COOLDOWN = 18; // ticks var LASER_COOLDOWN_RAPID = 6; // Track current firing cooldown, increases as waves progress var currentLaserCooldown = LASER_COOLDOWN; var currentLaserCooldownRapid = LASER_COOLDOWN_RAPID; var CRYSTAL_COLLECT_DIST = 120; var BONUS_COLLECT_DIST = 120; var CRYSTAL_MOVE_STEP = 180; // How much movement area expands per crystal var MAX_CRYSTALS = 8; var WAVE_ENEMY_BASE = 10; var WAVE_ENEMY_INC = 5; // Increase by 5 each wave var WAVE_SPEED_INC = 0.10; var WAVE_HP_INC = 1; var BONUS_WAVE_INTERVAL = 3; var POWERUP_DURATION = 360; // 6 seconds at 60fps // Game state var player = null; var aliens = []; var lasers = []; var crystals = []; var bonuses = []; var boss = null; var bossActive = false; var bossDefeated = false; // Boss health scaling var bossBaseHp = 10; var bossHpInc = 5; var dragNode = null; var dragOffsetX = 0; var dragOffsetY = 0; var lastMoveX = 0; var lastMoveY = 0; var lastAlienSpawnTick = 0; var wave = 1; var enemiesLeft = 0; var enemiesToSpawn = 0; var enemiesKilled = 0; var aliensKilledThisWave = 0; // Track aliens killed in current wave var crystalsCollected = 0; var leftLimit = PLAYER_MIN_X; var rightLimit = PLAYER_MAX_X; var bonusActive = false; var bonusType = null; // Double shot state for player bonus var doubleShotActive = false; var doubleShotTimer = 0; // Triple shot state for player bonus (after 5th wave) var tripleShotActive = false; var tripleShotTimer = 0; // Removed score and scoreTxt, replaced by crystalTxt var waveTxt = null; var powerupTxt = null; var gameOver = false; var youWin = false; // GUI // Crystal icon in upper left (avoid top left 100x100 for menu) // Crystal icon and count, aligned and sized together var crystalIcon = LK.getAsset('crystal', { anchorX: 0, anchorY: 0.5 }); crystalIcon.x = 20; crystalIcon.y = 160 + 40; // center of 80px icon at 160+40=200 crystalIcon.width = 100; crystalIcon.height = 100; LK.gui.topLeft.addChild(crystalIcon); // Crystal count text, vertically centered with icon, same height var crystalTxt = new Text2('0', { size: 100, fill: 0x00eaff }); crystalTxt.anchor.set(0, 0.5); crystalTxt.x = crystalIcon.x + crystalIcon.width + 24; crystalTxt.y = crystalIcon.y; LK.gui.topLeft.addChild(crystalTxt); waveTxt = new Text2('Wave 1', { size: 80, fill: 0xAAFFFF }); waveTxt.anchor.set(0.5, 0); LK.gui.top.addChild(waveTxt); waveTxt.y = 120; powerupTxt = new Text2('', { size: 70, fill: 0xFFE100 }); powerupTxt.anchor.set(0.5, 0); LK.gui.top.addChild(powerupTxt); powerupTxt.y = 220; // Start music LK.playMusic('bgmusic'); // Spawn player player = new Player(); player.x = PLAYER_START_X; player.y = PLAYER_START_Y; game.addChild(player); // Set initial movement limits leftLimit = player.leftLimit; rightLimit = player.rightLimit; // Start first wave function startWave(waveNum) { wave = waveNum; waveTxt.setText('Wave ' + wave); // Set enemiesToSpawn to 30 for each wave, so wave increases every 30 aliens // After 5th wave, increase number of aliens per wave by 5 for each wave after 5 if (wave > 5) { enemiesToSpawn = 30 + (wave - 5) * 5; } else { enemiesToSpawn = 30; } enemiesLeft = enemiesToSpawn; lastAlienSpawnTick = LK.ticks; aliensKilledThisWave = 0; // Reset for new wave // Change star color every 2 waves // Use the new wave number (after increment) for correct color var starColor = getStarColorForWave(wave); for (var i = 0; i < stars.length; i++) { stars[i].setColor(starColor); } // Reset firing speed to base values every wave, then increase firing speed by 0.15x per wave currentLaserCooldown = Math.max(2, Math.round(LASER_COOLDOWN * Math.pow(0.85, wave - 1))); currentLaserCooldownRapid = Math.max(1, Math.round(LASER_COOLDOWN_RAPID * Math.pow(0.85, wave - 1))); // Increase boss health after each wave if (wave > 1) { bossBaseHp += bossHpInc; } // Enable double shot after 5th wave if (wave > 5) { doubleShotActive = true; doubleShotTimer = 0; // 0 means infinite duration powerupTxt.setText('Double Shot!'); if (player && player.children && player.children.length > 0) { player.children[0].tint = 0xff0000; } } else if (wave === 5) { // If returning to wave 5, disable double shot doubleShotActive = false; doubleShotTimer = 0; if (player && player.children && player.children.length > 0) { player.children[0].tint = 0xffffff; } powerupTxt.setText(''); } } // Set initial star color var initialStarColor = getStarColorForWave(1); for (var i = 0; i < stars.length; i++) { stars[i].setColor(initialStarColor); } startWave(1); // Utility: clamp function clamp(val, min, max) { if (val < min) return min; if (val > max) return max; return val; } // Utility: random int function randInt(min, max) { return min + Math.floor(Math.random() * (max - min + 1)); } // Utility: random powerup function randomPowerup() { var arr = ['rapid', 'pierce', 'shield']; return arr[randInt(0, arr.length - 1)]; } // Handle movement (dragging) function handleMove(x, y, obj) { if (dragNode === player) { // Clamp to current movement limits var newX = clamp(x, leftLimit, rightLimit); // Prevent player from entering top left 100x100 area (menu) if (newX - player.width / 2 < 100) { newX = 100 + player.width / 2; } player.x = newX; lastMoveX = newX; lastMoveY = player.y; } } game.move = handleMove; game.down = function (x, y, obj) { // Only allow drag if touch/click is on player or below if (y > GROUND_Y - 200) { dragNode = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // Main game update game.update = function () { // Update background stars for (var i = 0; i < stars.length; i++) { stars[i].update(); } if (gameOver || youWin) return; // Player update (powerup timer) player.update(); // Aliens update for (var i = aliens.length - 1; i >= 0; i--) { var alien = aliens[i]; alien.update(); // Check if alien reached bottom edge (player dies) if (alien.y > GAME_HEIGHT + alien.height / 2) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; return; } // Check collision with player (if shielded, destroy alien) if (player.powered && player.powerType === 'shield' && alien.intersects(player)) { LK.getSound('alien_die').play(); LK.effects.flashObject(alien, 0x00eaff, 400); alien.isDead = true; aliens.splice(i, 1); alien.destroy(); continue; } // If not shielded and alien collides with player, game over if ((!player.powered || player.powerType !== 'shield') && alien.intersects(player)) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; return; } } // Boss update if (bossActive && boss) { boss.update(); // Check if boss reached bottom edge (player dies) if (boss.y > GAME_HEIGHT + boss.height / 2) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; return; } // Check collision with player (if shielded, destroy boss) if (player.powered && player.powerType === 'shield' && boss.intersects(player)) { LK.getSound('alien_die').play(); LK.effects.flashObject(boss, 0x00eaff, 400); boss.isDead = true; bossActive = false; bossDefeated = true; boss.destroy(); boss = null; } } // Lasers update for (var i = lasers.length - 1; i >= 0; i--) { var laser = lasers[i]; laser.update(); // Remove if off screen if (laser.y < -100) { lasers.splice(i, 1); laser.destroy(); continue; } // Check collision with aliens var hit = false; for (var j = aliens.length - 1; j >= 0; j--) { var alien = aliens[j]; if (!alien.isDead && laser.intersects(alien)) { alien.hp -= player.powered && player.powerType === 'pierce' ? 2 : 1; if (alien.hp <= 0) { // Alien dies LK.getSound('alien_die').play(); LK.effects.flashObject(alien, 0xff0000, 400); alien.isDead = true; // Drop crystal var crystal = new Crystal(); crystal.x = alien.x; crystal.y = alien.y; crystals.push(crystal); game.addChild(crystal); // Remove alien aliens.splice(j, 1); alien.destroy(); enemiesLeft--; enemiesKilled++; aliensKilledThisWave++; // Every 30 aliens, increase wave by 1 (only if all 30 were killed in this wave) if (aliensKilledThisWave > 0 && aliensKilledThisWave % 30 === 0 && enemiesLeft === 0) { // After each wave, spawn boss bossActive = true; bossDefeated = false; boss = new Boss(); boss.x = GAME_WIDTH / 2; boss.y = -boss.height / 2; game.addChild(boss); // Only start next wave after boss is defeated // Bonus box every BONUS_WAVE_INTERVAL after wave increment (handled after boss defeat) return; // Prevent double wave start if also enemiesLeft==0 } // New wave? if (enemiesLeft <= 0) { // No wave increase here, handled by 30 aliens killed logic } } hit = true; if (!(player.powered && player.powerType === 'pierce')) { break; } } } // Boss hit by laser if (bossActive && boss && laser.intersects(boss)) { boss.hp -= player.powered && player.powerType === 'pierce' ? 2 : 1; if (boss.hp <= 0) { LK.getSound('alien_die').play(); LK.effects.flashObject(boss, 0xff0000, 600); // Drop multiple crystals for (var c = 0; c < 3; c++) { var crystal = new Crystal(); crystal.x = boss.x + randInt(-60, 60); crystal.y = boss.y + randInt(-60, 60); crystals.push(crystal); game.addChild(crystal); } boss.isDead = true; bossActive = false; bossDefeated = true; boss.destroy(); boss = null; // Start next wave startWave(wave + 1); // Bonus box every BONUS_WAVE_INTERVAL after wave increment if ((wave + 1) % BONUS_WAVE_INTERVAL === 0) { var bonus = new BonusBox(); bonus.x = randInt(leftLimit + 100, rightLimit - 100); bonus.y = randInt(ENEMY_START_Y_MIN + 100, ENEMY_START_Y_MAX - 100); bonus.type = randomPowerup(); bonuses.push(bonus); game.addChild(bonus); } } hit = true; } if (hit && !(player.powered && player.powerType === 'pierce')) { lasers.splice(i, 1); laser.destroy(); } } // Crystals update for (var i = crystals.length - 1; i >= 0; i--) { var crystal = crystals[i]; // If player close enough, collect var dx = player.x - crystal.x; var dy = player.y - crystal.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < CRYSTAL_COLLECT_DIST) { LK.getSound('crystal_get').play(); LK.effects.flashObject(crystal, 0x00eaff, 400); crystals.splice(i, 1); crystal.destroy(); // Expand movement area and always update score crystalsCollected++; crystalTxt.setText(crystalsCollected); if (crystalsCollected <= MAX_CRYSTALS) { leftLimit = Math.max(PLAYER_MIN_X - crystalsCollected * CRYSTAL_MOVE_STEP, 80 + player.width / 2); rightLimit = Math.min(PLAYER_MAX_X + crystalsCollected * CRYSTAL_MOVE_STEP, GAME_WIDTH - 80 - player.width / 2); } } } // Bonus boxes update for (var i = bonuses.length - 1; i >= 0; i--) { var bonus = bonuses[i]; var dx = player.x - bonus.x; var dy = player.y - bonus.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < BONUS_COLLECT_DIST) { LK.getSound('bonus_get').play(); LK.effects.flashObject(bonus, 0xffe100, 400); bonuses.splice(i, 1); bonus.destroy(); // After 5th wave, randomly grant either alien slowdown for 5 seconds or triple shot for 5 seconds if (wave > 5) { if (Math.random() < 0.5) { // Alien slowdown: halve alien and boss speed for 5 seconds (300 ticks) powerupTxt.setText('Alien Slowdown!'); for (var j = 0; j < aliens.length; j++) { if (typeof aliens[j].originalSpeed === "undefined") aliens[j].originalSpeed = aliens[j].speed; aliens[j].speed = aliens[j].originalSpeed * 0.5; } if (bossActive && boss) { if (typeof boss.originalSpeed === "undefined") boss.originalSpeed = boss.speed; boss.speed = boss.originalSpeed * 0.5; } // Set timer to restore speed if (typeof alienSlowdownTimer === "undefined") alienSlowdownTimer = 0; alienSlowdownTimer = 300; player.setPower(null, 0); // No player power, just visual } else { // Triple shot: grant for 5 seconds (300 ticks) powerupTxt.setText('Triple Shot!'); tripleShotActive = true; tripleShotTimer = 300; player.setPower(null, 0); // No player power, just visual } } else { // Before or at 5th wave, randomly grant either alien slowdown for 5 seconds or double shot for 5 seconds if (Math.random() < 0.5) { // Alien slowdown: halve alien and boss speed for 5 seconds (300 ticks) powerupTxt.setText('Alien Slowdown!'); for (var j = 0; j < aliens.length; j++) { if (typeof aliens[j].originalSpeed === "undefined") aliens[j].originalSpeed = aliens[j].speed; aliens[j].speed = aliens[j].originalSpeed * 0.5; } if (bossActive && boss) { if (typeof boss.originalSpeed === "undefined") boss.originalSpeed = boss.speed; boss.speed = boss.originalSpeed * 0.5; } // Set timer to restore speed if (typeof alienSlowdownTimer === "undefined") alienSlowdownTimer = 0; alienSlowdownTimer = 300; player.setPower(null, 0); // No player power, just visual } else { // Double shot: always grant for 5 seconds (300 ticks) when bonus is collected powerupTxt.setText('Double Shot!'); doubleShotActive = true; doubleShotTimer = 300; player.setPower(null, 0); // No player power, just visual } } } } // Powerup text timer if (player.powered) { powerupTxt.alpha = 1; } else { if (powerupTxt.alpha > 0) { powerupTxt.alpha -= 0.04; if (powerupTxt.alpha < 0) powerupTxt.alpha = 0; } powerupTxt.setText(''); } // Alien slowdown timer if (typeof alienSlowdownTimer !== "undefined" && alienSlowdownTimer > 0) { alienSlowdownTimer--; if (alienSlowdownTimer <= 0) { // Restore all aliens' and boss's speed, and clear originalSpeed to prevent stacking for (var j = 0; j < aliens.length; j++) { if (typeof aliens[j].originalSpeed !== "undefined") { aliens[j].speed = aliens[j].originalSpeed; delete aliens[j].originalSpeed; } } if (bossActive && boss && typeof boss.originalSpeed !== "undefined") { boss.speed = boss.originalSpeed; delete boss.originalSpeed; } powerupTxt.setText(''); } } // Double shot timer if (doubleShotActive) { // Set player and all lasers to red tint if (player && player.children && player.children.length > 0) { // Player sprite is always first child if (player.children[0].tint !== 0xff0000) player.children[0].tint = 0xff0000; } for (var i = 0; i < lasers.length; i++) { if (lasers[i].children && lasers[i].children.length > 0) { if (lasers[i].children[0].tint !== 0xff0000) lasers[i].children[0].tint = 0xff0000; } } // Only decrement timer if timer is not 0 (0 means infinite double shot) if (doubleShotTimer !== 0) { doubleShotTimer--; if (doubleShotTimer <= 0) { doubleShotActive = false; // Restore player tint (respect powerup color if active) if (player && player.children && player.children.length > 0) { if (player.powered) { if (player.powerType === 'rapid') { player.children[0].tint = 0xffe100; } else if (player.powerType === 'pierce') { player.children[0].tint = 0x00ffb0; } else if (player.powerType === 'shield') { player.children[0].tint = 0x00eaff; } } else { player.children[0].tint = 0xffffff; } } // Restore all lasers to normal tint for (var i = 0; i < lasers.length; i++) { if (lasers[i].children && lasers[i].children.length > 0) { lasers[i].children[0].tint = 0xffffff; } } } } } // Triple shot timer if (tripleShotActive) { // Set player and all lasers to magenta tint if (player && player.children && player.children.length > 0) { if (player.children[0].tint !== 0xff00cc) player.children[0].tint = 0xff00cc; } for (var i = 0; i < lasers.length; i++) { if (lasers[i].children && lasers[i].children.length > 0) { if (lasers[i].children[0].tint !== 0xff00cc) lasers[i].children[0].tint = 0xff00cc; } } if (tripleShotTimer !== 0) { tripleShotTimer--; if (tripleShotTimer <= 0) { tripleShotActive = false; // Restore player tint (respect powerup color if active) if (player && player.children && player.children.length > 0) { if (player.powered) { if (player.powerType === 'rapid') { player.children[0].tint = 0xffe100; } else if (player.powerType === 'pierce') { player.children[0].tint = 0x00ffb0; } else if (player.powerType === 'shield') { player.children[0].tint = 0x00eaff; } } else { player.children[0].tint = 0xffffff; } } // Restore all lasers to normal tint for (var i = 0; i < lasers.length; i++) { if (lasers[i].children && lasers[i].children.length > 0) { lasers[i].children[0].tint = 0xffffff; } } } } } // Player auto-fire var cooldown = player.powered && player.powerType === 'rapid' ? currentLaserCooldownRapid : currentLaserCooldown; if (player.shootCooldown > 0) player.shootCooldown--; // Prevent shooting if game is over or won if (gameOver || youWin) return; // Always fire if cooldown is ready, regardless of nearest alien if (player.shootCooldown <= 0) { // Fire laser(s) if (tripleShotActive) { // Triple shot: fire three lasers, center and two offset var laser1 = new Laser(); laser1.x = player.x - 48; laser1.y = player.y - player.height / 2 + 10; if (player.powered && player.powerType === 'pierce') { laser1.pierce = true; } // Set laser magenta if triple shot if (laser1.children && laser1.children.length > 0) { laser1.children[0].tint = 0xff00cc; } lasers.push(laser1); game.addChild(laser1); var laser2 = new Laser(); laser2.x = player.x; laser2.y = player.y - player.height / 2 + 10; if (player.powered && player.powerType === 'pierce') { laser2.pierce = true; } if (laser2.children && laser2.children.length > 0) { laser2.children[0].tint = 0xff00cc; } lasers.push(laser2); game.addChild(laser2); var laser3 = new Laser(); laser3.x = player.x + 48; laser3.y = player.y - player.height / 2 + 10; if (player.powered && player.powerType === 'pierce') { laser3.pierce = true; } if (laser3.children && laser3.children.length > 0) { laser3.children[0].tint = 0xff00cc; } lasers.push(laser3); game.addChild(laser3); player.shootCooldown = cooldown; LK.getSound('laser_shoot').play(); } else if (doubleShotActive) { // Double shot: fire two lasers, slightly offset var laser1 = new Laser(); laser1.x = player.x - 32; laser1.y = player.y - player.height / 2 + 10; if (player.powered && player.powerType === 'pierce') { laser1.pierce = true; } // Set laser red if double shot if (laser1.children && laser1.children.length > 0) { laser1.children[0].tint = 0xff0000; } lasers.push(laser1); game.addChild(laser1); var laser2 = new Laser(); laser2.x = player.x + 32; laser2.y = player.y - player.height / 2 + 10; if (player.powered && player.powerType === 'pierce') { laser2.pierce = true; } // Set laser red if double shot if (laser2.children && laser2.children.length > 0) { laser2.children[0].tint = 0xff0000; } lasers.push(laser2); game.addChild(laser2); player.shootCooldown = cooldown; LK.getSound('laser_shoot').play(); } else { // Normal single shot var laser = new Laser(); laser.x = player.x; laser.y = player.y - player.height / 2 + 10; if (player.powered && player.powerType === 'pierce') { laser.pierce = true; } // Set laser red if double shot is active if (doubleShotActive && laser.children && laser.children.length > 0) { laser.children[0].tint = 0xff0000; } lasers.push(laser); game.addChild(laser); player.shootCooldown = cooldown; LK.getSound('laser_shoot').play(); } } // Alien image pool for 4 types var ALIEN_IMAGES = ['alien', 'alien2', 'alien3', 'alien4']; // Helper: get alien image(s) for this wave function getAlienImagesForWave(wave) { // Every 3 waves, cycle to next image var idx = Math.floor((wave - 1) / 3) % ALIEN_IMAGES.length; // 30% chance to mix all images in this wave if (Math.random() < 0.3) { // Return all images for mixing return ALIEN_IMAGES.slice(); } // Otherwise, use only the current image for this 3-wave block return [ALIEN_IMAGES[idx]]; } // Track which images to use for this wave if (typeof currentWaveAlienImages === "undefined") { var currentWaveAlienImages = getAlienImagesForWave(wave); } // If wave changed, update currentWaveAlienImages if (typeof lastAlienWave === "undefined" || lastAlienWave !== wave) { currentWaveAlienImages = getAlienImagesForWave(wave); lastAlienWave = wave; } // Spawn aliens for current wave if (!bossActive && enemiesToSpawn > 0 && LK.ticks - lastAlienSpawnTick > 24) { // Pick a random image from currentWaveAlienImages var imgIdx = randInt(0, currentWaveAlienImages.length - 1); var alienImage = currentWaveAlienImages[imgIdx]; // Create a new Alien and set its image var alien = new Alien(); // Remove the default sprite and attach the correct one if (alien.children && alien.children.length > 0) { alien.removeChild(alien.children[0]); } var alienSprite = alien.attachAsset(alienImage, { anchorX: 0.5, anchorY: 0.5 }); alien.width = alienSprite.width; alien.height = alienSprite.height; // Pick a random road for the alien to spawn on (X position) var roadIdx = randInt(0, ROAD_X_POSITIONS.length - 1); alien.x = ROAD_X_POSITIONS[roadIdx]; alien.y = -alien.height / 2; alien.wave = wave; // After 5th wave, increase speed more rapidly if (wave > 5) { alien.speed = 2.5 + (wave - 1) * WAVE_SPEED_INC + (wave - 5) * 0.5; } else { alien.speed = 2.5 + (wave - 1) * WAVE_SPEED_INC; } alien.hp = 1 + Math.floor((wave - 1) * WAVE_HP_INC / 2); aliens.push(alien); game.addChild(alien); enemiesToSpawn--; lastAlienSpawnTick = LK.ticks; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Alien class
var Alien = Container.expand(function () {
var self = Container.call(this);
// Attach a default sprite, but allow it to be replaced after construction
var alienSprite = self.attachAsset('alien', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = alienSprite.width;
self.height = alienSprite.height;
self.speed = 2.5;
self.hp = 1;
self.wave = 1;
self.lastX = self.x;
self.lastY = self.y;
self.lastIntersecting = false;
self.isDead = false;
self.update = function () {
self.lastY = self.y;
self.y += self.speed;
};
return self;
});
// Bonus box class
var BonusBox = Container.expand(function () {
var self = Container.call(this);
var bonusSprite = self.attachAsset('bonus', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = bonusSprite.width;
self.height = bonusSprite.height;
self.lastY = self.y;
self.lastIntersecting = false;
self.type = null; // Set on spawn
self.fallSpeed = 10; // Speed at which the bonus moves toward the player
self.update = function () {
// Move toward the player's current position
if (typeof player !== "undefined" && player !== null) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 2) {
// Normalize and move toward player
self.x += dx / dist * self.fallSpeed;
self.y += dy / dist * self.fallSpeed;
}
}
};
return self;
});
// Boss class
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossSprite = self.attachAsset('alien4', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
self.width = bossSprite.width * 2;
self.height = bossSprite.height * 2;
self.speed = 4;
// Use global bossBaseHp for initial boss health
self.hp = typeof bossBaseHp !== "undefined" ? bossBaseHp : 10;
self.lastY = self.y;
self.lastIntersecting = false;
self.isDead = false;
self.update = function () {
self.lastY = self.y;
self.y += self.speed;
};
return self;
});
// Crystal class
var Crystal = Container.expand(function () {
var self = Container.call(this);
var crystalSprite = self.attachAsset('crystal', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = crystalSprite.width;
self.height = crystalSprite.height;
self.lastY = self.y;
self.lastIntersecting = false;
self.fallSpeed = 12; // Speed at which the crystal falls toward the player
self.update = function () {
// Move toward the player's current position
if (typeof player !== "undefined" && player !== null) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 2) {
// Normalize and move toward player
self.x += dx / dist * self.fallSpeed;
self.y += dy / dist * self.fallSpeed;
}
}
};
return self;
});
// Laser class
var Laser = Container.expand(function () {
var self = Container.call(this);
var laserSprite = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 1
});
self.width = laserSprite.width;
self.height = laserSprite.height;
self.speed = 32;
self.pierce = false;
self.lastY = self.y;
self.lastIntersecting = false;
self.update = function () {
self.y -= self.speed;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = playerSprite.width;
self.height = playerSprite.height;
self.shootCooldown = 0;
self.moveSpeed = 32; // px per tick when dragged
self.leftLimit = 0 + self.width / 2 + 40;
self.rightLimit = 2048 - self.width / 2 - 40;
self.powered = false;
self.powerTimer = 0;
self.powerType = null;
// For powerup visuals
self.setPower = function (type, duration) {
self.powered = true;
self.powerType = type;
self.powerTimer = duration;
if (type === 'rapid') {
playerSprite.tint = 0xffe100;
} else if (type === 'pierce') {
playerSprite.tint = 0x00ffb0;
} else if (type === 'shield') {
playerSprite.tint = 0x00eaff;
}
};
self.clearPower = function () {
self.powered = false;
self.powerType = null;
self.powerTimer = 0;
playerSprite.tint = 0xffffff;
};
self.update = function () {
if (self.powered) {
self.powerTimer--;
if (self.powerTimer <= 0) {
self.clearPower();
}
}
};
return self;
});
// Star class for background stars
var Star = Container.expand(function () {
var self = Container.call(this);
// Use a white ellipse as a star, randomize size (smaller, more variety)
// Reduced min/max size for smaller stars
var size = randInt(1, 2);
var starSprite = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size / 120,
scaleY: size / 120,
tint: 0xffffff
});
self.starSprite = starSprite; // Store for tinting
self.width = size;
self.height = size;
// More parallax: slower stars are smaller, faster are bigger
// Speed: 0.2 to 2.2, with more slow stars
var speedBase = Math.pow(Math.random(), 2.2); // bias toward 0
self.speed = 0.2 + speedBase * 2.0;
self.alpha = 0.4 + Math.random() * 0.6;
self.setColor = function (color) {
if (self.starSprite) self.starSprite.tint = color;
};
self.update = function () {
// Only update if star is visible or about to be visible
if (self.y > -self.height && self.y < GAME_HEIGHT + self.height * 2) {
self.y += self.speed;
} else {
self.y = -self.height;
self.x = randInt(0, GAME_WIDTH);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Music
// Sound effects
// Bonus box
// Crystal (collectible)
// Player bullet (laser)
// Alien (enemy)
// Player
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// --- Starfield background ---
// Star color palette (cycle every 2 waves)
var STAR_COLORS = [0xffffff,
// white
0xffe100,
// yellow
0x00eaff,
// cyan
0xff00cc,
// magenta
0x00ffb0,
// green
0xffa500,
// orange
0xAAFFFF,
// light blue
0xff0000 // red
];
function getStarColorForWave(wave) {
var idx = Math.floor((wave - 1) / 2) % STAR_COLORS.length;
return STAR_COLORS[idx];
}
var NUM_STARS = 320;
var stars = [];
for (var i = 0; i < NUM_STARS; i++) {
var star = new Star();
star.x = randInt(0, GAME_WIDTH);
star.y = randInt(0, GAME_HEIGHT);
stars.push(star);
game.addChild(star);
}
var GROUND_Y = GAME_HEIGHT - 220;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GROUND_Y;
var PLAYER_MIN_X = 160 + 40;
var PLAYER_MAX_X = GAME_WIDTH - 160 - 40;
var ENEMY_START_X_MIN = 160;
var ENEMY_START_X_MAX = GAME_WIDTH - 160;
// Define Y spawn range for enemies (top margin to bottom margin for bonus box spawn)
var ENEMY_START_Y_MIN = 0;
var ENEMY_START_Y_MAX = GAME_HEIGHT - 400;
// Define 3 fixed road X positions for aliens to spawn and move along (top to bottom)
var ROAD_X_POSITIONS = [ENEMY_START_X_MIN + Math.floor((ENEMY_START_X_MAX - ENEMY_START_X_MIN) * 0.2), ENEMY_START_X_MIN + Math.floor((ENEMY_START_X_MAX - ENEMY_START_X_MIN) * 0.5), ENEMY_START_X_MIN + Math.floor((ENEMY_START_X_MAX - ENEMY_START_X_MIN) * 0.8)];
var LASER_COOLDOWN = 18; // ticks
var LASER_COOLDOWN_RAPID = 6;
// Track current firing cooldown, increases as waves progress
var currentLaserCooldown = LASER_COOLDOWN;
var currentLaserCooldownRapid = LASER_COOLDOWN_RAPID;
var CRYSTAL_COLLECT_DIST = 120;
var BONUS_COLLECT_DIST = 120;
var CRYSTAL_MOVE_STEP = 180; // How much movement area expands per crystal
var MAX_CRYSTALS = 8;
var WAVE_ENEMY_BASE = 10;
var WAVE_ENEMY_INC = 5; // Increase by 5 each wave
var WAVE_SPEED_INC = 0.10;
var WAVE_HP_INC = 1;
var BONUS_WAVE_INTERVAL = 3;
var POWERUP_DURATION = 360; // 6 seconds at 60fps
// Game state
var player = null;
var aliens = [];
var lasers = [];
var crystals = [];
var bonuses = [];
var boss = null;
var bossActive = false;
var bossDefeated = false;
// Boss health scaling
var bossBaseHp = 10;
var bossHpInc = 5;
var dragNode = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastMoveX = 0;
var lastMoveY = 0;
var lastAlienSpawnTick = 0;
var wave = 1;
var enemiesLeft = 0;
var enemiesToSpawn = 0;
var enemiesKilled = 0;
var aliensKilledThisWave = 0; // Track aliens killed in current wave
var crystalsCollected = 0;
var leftLimit = PLAYER_MIN_X;
var rightLimit = PLAYER_MAX_X;
var bonusActive = false;
var bonusType = null;
// Double shot state for player bonus
var doubleShotActive = false;
var doubleShotTimer = 0;
// Triple shot state for player bonus (after 5th wave)
var tripleShotActive = false;
var tripleShotTimer = 0;
// Removed score and scoreTxt, replaced by crystalTxt
var waveTxt = null;
var powerupTxt = null;
var gameOver = false;
var youWin = false;
// GUI
// Crystal icon in upper left (avoid top left 100x100 for menu)
// Crystal icon and count, aligned and sized together
var crystalIcon = LK.getAsset('crystal', {
anchorX: 0,
anchorY: 0.5
});
crystalIcon.x = 20;
crystalIcon.y = 160 + 40; // center of 80px icon at 160+40=200
crystalIcon.width = 100;
crystalIcon.height = 100;
LK.gui.topLeft.addChild(crystalIcon);
// Crystal count text, vertically centered with icon, same height
var crystalTxt = new Text2('0', {
size: 100,
fill: 0x00eaff
});
crystalTxt.anchor.set(0, 0.5);
crystalTxt.x = crystalIcon.x + crystalIcon.width + 24;
crystalTxt.y = crystalIcon.y;
LK.gui.topLeft.addChild(crystalTxt);
waveTxt = new Text2('Wave 1', {
size: 80,
fill: 0xAAFFFF
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
waveTxt.y = 120;
powerupTxt = new Text2('', {
size: 70,
fill: 0xFFE100
});
powerupTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(powerupTxt);
powerupTxt.y = 220;
// Start music
LK.playMusic('bgmusic');
// Spawn player
player = new Player();
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
game.addChild(player);
// Set initial movement limits
leftLimit = player.leftLimit;
rightLimit = player.rightLimit;
// Start first wave
function startWave(waveNum) {
wave = waveNum;
waveTxt.setText('Wave ' + wave);
// Set enemiesToSpawn to 30 for each wave, so wave increases every 30 aliens
// After 5th wave, increase number of aliens per wave by 5 for each wave after 5
if (wave > 5) {
enemiesToSpawn = 30 + (wave - 5) * 5;
} else {
enemiesToSpawn = 30;
}
enemiesLeft = enemiesToSpawn;
lastAlienSpawnTick = LK.ticks;
aliensKilledThisWave = 0; // Reset for new wave
// Change star color every 2 waves
// Use the new wave number (after increment) for correct color
var starColor = getStarColorForWave(wave);
for (var i = 0; i < stars.length; i++) {
stars[i].setColor(starColor);
}
// Reset firing speed to base values every wave, then increase firing speed by 0.15x per wave
currentLaserCooldown = Math.max(2, Math.round(LASER_COOLDOWN * Math.pow(0.85, wave - 1)));
currentLaserCooldownRapid = Math.max(1, Math.round(LASER_COOLDOWN_RAPID * Math.pow(0.85, wave - 1)));
// Increase boss health after each wave
if (wave > 1) {
bossBaseHp += bossHpInc;
}
// Enable double shot after 5th wave
if (wave > 5) {
doubleShotActive = true;
doubleShotTimer = 0; // 0 means infinite duration
powerupTxt.setText('Double Shot!');
if (player && player.children && player.children.length > 0) {
player.children[0].tint = 0xff0000;
}
} else if (wave === 5) {
// If returning to wave 5, disable double shot
doubleShotActive = false;
doubleShotTimer = 0;
if (player && player.children && player.children.length > 0) {
player.children[0].tint = 0xffffff;
}
powerupTxt.setText('');
}
}
// Set initial star color
var initialStarColor = getStarColorForWave(1);
for (var i = 0; i < stars.length; i++) {
stars[i].setColor(initialStarColor);
}
startWave(1);
// Utility: clamp
function clamp(val, min, max) {
if (val < min) return min;
if (val > max) return max;
return val;
}
// Utility: random int
function randInt(min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
}
// Utility: random powerup
function randomPowerup() {
var arr = ['rapid', 'pierce', 'shield'];
return arr[randInt(0, arr.length - 1)];
}
// Handle movement (dragging)
function handleMove(x, y, obj) {
if (dragNode === player) {
// Clamp to current movement limits
var newX = clamp(x, leftLimit, rightLimit);
// Prevent player from entering top left 100x100 area (menu)
if (newX - player.width / 2 < 100) {
newX = 100 + player.width / 2;
}
player.x = newX;
lastMoveX = newX;
lastMoveY = player.y;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only allow drag if touch/click is on player or below
if (y > GROUND_Y - 200) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game update
game.update = function () {
// Update background stars
for (var i = 0; i < stars.length; i++) {
stars[i].update();
}
if (gameOver || youWin) return;
// Player update (powerup timer)
player.update();
// Aliens update
for (var i = aliens.length - 1; i >= 0; i--) {
var alien = aliens[i];
alien.update();
// Check if alien reached bottom edge (player dies)
if (alien.y > GAME_HEIGHT + alien.height / 2) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
// Check collision with player (if shielded, destroy alien)
if (player.powered && player.powerType === 'shield' && alien.intersects(player)) {
LK.getSound('alien_die').play();
LK.effects.flashObject(alien, 0x00eaff, 400);
alien.isDead = true;
aliens.splice(i, 1);
alien.destroy();
continue;
}
// If not shielded and alien collides with player, game over
if ((!player.powered || player.powerType !== 'shield') && alien.intersects(player)) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
}
// Boss update
if (bossActive && boss) {
boss.update();
// Check if boss reached bottom edge (player dies)
if (boss.y > GAME_HEIGHT + boss.height / 2) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
// Check collision with player (if shielded, destroy boss)
if (player.powered && player.powerType === 'shield' && boss.intersects(player)) {
LK.getSound('alien_die').play();
LK.effects.flashObject(boss, 0x00eaff, 400);
boss.isDead = true;
bossActive = false;
bossDefeated = true;
boss.destroy();
boss = null;
}
}
// Lasers update
for (var i = lasers.length - 1; i >= 0; i--) {
var laser = lasers[i];
laser.update();
// Remove if off screen
if (laser.y < -100) {
lasers.splice(i, 1);
laser.destroy();
continue;
}
// Check collision with aliens
var hit = false;
for (var j = aliens.length - 1; j >= 0; j--) {
var alien = aliens[j];
if (!alien.isDead && laser.intersects(alien)) {
alien.hp -= player.powered && player.powerType === 'pierce' ? 2 : 1;
if (alien.hp <= 0) {
// Alien dies
LK.getSound('alien_die').play();
LK.effects.flashObject(alien, 0xff0000, 400);
alien.isDead = true;
// Drop crystal
var crystal = new Crystal();
crystal.x = alien.x;
crystal.y = alien.y;
crystals.push(crystal);
game.addChild(crystal);
// Remove alien
aliens.splice(j, 1);
alien.destroy();
enemiesLeft--;
enemiesKilled++;
aliensKilledThisWave++;
// Every 30 aliens, increase wave by 1 (only if all 30 were killed in this wave)
if (aliensKilledThisWave > 0 && aliensKilledThisWave % 30 === 0 && enemiesLeft === 0) {
// After each wave, spawn boss
bossActive = true;
bossDefeated = false;
boss = new Boss();
boss.x = GAME_WIDTH / 2;
boss.y = -boss.height / 2;
game.addChild(boss);
// Only start next wave after boss is defeated
// Bonus box every BONUS_WAVE_INTERVAL after wave increment (handled after boss defeat)
return; // Prevent double wave start if also enemiesLeft==0
}
// New wave?
if (enemiesLeft <= 0) {
// No wave increase here, handled by 30 aliens killed logic
}
}
hit = true;
if (!(player.powered && player.powerType === 'pierce')) {
break;
}
}
}
// Boss hit by laser
if (bossActive && boss && laser.intersects(boss)) {
boss.hp -= player.powered && player.powerType === 'pierce' ? 2 : 1;
if (boss.hp <= 0) {
LK.getSound('alien_die').play();
LK.effects.flashObject(boss, 0xff0000, 600);
// Drop multiple crystals
for (var c = 0; c < 3; c++) {
var crystal = new Crystal();
crystal.x = boss.x + randInt(-60, 60);
crystal.y = boss.y + randInt(-60, 60);
crystals.push(crystal);
game.addChild(crystal);
}
boss.isDead = true;
bossActive = false;
bossDefeated = true;
boss.destroy();
boss = null;
// Start next wave
startWave(wave + 1);
// Bonus box every BONUS_WAVE_INTERVAL after wave increment
if ((wave + 1) % BONUS_WAVE_INTERVAL === 0) {
var bonus = new BonusBox();
bonus.x = randInt(leftLimit + 100, rightLimit - 100);
bonus.y = randInt(ENEMY_START_Y_MIN + 100, ENEMY_START_Y_MAX - 100);
bonus.type = randomPowerup();
bonuses.push(bonus);
game.addChild(bonus);
}
}
hit = true;
}
if (hit && !(player.powered && player.powerType === 'pierce')) {
lasers.splice(i, 1);
laser.destroy();
}
}
// Crystals update
for (var i = crystals.length - 1; i >= 0; i--) {
var crystal = crystals[i];
// If player close enough, collect
var dx = player.x - crystal.x;
var dy = player.y - crystal.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < CRYSTAL_COLLECT_DIST) {
LK.getSound('crystal_get').play();
LK.effects.flashObject(crystal, 0x00eaff, 400);
crystals.splice(i, 1);
crystal.destroy();
// Expand movement area and always update score
crystalsCollected++;
crystalTxt.setText(crystalsCollected);
if (crystalsCollected <= MAX_CRYSTALS) {
leftLimit = Math.max(PLAYER_MIN_X - crystalsCollected * CRYSTAL_MOVE_STEP, 80 + player.width / 2);
rightLimit = Math.min(PLAYER_MAX_X + crystalsCollected * CRYSTAL_MOVE_STEP, GAME_WIDTH - 80 - player.width / 2);
}
}
}
// Bonus boxes update
for (var i = bonuses.length - 1; i >= 0; i--) {
var bonus = bonuses[i];
var dx = player.x - bonus.x;
var dy = player.y - bonus.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < BONUS_COLLECT_DIST) {
LK.getSound('bonus_get').play();
LK.effects.flashObject(bonus, 0xffe100, 400);
bonuses.splice(i, 1);
bonus.destroy();
// After 5th wave, randomly grant either alien slowdown for 5 seconds or triple shot for 5 seconds
if (wave > 5) {
if (Math.random() < 0.5) {
// Alien slowdown: halve alien and boss speed for 5 seconds (300 ticks)
powerupTxt.setText('Alien Slowdown!');
for (var j = 0; j < aliens.length; j++) {
if (typeof aliens[j].originalSpeed === "undefined") aliens[j].originalSpeed = aliens[j].speed;
aliens[j].speed = aliens[j].originalSpeed * 0.5;
}
if (bossActive && boss) {
if (typeof boss.originalSpeed === "undefined") boss.originalSpeed = boss.speed;
boss.speed = boss.originalSpeed * 0.5;
}
// Set timer to restore speed
if (typeof alienSlowdownTimer === "undefined") alienSlowdownTimer = 0;
alienSlowdownTimer = 300;
player.setPower(null, 0); // No player power, just visual
} else {
// Triple shot: grant for 5 seconds (300 ticks)
powerupTxt.setText('Triple Shot!');
tripleShotActive = true;
tripleShotTimer = 300;
player.setPower(null, 0); // No player power, just visual
}
} else {
// Before or at 5th wave, randomly grant either alien slowdown for 5 seconds or double shot for 5 seconds
if (Math.random() < 0.5) {
// Alien slowdown: halve alien and boss speed for 5 seconds (300 ticks)
powerupTxt.setText('Alien Slowdown!');
for (var j = 0; j < aliens.length; j++) {
if (typeof aliens[j].originalSpeed === "undefined") aliens[j].originalSpeed = aliens[j].speed;
aliens[j].speed = aliens[j].originalSpeed * 0.5;
}
if (bossActive && boss) {
if (typeof boss.originalSpeed === "undefined") boss.originalSpeed = boss.speed;
boss.speed = boss.originalSpeed * 0.5;
}
// Set timer to restore speed
if (typeof alienSlowdownTimer === "undefined") alienSlowdownTimer = 0;
alienSlowdownTimer = 300;
player.setPower(null, 0); // No player power, just visual
} else {
// Double shot: always grant for 5 seconds (300 ticks) when bonus is collected
powerupTxt.setText('Double Shot!');
doubleShotActive = true;
doubleShotTimer = 300;
player.setPower(null, 0); // No player power, just visual
}
}
}
}
// Powerup text timer
if (player.powered) {
powerupTxt.alpha = 1;
} else {
if (powerupTxt.alpha > 0) {
powerupTxt.alpha -= 0.04;
if (powerupTxt.alpha < 0) powerupTxt.alpha = 0;
}
powerupTxt.setText('');
}
// Alien slowdown timer
if (typeof alienSlowdownTimer !== "undefined" && alienSlowdownTimer > 0) {
alienSlowdownTimer--;
if (alienSlowdownTimer <= 0) {
// Restore all aliens' and boss's speed, and clear originalSpeed to prevent stacking
for (var j = 0; j < aliens.length; j++) {
if (typeof aliens[j].originalSpeed !== "undefined") {
aliens[j].speed = aliens[j].originalSpeed;
delete aliens[j].originalSpeed;
}
}
if (bossActive && boss && typeof boss.originalSpeed !== "undefined") {
boss.speed = boss.originalSpeed;
delete boss.originalSpeed;
}
powerupTxt.setText('');
}
}
// Double shot timer
if (doubleShotActive) {
// Set player and all lasers to red tint
if (player && player.children && player.children.length > 0) {
// Player sprite is always first child
if (player.children[0].tint !== 0xff0000) player.children[0].tint = 0xff0000;
}
for (var i = 0; i < lasers.length; i++) {
if (lasers[i].children && lasers[i].children.length > 0) {
if (lasers[i].children[0].tint !== 0xff0000) lasers[i].children[0].tint = 0xff0000;
}
}
// Only decrement timer if timer is not 0 (0 means infinite double shot)
if (doubleShotTimer !== 0) {
doubleShotTimer--;
if (doubleShotTimer <= 0) {
doubleShotActive = false;
// Restore player tint (respect powerup color if active)
if (player && player.children && player.children.length > 0) {
if (player.powered) {
if (player.powerType === 'rapid') {
player.children[0].tint = 0xffe100;
} else if (player.powerType === 'pierce') {
player.children[0].tint = 0x00ffb0;
} else if (player.powerType === 'shield') {
player.children[0].tint = 0x00eaff;
}
} else {
player.children[0].tint = 0xffffff;
}
}
// Restore all lasers to normal tint
for (var i = 0; i < lasers.length; i++) {
if (lasers[i].children && lasers[i].children.length > 0) {
lasers[i].children[0].tint = 0xffffff;
}
}
}
}
}
// Triple shot timer
if (tripleShotActive) {
// Set player and all lasers to magenta tint
if (player && player.children && player.children.length > 0) {
if (player.children[0].tint !== 0xff00cc) player.children[0].tint = 0xff00cc;
}
for (var i = 0; i < lasers.length; i++) {
if (lasers[i].children && lasers[i].children.length > 0) {
if (lasers[i].children[0].tint !== 0xff00cc) lasers[i].children[0].tint = 0xff00cc;
}
}
if (tripleShotTimer !== 0) {
tripleShotTimer--;
if (tripleShotTimer <= 0) {
tripleShotActive = false;
// Restore player tint (respect powerup color if active)
if (player && player.children && player.children.length > 0) {
if (player.powered) {
if (player.powerType === 'rapid') {
player.children[0].tint = 0xffe100;
} else if (player.powerType === 'pierce') {
player.children[0].tint = 0x00ffb0;
} else if (player.powerType === 'shield') {
player.children[0].tint = 0x00eaff;
}
} else {
player.children[0].tint = 0xffffff;
}
}
// Restore all lasers to normal tint
for (var i = 0; i < lasers.length; i++) {
if (lasers[i].children && lasers[i].children.length > 0) {
lasers[i].children[0].tint = 0xffffff;
}
}
}
}
}
// Player auto-fire
var cooldown = player.powered && player.powerType === 'rapid' ? currentLaserCooldownRapid : currentLaserCooldown;
if (player.shootCooldown > 0) player.shootCooldown--;
// Prevent shooting if game is over or won
if (gameOver || youWin) return;
// Always fire if cooldown is ready, regardless of nearest alien
if (player.shootCooldown <= 0) {
// Fire laser(s)
if (tripleShotActive) {
// Triple shot: fire three lasers, center and two offset
var laser1 = new Laser();
laser1.x = player.x - 48;
laser1.y = player.y - player.height / 2 + 10;
if (player.powered && player.powerType === 'pierce') {
laser1.pierce = true;
}
// Set laser magenta if triple shot
if (laser1.children && laser1.children.length > 0) {
laser1.children[0].tint = 0xff00cc;
}
lasers.push(laser1);
game.addChild(laser1);
var laser2 = new Laser();
laser2.x = player.x;
laser2.y = player.y - player.height / 2 + 10;
if (player.powered && player.powerType === 'pierce') {
laser2.pierce = true;
}
if (laser2.children && laser2.children.length > 0) {
laser2.children[0].tint = 0xff00cc;
}
lasers.push(laser2);
game.addChild(laser2);
var laser3 = new Laser();
laser3.x = player.x + 48;
laser3.y = player.y - player.height / 2 + 10;
if (player.powered && player.powerType === 'pierce') {
laser3.pierce = true;
}
if (laser3.children && laser3.children.length > 0) {
laser3.children[0].tint = 0xff00cc;
}
lasers.push(laser3);
game.addChild(laser3);
player.shootCooldown = cooldown;
LK.getSound('laser_shoot').play();
} else if (doubleShotActive) {
// Double shot: fire two lasers, slightly offset
var laser1 = new Laser();
laser1.x = player.x - 32;
laser1.y = player.y - player.height / 2 + 10;
if (player.powered && player.powerType === 'pierce') {
laser1.pierce = true;
}
// Set laser red if double shot
if (laser1.children && laser1.children.length > 0) {
laser1.children[0].tint = 0xff0000;
}
lasers.push(laser1);
game.addChild(laser1);
var laser2 = new Laser();
laser2.x = player.x + 32;
laser2.y = player.y - player.height / 2 + 10;
if (player.powered && player.powerType === 'pierce') {
laser2.pierce = true;
}
// Set laser red if double shot
if (laser2.children && laser2.children.length > 0) {
laser2.children[0].tint = 0xff0000;
}
lasers.push(laser2);
game.addChild(laser2);
player.shootCooldown = cooldown;
LK.getSound('laser_shoot').play();
} else {
// Normal single shot
var laser = new Laser();
laser.x = player.x;
laser.y = player.y - player.height / 2 + 10;
if (player.powered && player.powerType === 'pierce') {
laser.pierce = true;
}
// Set laser red if double shot is active
if (doubleShotActive && laser.children && laser.children.length > 0) {
laser.children[0].tint = 0xff0000;
}
lasers.push(laser);
game.addChild(laser);
player.shootCooldown = cooldown;
LK.getSound('laser_shoot').play();
}
}
// Alien image pool for 4 types
var ALIEN_IMAGES = ['alien', 'alien2', 'alien3', 'alien4'];
// Helper: get alien image(s) for this wave
function getAlienImagesForWave(wave) {
// Every 3 waves, cycle to next image
var idx = Math.floor((wave - 1) / 3) % ALIEN_IMAGES.length;
// 30% chance to mix all images in this wave
if (Math.random() < 0.3) {
// Return all images for mixing
return ALIEN_IMAGES.slice();
}
// Otherwise, use only the current image for this 3-wave block
return [ALIEN_IMAGES[idx]];
}
// Track which images to use for this wave
if (typeof currentWaveAlienImages === "undefined") {
var currentWaveAlienImages = getAlienImagesForWave(wave);
}
// If wave changed, update currentWaveAlienImages
if (typeof lastAlienWave === "undefined" || lastAlienWave !== wave) {
currentWaveAlienImages = getAlienImagesForWave(wave);
lastAlienWave = wave;
}
// Spawn aliens for current wave
if (!bossActive && enemiesToSpawn > 0 && LK.ticks - lastAlienSpawnTick > 24) {
// Pick a random image from currentWaveAlienImages
var imgIdx = randInt(0, currentWaveAlienImages.length - 1);
var alienImage = currentWaveAlienImages[imgIdx];
// Create a new Alien and set its image
var alien = new Alien();
// Remove the default sprite and attach the correct one
if (alien.children && alien.children.length > 0) {
alien.removeChild(alien.children[0]);
}
var alienSprite = alien.attachAsset(alienImage, {
anchorX: 0.5,
anchorY: 0.5
});
alien.width = alienSprite.width;
alien.height = alienSprite.height;
// Pick a random road for the alien to spawn on (X position)
var roadIdx = randInt(0, ROAD_X_POSITIONS.length - 1);
alien.x = ROAD_X_POSITIONS[roadIdx];
alien.y = -alien.height / 2;
alien.wave = wave;
// After 5th wave, increase speed more rapidly
if (wave > 5) {
alien.speed = 2.5 + (wave - 1) * WAVE_SPEED_INC + (wave - 5) * 0.5;
} else {
alien.speed = 2.5 + (wave - 1) * WAVE_SPEED_INC;
}
alien.hp = 1 + Math.floor((wave - 1) * WAVE_HP_INC / 2);
aliens.push(alien);
game.addChild(alien);
enemiesToSpawn--;
lastAlienSpawnTick = LK.ticks;
}
};
A triangular spaceship. In-Game asset. 2d. High contrast. No shadows
alien creature drawing. In-Game asset. 2d. High contrast. No shadows
alien creature drawing. In-Game asset. 2d. High contrast. No shadows
alien creature drawing. In-Game asset. 2d. High contrast. No shadows
alien creature drawing. In-Game asset. 2d. High contrast. No shadows
blue crystal. In-Game asset. 2d. High contrast. No shadows
draw thick long laser bullet. In-Game asset. 2d. High contrast. No shadows
remove bonus text
spaceship. In-Game asset. 2d. High contrast. No shadows