Code edit (1 edits merged)
Please save this source code
User prompt
The current level should be displayed in the top-left corner of the screen.
User prompt
There should be a level system. The current level number should be displayed at the top-right corner of the screen. The number of enemies in the current level should be displayed at the top-left corner. When the player kills all enemies, the game should automatically progress to the next level. In each new level, the score awarded for killing enemies should increase multiplicatively (e.g., doubled each level). Example: Level 1: each enemy = 10 points Level 2: each enemy = 20 points Level 3: each enemy = 40 points
User prompt
prevent enemies from moving outside the screen area.
User prompt
When I shoot an enemy, they should be removed instantly
User prompt
If I shoot the enemy, the enemy should die
Code edit (1 edits merged)
Please save this source code
User prompt
Space Wars: Galactic Showdown
Initial prompt
make me space wars game
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ // Boss ship class var BossShip = Container.expand(function () { var self = Container.call(this); // Attach boss asset (use enemyShip but much larger, and red tint) var boss = self.attachAsset('enemyShip', { width: 480, height: 480, color: 0xff0000, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); self.radius = 240; self.isAlive = true; self.health = 40; self.maxHealth = 40; self.lastY = undefined; // Boss always at top center, stationary self.x = GAME_WIDTH / 2; self.y = 340; // Boss update self.update = function () { // Boss remains stationary at top center // Unique Skill 1: Radial bullet spread every 120 ticks if (LK.ticks % 120 === 0) { var numBullets = 14; for (var i = 0; i < numBullets; i++) { var angle = 2 * Math.PI / numBullets * i; var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + 220; // Give bullet a custom direction bullet.customVX = Math.cos(angle) * 13; bullet.customVY = Math.sin(angle) * 13; bullet.update = function () { this.x += this.customVX; this.y += this.customVY; }; enemyBullets.push(bullet); game.addChild(bullet); } } // Unique Skill 2: Laser attack every 300 ticks (fires a fast straight bullet down the center) if (LK.ticks % 300 === 0) { var laser = new EnemyBullet(); laser.x = self.x; laser.y = self.y + 260; laser.speed = 38; laser.radius = 60; // Make it visually larger var asset = laser.children[0]; if (asset) { asset.width = 90; asset.height = 340; } enemyBullets.push(laser); game.addChild(laser); } // Unique Skill 3: Area attack (big slow bullet straight down) every 180 ticks if (LK.ticks % 180 === 0) { var area = new EnemyBullet(); area.x = self.x; area.y = self.y + 240; area.speed = 10; area.radius = 120; var asset = area.children[0]; if (asset) { asset.width = 220; asset.height = 120; } enemyBullets.push(area); game.addChild(area); } }; // Flash on hit self.flash = function () { tween(boss, { tint: 0xffffff }, { duration: 80, onFinish: function onFinish() { tween(boss, { tint: 0xff0000 }, { duration: 120 }); } }); }; return self; }); // Enemy bullet class var EnemyBullet = Container.expand(function () { var self = Container.call(this); // Attach bullet asset (red, 24x48) var bullet = self.attachAsset('enemyBullet', { width: 24, height: 48, color: 0xff4444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.speed = 14; self.radius = 12; self.update = function () { self.y += self.speed; }; return self; }); // Enemy ship class var EnemyShip = Container.expand(function () { var self = Container.call(this); // Attach enemy asset (ellipse, magenta, 220x220) var enemy = self.attachAsset('enemyShip', { width: 220, height: 220, color: 0xff33cc, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); self.radius = 110; self.speed = 4 + Math.random() * 2; // Vary speed self.dirX = (Math.random() - 0.5) * 2; // Slight horizontal drift // For movement patterns self.waveOffset = Math.random() * Math.PI * 2; // For collision self.isAlive = true; // Update per frame self.update = function () { // Move down, with a sine wave pattern self.y += self.speed; self.x += Math.sin(LK.ticks / 30 + self.waveOffset) * 2 + self.dirX; // Prevent enemy from moving outside the horizontal screen area var minX = 60; var maxX = 2048 - 60; if (self.x < minX) { self.x = minX; self.dirX = Math.abs(self.dirX); // bounce right } if (self.x > maxX) { self.x = maxX; self.dirX = -Math.abs(self.dirX); // bounce left } }; // Flash on hit self.flash = function () { tween(enemy, { tint: 0xffffff }, { duration: 80, onFinish: function onFinish() { tween(enemy, { tint: 0xff33cc }, { duration: 120 }); } }); }; return self; }); // Player bullet class var PlayerBullet = Container.expand(function () { var self = Container.call(this); // Attach bullet asset (yellow, 30x60) var bullet = self.attachAsset('playerBullet', { width: 30, height: 60, color: 0xffe066, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.speed = -22; self.radius = 15; self.update = function () { self.y += self.speed; }; return self; }); // Player spaceship class var PlayerShip = Container.expand(function () { var self = Container.call(this); // Attach ship asset (box, blue, 220x220) var ship = self.attachAsset('playerShip', { width: 220, height: 220, color: 0x3399ff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Ship properties self.radius = 110; // for collision self.isAlive = true; // Ship flash effect on hit self.flash = function () { tween(ship, { tint: 0xff3333 }, { duration: 100, onFinish: function onFinish() { tween(ship, { tint: 0x3399ff }, { duration: 200 }); } }); }; return self; }); // Powerup class var PowerUp = Container.expand(function () { var self = Container.call(this); // Attach powerup asset (green ellipse, 120x120) var pu = self.attachAsset('powerUp', { width: 120, height: 120, color: 0x44ff88, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); self.radius = 60; self.speed = 6; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000010 }); /**** * Game Code ****/ // --- Start Screen Overlay --- var startScreenOverlay = new Container(); // Large main title: "Space Wars" var mainTitle = new Text2('Space Wars', { size: 220, fill: 0xFFD700, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); mainTitle.anchor.set(0.5, 0); mainTitle.x = GAME_WIDTH / 2; mainTitle.y = 120; // Subtitle: "Galactic Showdown" var subTitle = new Text2('Galactic Showdown', { size: 110, fill: 0x99e6ff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); subTitle.anchor.set(0.5, 0); subTitle.x = GAME_WIDTH / 2; subTitle.y = mainTitle.y + mainTitle.height + 20; // --- SHOP MENU UI --- // Shop state var shopOpen = false; var shopOverlay = new Container(); shopOverlay.visible = false; var shopTitle = new Text2('Costume Shop', { size: 120, fill: 0xFFD700, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); shopTitle.anchor.set(0.5, 0); shopTitle.x = GAME_WIDTH / 2; shopTitle.y = 120; shopOverlay.addChild(shopTitle); // Define costumes (id, name, price, asset) var costumes = [{ id: "default", name: "Classic", price: 0, asset: "playerShip" }, { id: "red", name: "Red Comet", price: 200, asset: "enemyShip" }, { id: "gold", name: "Gold Star", price: 500, asset: "playerBullet" }]; // Persistent unlocks if (!storage.unlockedCostumes) storage.unlockedCostumes = { "default": true }; if (!storage.selectedCostume) storage.selectedCostume = "default"; // Shop navigation state var shopIndex = 0; function updateShopUI() { // Remove old preview if any if (shopOverlay.costumePreview) { shopOverlay.removeChild(shopOverlay.costumePreview); shopOverlay.costumePreview = null; } var c = costumes[shopIndex]; var preview = LK.getAsset(c.asset, { width: 320, height: 320, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: 520 }); shopOverlay.addChild(preview); shopOverlay.costumePreview = preview; // Update name/price shopNameTxt.setText(c.name); if (c.price === 0) { shopPriceTxt.setText("Unlocked"); } else if (storage.unlockedCostumes[c.id]) { shopPriceTxt.setText("Unlocked"); } else { shopPriceTxt.setText("Price: " + c.price + " pts"); } // Show select/unlock button if (storage.unlockedCostumes[c.id]) { shopActionBtnText.setText(storage.selectedCostume === c.id ? "Selected" : "Select"); } else { shopActionBtnText.setText("Unlock"); } // Hide select if already selected shopActionBtn.alpha = storage.selectedCostume === c.id && storage.unlockedCostumes[c.id] ? 0.5 : 1; } // Costume name var shopNameTxt = new Text2('', { size: 80, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); shopNameTxt.anchor.set(0.5, 0); shopNameTxt.x = GAME_WIDTH / 2; shopNameTxt.y = 900; shopOverlay.addChild(shopNameTxt); // Costume price var shopPriceTxt = new Text2('', { size: 60, fill: 0xFFD700, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); shopPriceTxt.anchor.set(0.5, 0); shopPriceTxt.x = GAME_WIDTH / 2; shopPriceTxt.y = 1020; shopOverlay.addChild(shopPriceTxt); // Action button (Unlock/Select) var shopActionBtn = LK.getAsset('playerBullet', { width: 420, height: 110, color: 0x00ccff, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: 1200 }); var shopActionBtnText = new Text2('', { size: 70, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); shopActionBtnText.anchor.set(0.5, 0.5); shopActionBtnText.x = shopActionBtn.x; shopActionBtnText.y = shopActionBtn.y; shopOverlay.addChild(shopActionBtn); shopOverlay.addChild(shopActionBtnText); // Left/right nav buttons var shopLeftBtn = LK.getAsset('enemyBullet', { width: 100, height: 100, color: 0xFFD700, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2 - 300, y: 520 }); var shopRightBtn = LK.getAsset('enemyBullet', { width: 100, height: 100, color: 0xFFD700, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2 + 300, y: 520 }); shopOverlay.addChild(shopLeftBtn); shopOverlay.addChild(shopRightBtn); // Close button var shopCloseBtn = LK.getAsset('playerBullet', { width: 180, height: 80, color: 0x888888, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: 1450 }); var shopCloseBtnText = new Text2('Close', { size: 50, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); shopCloseBtnText.anchor.set(0.5, 0.5); shopCloseBtnText.x = shopCloseBtn.x; shopCloseBtnText.y = shopCloseBtn.y; shopOverlay.addChild(shopCloseBtn); shopOverlay.addChild(shopCloseBtnText); // Shop button on start screen var shopBtn = LK.getAsset('playerBullet', { width: 320, height: 100, color: 0xFFD700, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 + 320 }); var shopBtnText = new Text2('SHOP', { size: 70, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); shopBtnText.anchor.set(0.5, 0.5); shopBtnText.x = shopBtn.x; shopBtnText.y = shopBtn.y; // Shop button handler shopBtn.down = function (x, y, obj) { if (shopOpen) return; shopOpen = true; shopOverlay.visible = true; LK.gui.center.addChild(shopOverlay); updateShopUI(); // Hide start overlay if (startScreenOverlay.parent) startScreenOverlay.visible = false; }; // Shop close handler shopCloseBtn.down = function (x, y, obj) { shopOpen = false; shopOverlay.visible = false; if (shopOverlay.parent) LK.gui.center.removeChild(shopOverlay); if (startScreenOverlay && !gameStarted) startScreenOverlay.visible = true; }; // Shop left/right nav shopLeftBtn.down = function (x, y, obj) { shopIndex = (shopIndex + costumes.length - 1) % costumes.length; updateShopUI(); }; shopRightBtn.down = function (x, y, obj) { shopIndex = (shopIndex + 1) % costumes.length; updateShopUI(); }; // Shop action (unlock/select) shopActionBtn.down = function (x, y, obj) { var c = costumes[shopIndex]; if (storage.unlockedCostumes[c.id]) { // Select storage.selectedCostume = c.id; updateShopUI(); } else { // Unlock if enough score var score = LK.getScore(); if (score >= c.price) { LK.setScore(score - c.price); scoreTxt.setText(LK.getScore()); storage.unlockedCostumes[c.id] = true; storage.selectedCostume = c.id; updateShopUI(); } else { // Not enough points, flash price red shopPriceTxt.setText("Need " + (c.price - score) + " more!"); tween(shopPriceTxt, { fill: 0xff0000 }, { duration: 200, onFinish: function onFinish() { shopPriceTxt.setText("Price: " + c.price + " pts"); } }); } } }; // Add to overlay startScreenOverlay.addChild(mainTitle); startScreenOverlay.addChild(subTitle); startScreenOverlay.addChild(shopBtn); startScreenOverlay.addChild(shopBtnText); // Play button (large, centered) var playBtn = LK.getAsset('playerBullet', { width: 520, height: 140, color: 0x00ccff, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 + 120 }); var playBtnText = new Text2('PLAY', { size: 100, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); playBtnText.anchor.set(0.5, 0.5); playBtnText.x = playBtn.x; playBtnText.y = playBtn.y; startScreenOverlay.addChild(playBtn); startScreenOverlay.addChild(playBtnText); // Animate main title in mainTitle.alpha = 0; tween(mainTitle, { alpha: 1 }, { duration: 700, easing: tween.cubicOut }); // Animate subtitle in (slightly delayed) subTitle.alpha = 0; tween(subTitle, { alpha: 1 }, { duration: 700, delay: 200, easing: tween.cubicOut }); // Animate play button in playBtn.alpha = 0; playBtnText.alpha = 0; tween(playBtn, { alpha: 1 }, { duration: 700, easing: tween.cubicOut }); tween(playBtnText, { alpha: 1 }, { duration: 900, easing: tween.cubicOut }); // Add to overlay startScreenOverlay.addChild(mainTitle); startScreenOverlay.addChild(subTitle); startScreenOverlay.addChild(playBtn); startScreenOverlay.addChild(playBtnText); LK.gui.center.addChild(startScreenOverlay); // Block gameplay until Play is pressed var gameStarted = false; function hideStartScreen() { if (startScreenOverlay.parent) { LK.gui.center.removeChild(startScreenOverlay); } gameStarted = true; } // Play button interaction playBtn.down = function (x, y, obj) { if (gameStarted) return; // Animate out tween(startScreenOverlay, { alpha: 0 }, { duration: 400, easing: tween.cubicIn, onFinish: function onFinish() { // Start background music only after Play is clicked LK.playMusic('sapce'); hideStartScreen(); } }); }; playBtn.move = function (x, y, obj) {}; playBtn.up = function (x, y, obj) {}; // Block all gameplay input until started var origGameMove = game.move; var origGameDown = game.down; var origGameUp = game.up; game.move = function (x, y, obj) { if (!gameStarted) return; if (origGameMove) origGameMove(x, y, obj); }; game.down = function (x, y, obj) { if (!gameStarted) return; if (origGameDown) origGameDown(x, y, obj); }; game.up = function (x, y, obj) { if (!gameStarted) return; if (origGameUp) origGameUp(x, y, obj); }; // --- Volume Control UI on Pause --- var volumeSlider = null; var volumeLabel = null; var lastPauseState = false; // Helper to create volume slider UI function showVolumeSlider() { if (!LK.isPaused) return; if (volumeSlider) return; // Already shown // Create label volumeLabel = new Text2('Music Volume', { size: 60, fill: 0xFFFFFF }); volumeLabel.anchor.set(0.5, 0.5); volumeLabel.x = GAME_WIDTH / 2; volumeLabel.y = GAME_HEIGHT / 2 - 120; LK.gui.center.addChild(volumeLabel); // Create slider background var sliderBg = LK.getAsset('playerBullet', { width: 500, height: 32, color: 0x222222, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 }); LK.gui.center.addChild(sliderBg); // Create slider knob volumeSlider = LK.getAsset('enemyBullet', { width: 60, height: 60, color: 0xFFD700, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 }); LK.gui.center.addChild(volumeSlider); // Set initial knob position based on current music volume var currentVolume = LK.getMusicVolume ? LK.getMusicVolume() : 1; var minX = GAME_WIDTH / 2 - 220; var maxX = GAME_WIDTH / 2 + 220; volumeSlider.x = minX + (maxX - minX) * currentVolume; // Drag logic var dragging = false; volumeSlider.down = function (x, y, obj) { dragging = true; }; volumeSlider.up = function (x, y, obj) { dragging = false; }; // Move handler for slider volumeSlider.move = function (x, y, obj) { if (!dragging) return; var minX = GAME_WIDTH / 2 - 220; var maxX = GAME_WIDTH / 2 + 220; var px = Math.max(minX, Math.min(maxX, x)); volumeSlider.x = px; // Calculate volume (0..1) var vol = (px - minX) / (maxX - minX); if (vol < 0) vol = 0; if (vol > 1) vol = 1; LK.setMusicVolume ? LK.setMusicVolume(vol) : null; }; // Attach move/up to gui.center for mobile, but only if game is paused LK.gui.center.move = function (x, y, obj) { if (LK.isPaused && dragging) { volumeSlider.move(x, y, obj); } }; LK.gui.center.up = function (x, y, obj) { if (LK.isPaused) { dragging = false; } }; } // Helper to remove volume slider UI function hideVolumeSlider() { if (volumeSlider) { LK.gui.center.removeChild(volumeSlider); volumeSlider = null; } if (volumeLabel) { LK.gui.center.removeChild(volumeLabel); volumeLabel = null; } // Remove slider background if present var children = LK.gui.center.children; for (var i = children.length - 1; i >= 0; i--) { var c = children[i]; if (c && c.width === 500 && c.height === 32 && c.color === 0x222222) { LK.gui.center.removeChild(c); } } // Remove move/up handlers LK.gui.center.move = null; LK.gui.center.up = null; } // Listen for pause/resume to show/hide slider LK.on('pause', function () { showVolumeSlider(); }); LK.on('resume', function () { hideVolumeSlider(); }); var backgroundImg = LK.getAsset('background', { width: GAME_WIDTH, height: GAME_HEIGHT, anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(backgroundImg, 0); // Always at the back layer // Play sapce music at game start // (Music will be started after Play is clicked, see playBtn.down handler) // LK.playMusic('sapce'); // Health bar asset (simple red bar, 600x40) // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLAYER_START_X = GAME_WIDTH / 2; var PLAYER_START_Y = GAME_HEIGHT - 350; // Level system variables var currentLevel = 1; var enemiesPerLevel = 6; // base number of enemies for level 1 var enemiesToSpawn = enemiesPerLevel; var enemiesKilledThisLevel = 0; var enemyScoreBase = 10; // base score for level 1 var enemyScoreThisLevel = enemyScoreBase; // Game state var player; var enemies = []; var playerBullets = []; var enemyBullets = []; var powerUps = []; var dragNode = null; var lastScore = 0; var scoreTxt; var powerUpActive = false; var powerUpTimer = 0; var enemySpawnTimer = 0; var enemyFireTimer = 0; var powerUpSpawnTimer = 0; var gameOver = false; // Boss state var bossActive = false; var bossShip = null; var bossHealthBar = null; // Score display scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Import storage plugin for persistent high score // High score display (top right, avoid top right 100x100) var highScore = storage.highScore || 0; var highScoreTxt = new Text2('High: ' + highScore, { size: 60, fill: 0xFFD700 }); highScoreTxt.anchor.set(1, 0); highScoreTxt.x = GAME_WIDTH - 120; highScoreTxt.y = 0; LK.gui.top.addChild(highScoreTxt); // Health system var playerHealth = 5; // Heart icon assets (use 5 separate heart images for live updating) var heartIcons = []; var heartAssetNames = ['heart1', 'heart2', 'heart3', 'heart4', 'heart5']; for (var i = 0; i < 5; i++) { var heart = LK.getAsset(heartAssetNames[i], { width: 60, height: 60, anchorX: 0.5, anchorY: 0.5, x: 200 + i * 70, y: 30 }); LK.gui.top.addChild(heart); heartIcons.push(heart); } // Helper to update heart icons based on playerHealth function updateHearts() { for (var i = 0; i < heartIcons.length; i++) { heartIcons[i].alpha = i < playerHealth ? 1 : 0.2; } } updateHearts(); // Level display (top left, avoid top left 100x100) var levelTxt = new Text2('Level 1', { size: 40, fill: 0xFFD700 }); levelTxt.anchor.set(0, 0); levelTxt.x = 180; levelTxt.y = 100; LK.gui.top.addChild(levelTxt); // Enemies left display (top left, avoid top left 100x100) var enemiesLeftTxt = new Text2('Enemies: 0', { size: 40, fill: 0xFF8888 }); enemiesLeftTxt.anchor.set(0, 0); enemiesLeftTxt.x = 180; enemiesLeftTxt.y = 60; LK.gui.top.addChild(enemiesLeftTxt); // Initialize player player = new PlayerShip(); // Apply selected costume if not default if (storage.selectedCostume && storage.selectedCostume !== "default") { var costume = null; for (var i = 0; i < costumes.length; i++) { if (costumes[i].id === storage.selectedCostume) costume = costumes[i]; } if (costume) { // Remove old asset and attach new if (player.children.length > 0) player.removeChild(player.children[0]); var costumeAsset = player.attachAsset(costume.asset, { width: 220, height: 220, anchorX: 0.5, anchorY: 0.5 }); } } player.x = PLAYER_START_X; player.y = PLAYER_START_Y; game.addChild(player); // Touch/move controls function handleMove(x, y, obj) { if (dragNode && player.isAlive) { // Clamp to game area, avoid top 100px (menu) var px = Math.max(60, Math.min(GAME_WIDTH - 60, x)); var py = Math.max(200, Math.min(GAME_HEIGHT - 60, y)); dragNode.x = px; dragNode.y = py; } } game.move = handleMove; game.down = function (x, y, obj) { if (player.isAlive) { dragNode = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // Helper: collision check (circle vs circle) function circlesIntersect(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < a.radius + b.radius; } // Helper: spawn enemy function spawnEnemy() { var enemy = new EnemyShip(); // Spawn in random horizontal position, avoid edges enemy.x = 120 + Math.random() * (GAME_WIDTH - 240); enemy.y = -100; enemies.push(enemy); game.addChild(enemy); } // Helper: spawn enemy bullet function spawnEnemyBullet(enemy) { var bullet = new EnemyBullet(); bullet.x = enemy.x; bullet.y = enemy.y + 60; enemyBullets.push(bullet); game.addChild(bullet); } // Helper: spawn player bullet function spawnPlayerBullet() { var bullet = new PlayerBullet(); bullet.x = player.x; bullet.y = player.y - 80; playerBullets.push(bullet); game.addChild(bullet); } // Helper: spawn powerup function spawnPowerUp() { var pu = new PowerUp(); pu.x = 120 + Math.random() * (GAME_WIDTH - 240); pu.y = -60; powerUps.push(pu); game.addChild(pu); } // Powerup effect: double shot function activatePowerUp() { powerUpActive = true; powerUpTimer = LK.ticks + 360; // 6 seconds at 60fps // Flash player ship green tween(player, { tint: 0x44ff88 }, { duration: 200, onFinish: function onFinish() { tween(player, { tint: 0x3399ff }, { duration: 200 }); } }); } // Main game update game.update = function () { if (!gameStarted) { return; } if (!player.isAlive) { return; } // --- Player auto-fire --- if (LK.ticks % (powerUpActive ? 7 : 14) === 0) { if (powerUpActive) { // Double shot: two bullets var b1 = new PlayerBullet(); b1.x = player.x - 36; b1.y = player.y - 80; playerBullets.push(b1); game.addChild(b1); var b2 = new PlayerBullet(); b2.x = player.x + 36; b2.y = player.y - 80; playerBullets.push(b2); game.addChild(b2); } else { spawnPlayerBullet(); } } // --- Enemy spawn --- if (!bossActive && enemySpawnTimer <= LK.ticks && enemiesToSpawn > 0) { spawnEnemy(); enemiesToSpawn--; // Escalate spawn rate var minDelay = 24, maxDelay = 60; var delay = Math.max(minDelay, maxDelay - Math.floor(LK.getScore() / 10) * 4); enemySpawnTimer = LK.ticks + delay; // Update enemies left UI enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length)); } // --- Enemy fire --- if (!bossActive && enemyFireTimer <= LK.ticks && enemies.length > 0) { // Pick random enemy to fire var idx = Math.floor(Math.random() * enemies.length); if (enemies[idx]) { spawnEnemyBullet(enemies[idx]); } // Reduce fire rate: increase min and max fire delay for better balance var minFire = 36, // was 18 maxFire = 100; // was 50 var fireDelay = Math.max(minFire, maxFire - Math.floor(LK.getScore() / 10) * 3); enemyFireTimer = LK.ticks + fireDelay; } // --- Powerup spawn --- if (powerUpSpawnTimer <= LK.ticks) { if (Math.random() < 0.18) { // ~18% chance spawnPowerUp(); } powerUpSpawnTimer = LK.ticks + 300 + Math.floor(Math.random() * 200); } // --- Powerup timer --- if (powerUpActive && LK.ticks > powerUpTimer) { powerUpActive = false; } // --- Update player bullets --- for (var i = playerBullets.length - 1; i >= 0; i--) { var b = playerBullets[i]; b.update(); // Remove if off screen if (b.y < -80) { b.destroy(); playerBullets.splice(i, 1); continue; } // Check collision with enemies for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; if (e.isAlive && circlesIntersect(b, e)) { e.isAlive = false; b.destroy(); playerBullets.splice(i, 1); // --- Quick fire burst effect for enemy hit --- var fireBurst = LK.getAsset('enemyBullet', { width: 120, height: 120, color: 0xffa200, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: e.x, y: e.y }); fireBurst.alpha = 0.85; fireBurst.scaleX = 1.0; fireBurst.scaleY = 1.0; game.addChild(fireBurst); tween(fireBurst, { tint: 0xfff200, scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { fireBurst.destroy(); } }); e.destroy(); enemies.splice(j, 1); // Score (scaled by level) LK.setScore(LK.getScore() + enemyScoreThisLevel); scoreTxt.setText(LK.getScore()); if (LK.getScore() > highScore) { highScore = LK.getScore(); highScoreTxt.setText('High: ' + highScore); storage.highScore = highScore; } // Track kills for level progression enemiesKilledThisLevel++; enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length)); // If all enemies for this level are killed, immediately start a new level (endless progression) if (enemiesKilledThisLevel >= enemiesPerLevel) { // Boss fight every 5 levels if ((currentLevel + 1) % 5 === 0) { // Spawn boss bossActive = true; bossShip = new BossShip(); // BossShip class now sets its own x/y to top center and is stationary game.addChild(bossShip); // Add boss health bar if (bossHealthBar) { LK.gui.top.removeChild(bossHealthBar); } bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, { size: 90, fill: 0xFF2222 }); bossHealthBar.anchor.set(0.5, 0); bossHealthBar.x = GAME_WIDTH / 2; bossHealthBar.y = 80; LK.gui.top.addChild(bossHealthBar); // Play boss music LK.playMusic('boss'); // UI levelTxt.setText('Boss Level ' + (currentLevel + 1)); enemiesLeftTxt.setText('Boss Fight!'); LK.effects.flashScreen(0xffcc00, 600); } else { // Next level! Endless progression currentLevel++; enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1; // escalate number of enemies enemiesToSpawn = enemiesPerLevel; enemiesKilledThisLevel = 0; enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1); // Update UI levelTxt.setText('Level ' + currentLevel); enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length)); // Optionally: flash screen or give feedback LK.effects.flashScreen(0x00ffcc, 400); } } break; } } } // --- Boss fight logic --- if (bossActive && bossShip && bossShip.isAlive) { bossShip.update(); // Boss health bar always at top center and visible only when boss is present if (!bossHealthBar) { bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, { size: 90, fill: 0xFF2222 }); bossHealthBar.anchor.set(0.5, 0); bossHealthBar.x = GAME_WIDTH / 2; bossHealthBar.y = 80; LK.gui.top.addChild(bossHealthBar); } else { bossHealthBar.x = GAME_WIDTH / 2; bossHealthBar.y = 80; bossHealthBar.setText('Boss: ' + bossShip.health + '/' + bossShip.maxHealth); } // Check player bullet collision with boss for (var i = playerBullets.length - 1; i >= 0; i--) { var b = playerBullets[i]; if (bossShip.isAlive && circlesIntersect(bossShip, b)) { bossShip.health--; bossShip.flash(); b.destroy(); playerBullets.splice(i, 1); if (bossShip.health <= 0) { bossShip.isAlive = false; bossActive = false; // Remove boss health bar if (bossHealthBar) { LK.gui.top.removeChild(bossHealthBar); bossHealthBar = null; } // Resume sapce music after boss fight LK.playMusic('sapce'); // Score for boss LK.setScore(LK.getScore() + 100 * currentLevel); scoreTxt.setText(LK.getScore()); if (LK.getScore() > highScore) { highScore = LK.getScore(); highScoreTxt.setText('High: ' + highScore); storage.highScore = highScore; } // Next level currentLevel++; enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1; enemiesToSpawn = enemiesPerLevel; enemiesKilledThisLevel = 0; enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1); levelTxt.setText('Level ' + currentLevel); enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel); LK.effects.flashScreen(0x00ffcc, 600); // Remove boss bossShip.destroy(); bossShip = null; } break; } } // Boss collision with player if (player.isAlive && bossShip && bossShip.isAlive && circlesIntersect(bossShip, player)) { bossShip.isAlive = false; bossActive = false; if (bossHealthBar) { LK.gui.top.removeChild(bossHealthBar); bossHealthBar = null; } // Resume sapce music after boss fight LK.playMusic('sapce'); player.flash(); playerHealth--; updateHearts(); LK.effects.flashScreen(0xff0000, 400); if (playerHealth <= 0) { player.isAlive = false; tween(player, { alpha: 0.2 }, { duration: 400 }); LK.setTimeout(function () { LK.showGameOver(); }, 900); } bossShip.destroy(); bossShip = null; } } // --- Update enemy bullets --- for (var i = enemyBullets.length - 1; i >= 0; i--) { var eb = enemyBullets[i]; eb.update(); if (eb.y > GAME_HEIGHT + 80) { eb.destroy(); enemyBullets.splice(i, 1); continue; } // Check collision with player if (player.isAlive && circlesIntersect(eb, player)) { eb.destroy(); enemyBullets.splice(i, 1); player.flash(); playerHealth--; updateHearts(); LK.effects.flashScreen(0xff0000, 400); if (playerHealth <= 0) { // Game over player.isAlive = false; tween(player, { alpha: 0.2 }, { duration: 400 }); LK.setTimeout(function () { LK.showGameOver(); }, 900); break; } } } // --- Update enemies --- for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; // Track lastY for crossing detection if (typeof e.lastY === "undefined") { e.lastY = e.y; } e.update(); // If enemy crosses into player's area (y > PLAYER_START_Y - 110, i.e. bottom of player ship) if (e.lastY <= PLAYER_START_Y - 110 && e.y > PLAYER_START_Y - 110) { // Only trigger if enemy is alive if (e.isAlive && player.isAlive) { e.isAlive = false; // --- Fiery, intense explosion effect for enemy ship --- var explosion = LK.getAsset('enemyShip', { width: 260, height: 260, color: 0xfff200, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: e.x, y: e.y }); explosion.alpha = 0.95; explosion.scaleX = 1.0; explosion.scaleY = 1.0; game.addChild(explosion); tween(explosion, { tint: 0xff6600, scaleX: 2.7, scaleY: 2.7, alpha: 0.7 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(explosion, { tint: 0xffffcc, scaleX: 3.5, scaleY: 3.5, alpha: 0 }, { duration: 220, easing: tween.cubicIn, onFinish: function onFinish() { explosion.destroy(); } }); } }); tween(e, {}, { duration: 120, onFinish: function onFinish() { e.destroy(); } }); enemies.splice(i, 1); playerHealth--; updateHearts(); LK.effects.flashScreen(0xff0000, 400); // Level progression if this was the last enemy for the level enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length)); if (enemiesKilledThisLevel + 1 >= enemiesPerLevel && enemiesToSpawn === 0 && enemies.length === 0) { // Next level! currentLevel++; enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1; enemiesToSpawn = enemiesPerLevel; enemiesKilledThisLevel = 0; enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1); // Update UI levelTxt.setText('Level ' + currentLevel); enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length)); LK.effects.flashScreen(0x00ffcc, 400); } else { // Only increment kills if not progressing level enemiesKilledThisLevel++; } if (playerHealth <= 0) { // Game over player.isAlive = false; tween(player, { alpha: 0.2 }, { duration: 400 }); LK.setTimeout(function () { LK.showGameOver(); }, 900); break; } continue; } } if (e.y > GAME_HEIGHT + 100) { e.destroy(); enemies.splice(i, 1); continue; } // Check collision with player if (player.isAlive && e.isAlive && circlesIntersect(e, player)) { e.isAlive = false; e.flash(); player.flash(); // --- Fiery, intense explosion effect for enemy ship --- var explosion = LK.getAsset('enemyShip', { width: 260, height: 260, color: 0xfff200, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: e.x, y: e.y }); explosion.alpha = 0.95; explosion.scaleX = 1.0; explosion.scaleY = 1.0; game.addChild(explosion); tween(explosion, { tint: 0xff6600, scaleX: 2.7, scaleY: 2.7, alpha: 0.7 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(explosion, { tint: 0xffffcc, scaleX: 3.5, scaleY: 3.5, alpha: 0 }, { duration: 220, easing: tween.cubicIn, onFinish: function onFinish() { explosion.destroy(); } }); } }); tween(e, {}, { duration: 120, onFinish: function onFinish() { e.destroy(); } }); enemies.splice(i, 1); playerHealth--; updateHearts(); LK.effects.flashScreen(0xff0000, 400); if (playerHealth <= 0) { // Game over player.isAlive = false; tween(player, { alpha: 0.2 }, { duration: 400 }); LK.setTimeout(function () { LK.showGameOver(); }, 900); break; } } // Update lastY for next frame e.lastY = e.y; } // --- Update powerups --- for (var i = powerUps.length - 1; i >= 0; i--) { var pu = powerUps[i]; pu.update(); if (pu.y > GAME_HEIGHT + 80) { pu.destroy(); powerUps.splice(i, 1); continue; } // Check collision with player if (player.isAlive && circlesIntersect(pu, player)) { pu.destroy(); powerUps.splice(i, 1); activatePowerUp(); } } }; // Set initial score LK.setScore(0); scoreTxt.setText('0'); if (LK.getScore() > highScore) { highScore = LK.getScore(); } highScoreTxt.setText('High: ' + highScore); // Reset level system currentLevel = 1; enemiesPerLevel = 6; enemiesToSpawn = enemiesPerLevel; enemiesKilledThisLevel = 0; enemyScoreThisLevel = enemyScoreBase; levelTxt.setText('Level 1'); enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel); playerHealth = 5; updateHearts(); // Reset boss state bossActive = false; if (bossShip) { bossShip.destroy(); bossShip = null; } if (bossHealthBar) { LK.gui.top.removeChild(bossHealthBar); bossHealthBar = null; } // Re-apply selected costume to player if (player && storage.selectedCostume && storage.selectedCostume !== "default") { var costume = null; for (var i = 0; i < costumes.length; i++) { if (costumes[i].id === storage.selectedCostume) costume = costumes[i]; } if (costume) { if (player.children.length > 0) player.removeChild(player.children[0]); var costumeAsset = player.attachAsset(costume.asset, { width: 220, height: 220, anchorX: 0.5, anchorY: 0.5 }); } }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Boss ship class
var BossShip = Container.expand(function () {
var self = Container.call(this);
// Attach boss asset (use enemyShip but much larger, and red tint)
var boss = self.attachAsset('enemyShip', {
width: 480,
height: 480,
color: 0xff0000,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 240;
self.isAlive = true;
self.health = 40;
self.maxHealth = 40;
self.lastY = undefined;
// Boss always at top center, stationary
self.x = GAME_WIDTH / 2;
self.y = 340;
// Boss update
self.update = function () {
// Boss remains stationary at top center
// Unique Skill 1: Radial bullet spread every 120 ticks
if (LK.ticks % 120 === 0) {
var numBullets = 14;
for (var i = 0; i < numBullets; i++) {
var angle = 2 * Math.PI / numBullets * i;
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 220;
// Give bullet a custom direction
bullet.customVX = Math.cos(angle) * 13;
bullet.customVY = Math.sin(angle) * 13;
bullet.update = function () {
this.x += this.customVX;
this.y += this.customVY;
};
enemyBullets.push(bullet);
game.addChild(bullet);
}
}
// Unique Skill 2: Laser attack every 300 ticks (fires a fast straight bullet down the center)
if (LK.ticks % 300 === 0) {
var laser = new EnemyBullet();
laser.x = self.x;
laser.y = self.y + 260;
laser.speed = 38;
laser.radius = 60;
// Make it visually larger
var asset = laser.children[0];
if (asset) {
asset.width = 90;
asset.height = 340;
}
enemyBullets.push(laser);
game.addChild(laser);
}
// Unique Skill 3: Area attack (big slow bullet straight down) every 180 ticks
if (LK.ticks % 180 === 0) {
var area = new EnemyBullet();
area.x = self.x;
area.y = self.y + 240;
area.speed = 10;
area.radius = 120;
var asset = area.children[0];
if (asset) {
asset.width = 220;
asset.height = 120;
}
enemyBullets.push(area);
game.addChild(area);
}
};
// Flash on hit
self.flash = function () {
tween(boss, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(boss, {
tint: 0xff0000
}, {
duration: 120
});
}
});
};
return self;
});
// Enemy bullet class
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (red, 24x48)
var bullet = self.attachAsset('enemyBullet', {
width: 24,
height: 48,
color: 0xff4444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 14;
self.radius = 12;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy ship class
var EnemyShip = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (ellipse, magenta, 220x220)
var enemy = self.attachAsset('enemyShip', {
width: 220,
height: 220,
color: 0xff33cc,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 110;
self.speed = 4 + Math.random() * 2; // Vary speed
self.dirX = (Math.random() - 0.5) * 2; // Slight horizontal drift
// For movement patterns
self.waveOffset = Math.random() * Math.PI * 2;
// For collision
self.isAlive = true;
// Update per frame
self.update = function () {
// Move down, with a sine wave pattern
self.y += self.speed;
self.x += Math.sin(LK.ticks / 30 + self.waveOffset) * 2 + self.dirX;
// Prevent enemy from moving outside the horizontal screen area
var minX = 60;
var maxX = 2048 - 60;
if (self.x < minX) {
self.x = minX;
self.dirX = Math.abs(self.dirX); // bounce right
}
if (self.x > maxX) {
self.x = maxX;
self.dirX = -Math.abs(self.dirX); // bounce left
}
};
// Flash on hit
self.flash = function () {
tween(enemy, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xff33cc
}, {
duration: 120
});
}
});
};
return self;
});
// Player bullet class
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (yellow, 30x60)
var bullet = self.attachAsset('playerBullet', {
width: 30,
height: 60,
color: 0xffe066,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -22;
self.radius = 15;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player spaceship class
var PlayerShip = Container.expand(function () {
var self = Container.call(this);
// Attach ship asset (box, blue, 220x220)
var ship = self.attachAsset('playerShip', {
width: 220,
height: 220,
color: 0x3399ff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Ship properties
self.radius = 110; // for collision
self.isAlive = true;
// Ship flash effect on hit
self.flash = function () {
tween(ship, {
tint: 0xff3333
}, {
duration: 100,
onFinish: function onFinish() {
tween(ship, {
tint: 0x3399ff
}, {
duration: 200
});
}
});
};
return self;
});
// Powerup class
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Attach powerup asset (green ellipse, 120x120)
var pu = self.attachAsset('powerUp', {
width: 120,
height: 120,
color: 0x44ff88,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 60;
self.speed = 6;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// --- Start Screen Overlay ---
var startScreenOverlay = new Container();
// Large main title: "Space Wars"
var mainTitle = new Text2('Space Wars', {
size: 220,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
mainTitle.anchor.set(0.5, 0);
mainTitle.x = GAME_WIDTH / 2;
mainTitle.y = 120;
// Subtitle: "Galactic Showdown"
var subTitle = new Text2('Galactic Showdown', {
size: 110,
fill: 0x99e6ff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
subTitle.anchor.set(0.5, 0);
subTitle.x = GAME_WIDTH / 2;
subTitle.y = mainTitle.y + mainTitle.height + 20;
// --- SHOP MENU UI ---
// Shop state
var shopOpen = false;
var shopOverlay = new Container();
shopOverlay.visible = false;
var shopTitle = new Text2('Costume Shop', {
size: 120,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = GAME_WIDTH / 2;
shopTitle.y = 120;
shopOverlay.addChild(shopTitle);
// Define costumes (id, name, price, asset)
var costumes = [{
id: "default",
name: "Classic",
price: 0,
asset: "playerShip"
}, {
id: "red",
name: "Red Comet",
price: 200,
asset: "enemyShip"
}, {
id: "gold",
name: "Gold Star",
price: 500,
asset: "playerBullet"
}];
// Persistent unlocks
if (!storage.unlockedCostumes) storage.unlockedCostumes = {
"default": true
};
if (!storage.selectedCostume) storage.selectedCostume = "default";
// Shop navigation state
var shopIndex = 0;
function updateShopUI() {
// Remove old preview if any
if (shopOverlay.costumePreview) {
shopOverlay.removeChild(shopOverlay.costumePreview);
shopOverlay.costumePreview = null;
}
var c = costumes[shopIndex];
var preview = LK.getAsset(c.asset, {
width: 320,
height: 320,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 520
});
shopOverlay.addChild(preview);
shopOverlay.costumePreview = preview;
// Update name/price
shopNameTxt.setText(c.name);
if (c.price === 0) {
shopPriceTxt.setText("Unlocked");
} else if (storage.unlockedCostumes[c.id]) {
shopPriceTxt.setText("Unlocked");
} else {
shopPriceTxt.setText("Price: " + c.price + " pts");
}
// Show select/unlock button
if (storage.unlockedCostumes[c.id]) {
shopActionBtnText.setText(storage.selectedCostume === c.id ? "Selected" : "Select");
} else {
shopActionBtnText.setText("Unlock");
}
// Hide select if already selected
shopActionBtn.alpha = storage.selectedCostume === c.id && storage.unlockedCostumes[c.id] ? 0.5 : 1;
}
// Costume name
var shopNameTxt = new Text2('', {
size: 80,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopNameTxt.anchor.set(0.5, 0);
shopNameTxt.x = GAME_WIDTH / 2;
shopNameTxt.y = 900;
shopOverlay.addChild(shopNameTxt);
// Costume price
var shopPriceTxt = new Text2('', {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopPriceTxt.anchor.set(0.5, 0);
shopPriceTxt.x = GAME_WIDTH / 2;
shopPriceTxt.y = 1020;
shopOverlay.addChild(shopPriceTxt);
// Action button (Unlock/Select)
var shopActionBtn = LK.getAsset('playerBullet', {
width: 420,
height: 110,
color: 0x00ccff,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 1200
});
var shopActionBtnText = new Text2('', {
size: 70,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopActionBtnText.anchor.set(0.5, 0.5);
shopActionBtnText.x = shopActionBtn.x;
shopActionBtnText.y = shopActionBtn.y;
shopOverlay.addChild(shopActionBtn);
shopOverlay.addChild(shopActionBtnText);
// Left/right nav buttons
var shopLeftBtn = LK.getAsset('enemyBullet', {
width: 100,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 - 300,
y: 520
});
var shopRightBtn = LK.getAsset('enemyBullet', {
width: 100,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 + 300,
y: 520
});
shopOverlay.addChild(shopLeftBtn);
shopOverlay.addChild(shopRightBtn);
// Close button
var shopCloseBtn = LK.getAsset('playerBullet', {
width: 180,
height: 80,
color: 0x888888,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 1450
});
var shopCloseBtnText = new Text2('Close', {
size: 50,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopCloseBtnText.anchor.set(0.5, 0.5);
shopCloseBtnText.x = shopCloseBtn.x;
shopCloseBtnText.y = shopCloseBtn.y;
shopOverlay.addChild(shopCloseBtn);
shopOverlay.addChild(shopCloseBtnText);
// Shop button on start screen
var shopBtn = LK.getAsset('playerBullet', {
width: 320,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 + 320
});
var shopBtnText = new Text2('SHOP', {
size: 70,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopBtnText.anchor.set(0.5, 0.5);
shopBtnText.x = shopBtn.x;
shopBtnText.y = shopBtn.y;
// Shop button handler
shopBtn.down = function (x, y, obj) {
if (shopOpen) return;
shopOpen = true;
shopOverlay.visible = true;
LK.gui.center.addChild(shopOverlay);
updateShopUI();
// Hide start overlay
if (startScreenOverlay.parent) startScreenOverlay.visible = false;
};
// Shop close handler
shopCloseBtn.down = function (x, y, obj) {
shopOpen = false;
shopOverlay.visible = false;
if (shopOverlay.parent) LK.gui.center.removeChild(shopOverlay);
if (startScreenOverlay && !gameStarted) startScreenOverlay.visible = true;
};
// Shop left/right nav
shopLeftBtn.down = function (x, y, obj) {
shopIndex = (shopIndex + costumes.length - 1) % costumes.length;
updateShopUI();
};
shopRightBtn.down = function (x, y, obj) {
shopIndex = (shopIndex + 1) % costumes.length;
updateShopUI();
};
// Shop action (unlock/select)
shopActionBtn.down = function (x, y, obj) {
var c = costumes[shopIndex];
if (storage.unlockedCostumes[c.id]) {
// Select
storage.selectedCostume = c.id;
updateShopUI();
} else {
// Unlock if enough score
var score = LK.getScore();
if (score >= c.price) {
LK.setScore(score - c.price);
scoreTxt.setText(LK.getScore());
storage.unlockedCostumes[c.id] = true;
storage.selectedCostume = c.id;
updateShopUI();
} else {
// Not enough points, flash price red
shopPriceTxt.setText("Need " + (c.price - score) + " more!");
tween(shopPriceTxt, {
fill: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
shopPriceTxt.setText("Price: " + c.price + " pts");
}
});
}
}
};
// Add to overlay
startScreenOverlay.addChild(mainTitle);
startScreenOverlay.addChild(subTitle);
startScreenOverlay.addChild(shopBtn);
startScreenOverlay.addChild(shopBtnText);
// Play button (large, centered)
var playBtn = LK.getAsset('playerBullet', {
width: 520,
height: 140,
color: 0x00ccff,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 + 120
});
var playBtnText = new Text2('PLAY', {
size: 100,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playBtnText.anchor.set(0.5, 0.5);
playBtnText.x = playBtn.x;
playBtnText.y = playBtn.y;
startScreenOverlay.addChild(playBtn);
startScreenOverlay.addChild(playBtnText);
// Animate main title in
mainTitle.alpha = 0;
tween(mainTitle, {
alpha: 1
}, {
duration: 700,
easing: tween.cubicOut
});
// Animate subtitle in (slightly delayed)
subTitle.alpha = 0;
tween(subTitle, {
alpha: 1
}, {
duration: 700,
delay: 200,
easing: tween.cubicOut
});
// Animate play button in
playBtn.alpha = 0;
playBtnText.alpha = 0;
tween(playBtn, {
alpha: 1
}, {
duration: 700,
easing: tween.cubicOut
});
tween(playBtnText, {
alpha: 1
}, {
duration: 900,
easing: tween.cubicOut
});
// Add to overlay
startScreenOverlay.addChild(mainTitle);
startScreenOverlay.addChild(subTitle);
startScreenOverlay.addChild(playBtn);
startScreenOverlay.addChild(playBtnText);
LK.gui.center.addChild(startScreenOverlay);
// Block gameplay until Play is pressed
var gameStarted = false;
function hideStartScreen() {
if (startScreenOverlay.parent) {
LK.gui.center.removeChild(startScreenOverlay);
}
gameStarted = true;
}
// Play button interaction
playBtn.down = function (x, y, obj) {
if (gameStarted) return;
// Animate out
tween(startScreenOverlay, {
alpha: 0
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
// Start background music only after Play is clicked
LK.playMusic('sapce');
hideStartScreen();
}
});
};
playBtn.move = function (x, y, obj) {};
playBtn.up = function (x, y, obj) {};
// Block all gameplay input until started
var origGameMove = game.move;
var origGameDown = game.down;
var origGameUp = game.up;
game.move = function (x, y, obj) {
if (!gameStarted) return;
if (origGameMove) origGameMove(x, y, obj);
};
game.down = function (x, y, obj) {
if (!gameStarted) return;
if (origGameDown) origGameDown(x, y, obj);
};
game.up = function (x, y, obj) {
if (!gameStarted) return;
if (origGameUp) origGameUp(x, y, obj);
};
// --- Volume Control UI on Pause ---
var volumeSlider = null;
var volumeLabel = null;
var lastPauseState = false;
// Helper to create volume slider UI
function showVolumeSlider() {
if (!LK.isPaused) return;
if (volumeSlider) return; // Already shown
// Create label
volumeLabel = new Text2('Music Volume', {
size: 60,
fill: 0xFFFFFF
});
volumeLabel.anchor.set(0.5, 0.5);
volumeLabel.x = GAME_WIDTH / 2;
volumeLabel.y = GAME_HEIGHT / 2 - 120;
LK.gui.center.addChild(volumeLabel);
// Create slider background
var sliderBg = LK.getAsset('playerBullet', {
width: 500,
height: 32,
color: 0x222222,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2
});
LK.gui.center.addChild(sliderBg);
// Create slider knob
volumeSlider = LK.getAsset('enemyBullet', {
width: 60,
height: 60,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2
});
LK.gui.center.addChild(volumeSlider);
// Set initial knob position based on current music volume
var currentVolume = LK.getMusicVolume ? LK.getMusicVolume() : 1;
var minX = GAME_WIDTH / 2 - 220;
var maxX = GAME_WIDTH / 2 + 220;
volumeSlider.x = minX + (maxX - minX) * currentVolume;
// Drag logic
var dragging = false;
volumeSlider.down = function (x, y, obj) {
dragging = true;
};
volumeSlider.up = function (x, y, obj) {
dragging = false;
};
// Move handler for slider
volumeSlider.move = function (x, y, obj) {
if (!dragging) return;
var minX = GAME_WIDTH / 2 - 220;
var maxX = GAME_WIDTH / 2 + 220;
var px = Math.max(minX, Math.min(maxX, x));
volumeSlider.x = px;
// Calculate volume (0..1)
var vol = (px - minX) / (maxX - minX);
if (vol < 0) vol = 0;
if (vol > 1) vol = 1;
LK.setMusicVolume ? LK.setMusicVolume(vol) : null;
};
// Attach move/up to gui.center for mobile, but only if game is paused
LK.gui.center.move = function (x, y, obj) {
if (LK.isPaused && dragging) {
volumeSlider.move(x, y, obj);
}
};
LK.gui.center.up = function (x, y, obj) {
if (LK.isPaused) {
dragging = false;
}
};
}
// Helper to remove volume slider UI
function hideVolumeSlider() {
if (volumeSlider) {
LK.gui.center.removeChild(volumeSlider);
volumeSlider = null;
}
if (volumeLabel) {
LK.gui.center.removeChild(volumeLabel);
volumeLabel = null;
}
// Remove slider background if present
var children = LK.gui.center.children;
for (var i = children.length - 1; i >= 0; i--) {
var c = children[i];
if (c && c.width === 500 && c.height === 32 && c.color === 0x222222) {
LK.gui.center.removeChild(c);
}
}
// Remove move/up handlers
LK.gui.center.move = null;
LK.gui.center.up = null;
}
// Listen for pause/resume to show/hide slider
LK.on('pause', function () {
showVolumeSlider();
});
LK.on('resume', function () {
hideVolumeSlider();
});
var backgroundImg = LK.getAsset('background', {
width: GAME_WIDTH,
height: GAME_HEIGHT,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(backgroundImg, 0); // Always at the back layer
// Play sapce music at game start
// (Music will be started after Play is clicked, see playBtn.down handler)
// LK.playMusic('sapce');
// Health bar asset (simple red bar, 600x40)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GAME_HEIGHT - 350;
// Level system variables
var currentLevel = 1;
var enemiesPerLevel = 6; // base number of enemies for level 1
var enemiesToSpawn = enemiesPerLevel;
var enemiesKilledThisLevel = 0;
var enemyScoreBase = 10; // base score for level 1
var enemyScoreThisLevel = enemyScoreBase;
// Game state
var player;
var enemies = [];
var playerBullets = [];
var enemyBullets = [];
var powerUps = [];
var dragNode = null;
var lastScore = 0;
var scoreTxt;
var powerUpActive = false;
var powerUpTimer = 0;
var enemySpawnTimer = 0;
var enemyFireTimer = 0;
var powerUpSpawnTimer = 0;
var gameOver = false;
// Boss state
var bossActive = false;
var bossShip = null;
var bossHealthBar = null;
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Import storage plugin for persistent high score
// High score display (top right, avoid top right 100x100)
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('High: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreTxt.anchor.set(1, 0);
highScoreTxt.x = GAME_WIDTH - 120;
highScoreTxt.y = 0;
LK.gui.top.addChild(highScoreTxt);
// Health system
var playerHealth = 5;
// Heart icon assets (use 5 separate heart images for live updating)
var heartIcons = [];
var heartAssetNames = ['heart1', 'heart2', 'heart3', 'heart4', 'heart5'];
for (var i = 0; i < 5; i++) {
var heart = LK.getAsset(heartAssetNames[i], {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: 200 + i * 70,
y: 30
});
LK.gui.top.addChild(heart);
heartIcons.push(heart);
}
// Helper to update heart icons based on playerHealth
function updateHearts() {
for (var i = 0; i < heartIcons.length; i++) {
heartIcons[i].alpha = i < playerHealth ? 1 : 0.2;
}
}
updateHearts();
// Level display (top left, avoid top left 100x100)
var levelTxt = new Text2('Level 1', {
size: 40,
fill: 0xFFD700
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 180;
levelTxt.y = 100;
LK.gui.top.addChild(levelTxt);
// Enemies left display (top left, avoid top left 100x100)
var enemiesLeftTxt = new Text2('Enemies: 0', {
size: 40,
fill: 0xFF8888
});
enemiesLeftTxt.anchor.set(0, 0);
enemiesLeftTxt.x = 180;
enemiesLeftTxt.y = 60;
LK.gui.top.addChild(enemiesLeftTxt);
// Initialize player
player = new PlayerShip();
// Apply selected costume if not default
if (storage.selectedCostume && storage.selectedCostume !== "default") {
var costume = null;
for (var i = 0; i < costumes.length; i++) {
if (costumes[i].id === storage.selectedCostume) costume = costumes[i];
}
if (costume) {
// Remove old asset and attach new
if (player.children.length > 0) player.removeChild(player.children[0]);
var costumeAsset = player.attachAsset(costume.asset, {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5
});
}
}
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
game.addChild(player);
// Touch/move controls
function handleMove(x, y, obj) {
if (dragNode && player.isAlive) {
// Clamp to game area, avoid top 100px (menu)
var px = Math.max(60, Math.min(GAME_WIDTH - 60, x));
var py = Math.max(200, Math.min(GAME_HEIGHT - 60, y));
dragNode.x = px;
dragNode.y = py;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (player.isAlive) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Helper: collision check (circle vs circle)
function circlesIntersect(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < a.radius + b.radius;
}
// Helper: spawn enemy
function spawnEnemy() {
var enemy = new EnemyShip();
// Spawn in random horizontal position, avoid edges
enemy.x = 120 + Math.random() * (GAME_WIDTH - 240);
enemy.y = -100;
enemies.push(enemy);
game.addChild(enemy);
}
// Helper: spawn enemy bullet
function spawnEnemyBullet(enemy) {
var bullet = new EnemyBullet();
bullet.x = enemy.x;
bullet.y = enemy.y + 60;
enemyBullets.push(bullet);
game.addChild(bullet);
}
// Helper: spawn player bullet
function spawnPlayerBullet() {
var bullet = new PlayerBullet();
bullet.x = player.x;
bullet.y = player.y - 80;
playerBullets.push(bullet);
game.addChild(bullet);
}
// Helper: spawn powerup
function spawnPowerUp() {
var pu = new PowerUp();
pu.x = 120 + Math.random() * (GAME_WIDTH - 240);
pu.y = -60;
powerUps.push(pu);
game.addChild(pu);
}
// Powerup effect: double shot
function activatePowerUp() {
powerUpActive = true;
powerUpTimer = LK.ticks + 360; // 6 seconds at 60fps
// Flash player ship green
tween(player, {
tint: 0x44ff88
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
tint: 0x3399ff
}, {
duration: 200
});
}
});
}
// Main game update
game.update = function () {
if (!gameStarted) {
return;
}
if (!player.isAlive) {
return;
}
// --- Player auto-fire ---
if (LK.ticks % (powerUpActive ? 7 : 14) === 0) {
if (powerUpActive) {
// Double shot: two bullets
var b1 = new PlayerBullet();
b1.x = player.x - 36;
b1.y = player.y - 80;
playerBullets.push(b1);
game.addChild(b1);
var b2 = new PlayerBullet();
b2.x = player.x + 36;
b2.y = player.y - 80;
playerBullets.push(b2);
game.addChild(b2);
} else {
spawnPlayerBullet();
}
}
// --- Enemy spawn ---
if (!bossActive && enemySpawnTimer <= LK.ticks && enemiesToSpawn > 0) {
spawnEnemy();
enemiesToSpawn--;
// Escalate spawn rate
var minDelay = 24,
maxDelay = 60;
var delay = Math.max(minDelay, maxDelay - Math.floor(LK.getScore() / 10) * 4);
enemySpawnTimer = LK.ticks + delay;
// Update enemies left UI
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
}
// --- Enemy fire ---
if (!bossActive && enemyFireTimer <= LK.ticks && enemies.length > 0) {
// Pick random enemy to fire
var idx = Math.floor(Math.random() * enemies.length);
if (enemies[idx]) {
spawnEnemyBullet(enemies[idx]);
}
// Reduce fire rate: increase min and max fire delay for better balance
var minFire = 36,
// was 18
maxFire = 100; // was 50
var fireDelay = Math.max(minFire, maxFire - Math.floor(LK.getScore() / 10) * 3);
enemyFireTimer = LK.ticks + fireDelay;
}
// --- Powerup spawn ---
if (powerUpSpawnTimer <= LK.ticks) {
if (Math.random() < 0.18) {
// ~18% chance
spawnPowerUp();
}
powerUpSpawnTimer = LK.ticks + 300 + Math.floor(Math.random() * 200);
}
// --- Powerup timer ---
if (powerUpActive && LK.ticks > powerUpTimer) {
powerUpActive = false;
}
// --- Update player bullets ---
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
b.update();
// Remove if off screen
if (b.y < -80) {
b.destroy();
playerBullets.splice(i, 1);
continue;
}
// Check collision with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var e = enemies[j];
if (e.isAlive && circlesIntersect(b, e)) {
e.isAlive = false;
b.destroy();
playerBullets.splice(i, 1);
// --- Quick fire burst effect for enemy hit ---
var fireBurst = LK.getAsset('enemyBullet', {
width: 120,
height: 120,
color: 0xffa200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
fireBurst.alpha = 0.85;
fireBurst.scaleX = 1.0;
fireBurst.scaleY = 1.0;
game.addChild(fireBurst);
tween(fireBurst, {
tint: 0xfff200,
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
fireBurst.destroy();
}
});
e.destroy();
enemies.splice(j, 1);
// Score (scaled by level)
LK.setScore(LK.getScore() + enemyScoreThisLevel);
scoreTxt.setText(LK.getScore());
if (LK.getScore() > highScore) {
highScore = LK.getScore();
highScoreTxt.setText('High: ' + highScore);
storage.highScore = highScore;
}
// Track kills for level progression
enemiesKilledThisLevel++;
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
// If all enemies for this level are killed, immediately start a new level (endless progression)
if (enemiesKilledThisLevel >= enemiesPerLevel) {
// Boss fight every 5 levels
if ((currentLevel + 1) % 5 === 0) {
// Spawn boss
bossActive = true;
bossShip = new BossShip();
// BossShip class now sets its own x/y to top center and is stationary
game.addChild(bossShip);
// Add boss health bar
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
}
bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, {
size: 90,
fill: 0xFF2222
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
LK.gui.top.addChild(bossHealthBar);
// Play boss music
LK.playMusic('boss');
// UI
levelTxt.setText('Boss Level ' + (currentLevel + 1));
enemiesLeftTxt.setText('Boss Fight!');
LK.effects.flashScreen(0xffcc00, 600);
} else {
// Next level! Endless progression
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1; // escalate number of enemies
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
// Update UI
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
// Optionally: flash screen or give feedback
LK.effects.flashScreen(0x00ffcc, 400);
}
}
break;
}
}
}
// --- Boss fight logic ---
if (bossActive && bossShip && bossShip.isAlive) {
bossShip.update();
// Boss health bar always at top center and visible only when boss is present
if (!bossHealthBar) {
bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, {
size: 90,
fill: 0xFF2222
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
LK.gui.top.addChild(bossHealthBar);
} else {
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
bossHealthBar.setText('Boss: ' + bossShip.health + '/' + bossShip.maxHealth);
}
// Check player bullet collision with boss
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
if (bossShip.isAlive && circlesIntersect(bossShip, b)) {
bossShip.health--;
bossShip.flash();
b.destroy();
playerBullets.splice(i, 1);
if (bossShip.health <= 0) {
bossShip.isAlive = false;
bossActive = false;
// Remove boss health bar
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Resume sapce music after boss fight
LK.playMusic('sapce');
// Score for boss
LK.setScore(LK.getScore() + 100 * currentLevel);
scoreTxt.setText(LK.getScore());
if (LK.getScore() > highScore) {
highScore = LK.getScore();
highScoreTxt.setText('High: ' + highScore);
storage.highScore = highScore;
}
// Next level
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel);
LK.effects.flashScreen(0x00ffcc, 600);
// Remove boss
bossShip.destroy();
bossShip = null;
}
break;
}
}
// Boss collision with player
if (player.isAlive && bossShip && bossShip.isAlive && circlesIntersect(bossShip, player)) {
bossShip.isAlive = false;
bossActive = false;
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Resume sapce music after boss fight
LK.playMusic('sapce');
player.flash();
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
}
bossShip.destroy();
bossShip = null;
}
}
// --- Update enemy bullets ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
eb.update();
if (eb.y > GAME_HEIGHT + 80) {
eb.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && circlesIntersect(eb, player)) {
eb.destroy();
enemyBullets.splice(i, 1);
player.flash();
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// Track lastY for crossing detection
if (typeof e.lastY === "undefined") {
e.lastY = e.y;
}
e.update();
// If enemy crosses into player's area (y > PLAYER_START_Y - 110, i.e. bottom of player ship)
if (e.lastY <= PLAYER_START_Y - 110 && e.y > PLAYER_START_Y - 110) {
// Only trigger if enemy is alive
if (e.isAlive && player.isAlive) {
e.isAlive = false;
// --- Fiery, intense explosion effect for enemy ship ---
var explosion = LK.getAsset('enemyShip', {
width: 260,
height: 260,
color: 0xfff200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
explosion.alpha = 0.95;
explosion.scaleX = 1.0;
explosion.scaleY = 1.0;
game.addChild(explosion);
tween(explosion, {
tint: 0xff6600,
scaleX: 2.7,
scaleY: 2.7,
alpha: 0.7
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(explosion, {
tint: 0xffffcc,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 220,
easing: tween.cubicIn,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
tween(e, {}, {
duration: 120,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(i, 1);
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
// Level progression if this was the last enemy for the level
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
if (enemiesKilledThisLevel + 1 >= enemiesPerLevel && enemiesToSpawn === 0 && enemies.length === 0) {
// Next level!
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
// Update UI
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
LK.effects.flashScreen(0x00ffcc, 400);
} else {
// Only increment kills if not progressing level
enemiesKilledThisLevel++;
}
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
continue;
}
}
if (e.y > GAME_HEIGHT + 100) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && e.isAlive && circlesIntersect(e, player)) {
e.isAlive = false;
e.flash();
player.flash();
// --- Fiery, intense explosion effect for enemy ship ---
var explosion = LK.getAsset('enemyShip', {
width: 260,
height: 260,
color: 0xfff200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
explosion.alpha = 0.95;
explosion.scaleX = 1.0;
explosion.scaleY = 1.0;
game.addChild(explosion);
tween(explosion, {
tint: 0xff6600,
scaleX: 2.7,
scaleY: 2.7,
alpha: 0.7
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(explosion, {
tint: 0xffffcc,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 220,
easing: tween.cubicIn,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
tween(e, {}, {
duration: 120,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(i, 1);
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
}
// Update lastY for next frame
e.lastY = e.y;
}
// --- Update powerups ---
for (var i = powerUps.length - 1; i >= 0; i--) {
var pu = powerUps[i];
pu.update();
if (pu.y > GAME_HEIGHT + 80) {
pu.destroy();
powerUps.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && circlesIntersect(pu, player)) {
pu.destroy();
powerUps.splice(i, 1);
activatePowerUp();
}
}
};
// Set initial score
LK.setScore(0);
scoreTxt.setText('0');
if (LK.getScore() > highScore) {
highScore = LK.getScore();
}
highScoreTxt.setText('High: ' + highScore);
// Reset level system
currentLevel = 1;
enemiesPerLevel = 6;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase;
levelTxt.setText('Level 1');
enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel);
playerHealth = 5;
updateHearts();
// Reset boss state
bossActive = false;
if (bossShip) {
bossShip.destroy();
bossShip = null;
}
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Re-apply selected costume to player
if (player && storage.selectedCostume && storage.selectedCostume !== "default") {
var costume = null;
for (var i = 0; i < costumes.length; i++) {
if (costumes[i].id === storage.selectedCostume) costume = costumes[i];
}
if (costume) {
if (player.children.length > 0) player.removeChild(player.children[0]);
var costumeAsset = player.attachAsset(costume.asset, {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5
});
}
}