/**** * 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
});
}
}