/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, level: 1 }); /**** * Classes ****/ var Asteroid = Container.expand(function () { var self = Container.call(this); // Random size asteroid var size = Math.random() * 0.7 + 0.7; // 0.7 to 1.4 var asteroidGraphics = self.attachAsset('asteroid', { anchorX: 0.5, anchorY: 0.5, scaleX: size, scaleY: size }); self.health = 30 * size; self.speed = 3 / size; // Bigger asteroids move slower self.rotationSpeed = (Math.random() - 0.5) * 0.05; self.points = Math.floor(50 * size); self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0x888888, 200); LK.getSound('enemyShoot', { volume: 0.5 }).play(); // Play hit sound tween(asteroidGraphics, { tint: 0xFFFFFF }, { duration: 50, onFinish: function onFinish() { asteroidGraphics.tint = 0x999999; } }); if (self.health <= 0) { self.onDestroyed(); } }; self.onDestroyed = function () { // Add points to score LK.setScore(LK.getScore() + self.points); // Create explosion var explosion = new Explosion(self.x, self.y, size); game.addChild(explosion); LK.getSound('explosion').play(); // Remove from game var index = asteroids.indexOf(self); if (index > -1) { asteroids.splice(index, 1); self.destroy(); } }; self.update = function () { self.y += self.speed; asteroidGraphics.rotation += self.rotationSpeed; // Check if asteroid is off-screen if (self.y > 2732 + 100) { var index = asteroids.indexOf(self); if (index > -1) { asteroids.splice(index, 1); self.destroy(); } } }; return self; }); var Boss = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); self.health = 500; self.maxHealth = 500; self.speed = 2; self.points = 2000; self.active = false; self.phase = 1; self.directionX = 1; self.shootCooldown = 30; self.specialAttackCooldown = 300; // Health bar self.healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5, y: -150 }); self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('enemyShoot', { volume: 0.5 }).play(); // Play hit sound tween(bossGraphics, { tint: 0xFFFFFF }, { duration: 50, onFinish: function onFinish() { bossGraphics.tint = 0xff3300; } }); // Update health bar scale self.healthBar.scaleX = Math.max(0, self.health / self.maxHealth); // Phase transition at 50% health if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; self.speed = 4; LK.effects.flashObject(self, 0xff00ff, 1000); // Purple flash for phase transition } if (self.health <= 0) { self.onDestroyed(); } }; self.onDestroyed = function () { // Add points to score LK.setScore(LK.getScore() + self.points); // Create multiple explosions for (var i = 0; i < 8; i++) { var explosionX = self.x + (Math.random() * 300 - 150); var explosionY = self.y + (Math.random() * 200 - 100); var explosion = new Explosion(explosionX, explosionY, 2); game.addChild(explosion); // Stagger the explosions (function (delay) { LK.setTimeout(function () { LK.getSound('explosion').play(); }, delay * 200); })(i); } // Complete level level++; if (level > storage.level) { storage.level = level; } // Check high score if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } // Show win screen LK.showYouWin(); // Remove from game var index = bosses.indexOf(self); if (index > -1) { bosses.splice(index, 1); self.destroy(); } }; self.shoot = function () { // Basic attack - 3 bullets for (var i = -1; i <= 1; i++) { var bullet = new EnemyBullet(); bullet.x = self.x + i * 80; bullet.y = self.y + 100; game.addChild(bullet); enemyBullets.push(bullet); } LK.getSound('enemyShoot').play(); }; self.specialAttack = function () { // Special attack based on phase if (self.phase === 1) { // Phase 1: Circle of 8 bullets for (var i = 0; i < 8; i++) { var angle = i / 8 * Math.PI * 2; var bullet = new EnemyBullet(); bullet.x = self.x + Math.cos(angle) * 100; bullet.y = self.y + Math.sin(angle) * 100; bullet.directionX = Math.cos(angle); bullet.directionY = Math.sin(angle); game.addChild(bullet); enemyBullets.push(bullet); } } else { // Phase 2: Targeted volley at player for (var i = 0; i < 5; i++) { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + 100; // Target the player var dx = player.x - bullet.x; var dy = player.y - bullet.y; var dist = Math.sqrt(dx * dx + dy * dy); bullet.directionX = dx / dist; bullet.directionY = dy / dist; bullet.speed = 8 + i; game.addChild(bullet); enemyBullets.push(bullet); // Timeout was empty and served no purpose, bullet already added. } } LK.getSound('enemyShoot').play(); }; self.update = function () { if (!self.active) { return; } // Movement self.x += self.speed * self.directionX; // Bounce at screen edges if (self.x < 300) { self.x = 300; self.directionX = 1; } else if (self.x > 2048 - 300) { self.x = 2048 - 300; self.directionX = -1; } // Regular attacks self.shootCooldown--; if (self.shootCooldown <= 0) { self.shoot(); self.shootCooldown = self.phase === 1 ? 60 : 40; } // Special attacks self.specialAttackCooldown--; if (self.specialAttackCooldown <= 0) { self.specialAttack(); self.specialAttackCooldown = 300; } }; self.activate = function () { self.active = true; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 20; self.speed = 2; self.shootCooldown = Math.floor(Math.random() * 120) + 60; self.points = 100; self.movementPattern = "straight"; // Default movement pattern self.directionX = 0; self.directionChangeTimer = 0; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('enemyShoot', { volume: 0.5 }).play(); // Play hit sound tween(enemyGraphics, { tint: 0xFFFFFF }, { duration: 50, onFinish: function onFinish() { enemyGraphics.tint = 0xff0000; } }); if (self.health <= 0) { self.onDestroyed(); } }; self.onDestroyed = function () { // Add points to score LK.setScore(LK.getScore() + self.points); // Create explosion var explosion = new Explosion(self.x, self.y, 1); game.addChild(explosion); LK.getSound('explosion').play(); // Increment enemies defeated count for level progression enemiesDefeated++; // Remove from game first var index = enemies.indexOf(self); if (index > -1) { enemies.splice(index, 1); // Check if should drop powerup (20% chance) after removal is confirmed if (Math.random() < 0.2) { var powerup = new Powerup(); powerup.x = self.x; powerup.y = self.y; game.addChild(powerup); powerups.push(powerup); } // Remove from game self.destroy(); } }; self.shoot = function () { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + 50; game.addChild(bullet); enemyBullets.push(bullet); LK.getSound('enemyShoot').play(); }; self.setMovementPattern = function (pattern) { self.movementPattern = pattern; if (pattern === "zigzag") { self.directionX = Math.random() < 0.5 ? -1 : 1; self.speed = 3; } else if (pattern === "circle") { self.angle = Math.random() * Math.PI * 2; self.centerX = self.x; self.centerY = self.y; self.radius = 100; self.speed = 0.02; } else if (pattern === "sine") { self.startX = self.x; self.amplitude = 150; self.frequency = 0.01; self.angle = Math.random() * Math.PI * 2; } }; self.update = function () { // Movement based on pattern if (self.movementPattern === "straight") { self.y += self.speed; } else if (self.movementPattern === "zigzag") { self.y += self.speed; self.x += self.speed * 2 * self.directionX; self.directionChangeTimer++; if (self.directionChangeTimer > 60) { self.directionX *= -1; self.directionChangeTimer = 0; } // Bounce off screen edges if (self.x < 50) { self.x = 50; self.directionX = 1; } else if (self.x > 2048 - 50) { self.x = 2048 - 50; self.directionX = -1; } } else if (self.movementPattern === "circle") { self.angle += self.speed; self.x = self.centerX + Math.cos(self.angle) * self.radius; self.y = self.centerY + Math.sin(self.angle) * self.radius + self.speed * 20; } else if (self.movementPattern === "sine") { self.y += self.speed * 2; self.angle += self.frequency; self.x = self.startX + Math.sin(self.angle) * self.amplitude; } // Shooting self.shootCooldown--; if (self.shootCooldown <= 0) { self.shoot(); self.shootCooldown = Math.floor(Math.random() * 120) + 120; } // Remove if off screen if (self.y > 2732 + 100) { var index = enemies.indexOf(self); if (index > -1) { enemies.splice(index, 1); self.destroy(); } } }; return self; }); var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 10; self.directionX = 0; self.directionY = 1; self.update = function () { // Move based on direction self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Check if bullet is off-screen if (self.y > 2732 + 50 || self.y < -50 || self.x < -50 || self.x > 2048 + 50) { self.removeFromGame(); } }; self.removeFromGame = function () { var index = enemyBullets.indexOf(self); if (index > -1) { enemyBullets.splice(index, 1); self.destroy(); } }; return self; }); var Explosion = Container.expand(function (x, y, size) { var self = Container.call(this); self.x = x; self.y = y; var explosionGraphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: size || 1, scaleY: size || 1, alpha: 0.9 }); // Animation for explosion self.duration = 30; // 0.5 seconds at 60fps self.timer = 0; self.update = function () { self.timer++; // Scale up and fade out var progress = self.timer / self.duration; explosionGraphics.scaleX = (size || 1) * (1 + progress); explosionGraphics.scaleY = (size || 1) * (1 + progress); explosionGraphics.alpha = 1 - progress; if (self.timer >= self.duration) { self.destroy(); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.speed = 15; self.shootCooldown = 0; self.invulnerable = false; self.powerupTimer = 0; self.powerupActive = false; self.takeDamage = function (amount) { if (self.invulnerable) { return; } self.health -= amount; LK.getSound('enemyShoot', { volume: 0.7 }).play(); // Play player hit sound if (self.health <= 0) { self.health = 0; self.onDestroyed(); } else { // Flash player when hit self.invulnerable = true; LK.effects.flashObject(self, 0xffffff, 500); // Screen shake on hit LK.effects.flashScreen(0xFF0000, 300); // Create small explosions around player for visual feedback for (var i = 0; i < 3; i++) { var explosionX = self.x + (Math.random() * 150 - 75); var explosionY = self.y + (Math.random() * 150 - 75); var smallExplosion = new Explosion(explosionX, explosionY, 0.5); game.addChild(smallExplosion); } LK.setTimeout(function () { self.invulnerable = false; }, 1000); } }; self.applyPowerup = function () { self.powerupActive = true; self.powerupTimer = 300; // 5 seconds at 60fps playerGraphics.tint = 0x00ff00; // Green tint for powered up LK.getSound('powerup').play(); // Visual effect for power-up tween(playerGraphics, { alpha: 0.5, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(playerGraphics, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); // Create a powerup aura if (!self.powerupAura) { self.powerupAura = LK.getAsset('powerup', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, alpha: 0.3, tint: 0x00FFAA }); self.addChild(self.powerupAura); } else { self.powerupAura.alpha = 0.3; } }; self.shoot = function () { if (self.shootCooldown <= 0) { var bullet = new PlayerBullet(); bullet.x = self.x; bullet.y = self.y - 70; game.addChild(bullet); bullets.push(bullet); LK.getSound('playerShoot').play(); // If powered up, shoot 3 bullets instead of 1 if (self.powerupActive) { var bulletLeft = new PlayerBullet(); bulletLeft.x = self.x - 40; bulletLeft.y = self.y - 50; var bulletRight = new PlayerBullet(); bulletRight.x = self.x + 40; bulletRight.y = self.y - 50; game.addChild(bulletLeft); game.addChild(bulletRight); bullets.push(bulletLeft); bullets.push(bulletRight); } self.shootCooldown = self.powerupActive ? 10 : 15; } }; self.onDestroyed = function () { var explosion = new Explosion(self.x, self.y, 2); game.addChild(explosion); LK.getSound('explosion').play(); // Show game over LK.showGameOver(); }; self.update = function () { if (self.shootCooldown > 0) { self.shootCooldown--; } if (self.powerupActive) { self.powerupTimer--; // Animate the powerup aura if (self.powerupAura) { self.powerupAura.rotation += 0.02; // Pulse the aura self.powerupAuraTime = (self.powerupAuraTime || 0) + 0.05; self.powerupAura.alpha = 0.2 + Math.sin(self.powerupAuraTime) * 0.1; self.powerupAura.scale.set(1.8 + Math.sin(self.powerupAuraTime) * 0.2); } // Warning animation when powerup is about to expire if (self.powerupTimer < 60) { // Flash the player when powerup is about to end playerGraphics.alpha = 0.5 + Math.sin(self.powerupTimer * 0.2) * 0.5; } if (self.powerupTimer <= 0) { self.powerupActive = false; playerGraphics.alpha = 1; // Remove powerup aura if (self.powerupAura) { self.powerupAura.destroy(); self.powerupAura = null; } // Reset powerup aura animation timer - Redundant as aura is destroyed // self.powerupAuraTime = 0; // The aura is already destroyed above, removing duplicate block. } } }; return self; }); var PlayerBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -15; // Move upwards self.damage = 10; self.update = function () { self.y += self.speed; // Check if bullet is off-screen if (self.y < -50) { self.removeFromGame(); } }; self.removeFromGame = function () { var index = bullets.indexOf(self); if (index > -1) { bullets.splice(index, 1); self.destroy(); } }; return self; }); var Powerup = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; self.pulsateTimer = 0; self.update = function () { self.y += self.speed; // Pulsate effect self.pulsateTimer += 0.05; var scale = 1 + Math.sin(self.pulsateTimer) * 0.2; powerupGraphics.scaleX = scale; powerupGraphics.scaleY = scale; powerupGraphics.alpha = 0.7 + Math.sin(self.pulsateTimer) * 0.3; // Pulsate alpha // Remove if off screen if (self.y > 2732 + 50) { self.removeFromGame(); } }; self.removeFromGame = function () { var index = powerups.indexOf(self); if (index > -1) { powerups.splice(index, 1); self.destroy(); } }; self.alpha = 0; self.scale.set(0.5); tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000011 // Deep space blue }); /**** * Game Code ****/ // Game state variables var player; var bullets = []; var enemies = []; var enemyBullets = []; var asteroids = []; var powerups = []; var bosses = []; var level = storage.level || 1; var enemySpawnTimer = 0; var asteroidSpawnTimer = 0; var bossSpawned = false; var gameStarted = false; var levelEnemiesRequired; var enemiesDefeated = 0; var dragNode = null; // Set up UI var scoreTxt = new Text2('Score: 0', { size: 70, fill: 0xFFFFFF }); scoreTxt.anchor.set(1, 0); // Anchor top-right scoreTxt.x = -20; // Padding from right edge scoreTxt.y = 20; // Padding from top edge LK.gui.topRight.addChild(scoreTxt); var highScoreTxt = new Text2('High Score: ' + storage.highScore, { size: 60, // Increased size fill: 0xFFFFFF }); highScoreTxt.anchor.set(1, 0); // Anchor top-right highScoreTxt.x = -20; // Align with scoreTxt padding highScoreTxt.y = scoreTxt.y + scoreTxt.height + 10; // Position below scoreTxt LK.gui.topRight.addChild(highScoreTxt); var levelTxt = new Text2('Level: ' + level, { size: 70, fill: 0xFFFFFF }); levelTxt.anchor.set(0, 0); // Anchor top-left now LK.gui.topLeft.addChild(levelTxt); levelTxt.x = 120; // Move slightly right from absolute left levelTxt.y = 20; // Move slightly down from absolute top, matching score padding // Add Glaud text var glaudTxt = new Text2('Glaud', { size: 40, // Small size fill: 0xFF8C00 // Orange color }); glaudTxt.anchor.set(0, 0); // Anchor top-left // Position below levelTxt glaudTxt.x = levelTxt.x; glaudTxt.y = levelTxt.y + levelTxt.height + 10; // 10px padding below LK.gui.topLeft.addChild(glaudTxt); var healthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0.5, scaleX: 1, scaleY: 0.6 }); // Center the health bar horizontally relative to the bottom-center anchor healthBar.x = 0; // Position the bar slightly up from the absolute bottom edge healthBar.y = -50; LK.gui.bottom.addChild(healthBar); var healthTxt = new Text2('Health', { size: 60, fill: 0xFFFFFF }); healthTxt.anchor.set(0.5, 1); // Anchor bottom-center // Position text centered horizontally, just above the health bar healthTxt.x = healthBar.x; healthTxt.y = healthBar.y - healthBar.height * healthBar.scaleY / 2 - 10; LK.gui.bottom.addChild(healthTxt); // Progress bar for level completion var progressBarBg = LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 0.4, tint: 0x555555 }); progressBarBg.x = 2048 / 2; progressBarBg.y = 100; LK.gui.top.addChild(progressBarBg); var progressBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0.5, scaleX: 0, scaleY: 0.3, tint: 0x00FFFF }); progressBar.x = 2048 / 2 - progressBarBg.width / 2; progressBar.y = 100; LK.gui.top.addChild(progressBar); var progressTxt = new Text2('Progress', { size: 50, // Increased size fill: 0xFFFFFF }); progressTxt.anchor.set(0.5, 1); // Anchor bottom-center progressTxt.x = progressBarBg.x; // Position text above the background bar progressTxt.y = progressBarBg.y - progressBarBg.height * progressBarBg.scaleY / 2 - 15; LK.gui.top.addChild(progressTxt); // Initialize player function initPlayer() { player = new Player(); player.x = 2048 / 2; player.y = 2732 + 100; // Start off-screen game.addChild(player); // Animate player entrance tween(player, { y: 2732 - 200, alpha: 1 }, { duration: 1000, easing: tween.easeOutBack }); } // Initialize level function initLevel() { // Clear any existing enemies and bullets for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].destroy(); } for (var i = enemyBullets.length - 1; i >= 0; i--) { enemyBullets[i].destroy(); } for (var i = asteroids.length - 1; i >= 0; i--) { asteroids[i].destroy(); } for (var i = powerups.length - 1; i >= 0; i--) { powerups[i].destroy(); } for (var i = bosses.length - 1; i >= 0; i--) { bosses[i].destroy(); } enemies = []; enemyBullets = []; asteroids = []; powerups = []; bosses = []; bossSpawned = false; enemiesDefeated = 0; // Set level difficulty levelEnemiesRequired = level * 15; enemySpawnTimer = 60; asteroidSpawnTimer = 180; // Update UI levelTxt.setText('Level: ' + level); // Stop pulsating effect on progress text if active if (progressTxt.pulsating) { tween.cancel(progressTxt.scale); tween.cancel(progressTxt); progressTxt.scale.set(1); progressTxt.alpha = 1; progressTxt.pulsating = false; } // Start music LK.playMusic('bgMusic'); } // Spawn enemy with random pattern function spawnEnemy() { var enemy = new Enemy(); // Random x position enemy.x = Math.random() * (2048 - 200) + 100; enemy.y = -100; // Set health and pattern based on level enemy.health = 20 + level * 5; // Choose movement pattern var patterns = ["straight", "zigzag", "sine"]; if (level >= 3) { patterns.push("circle"); } var pattern = patterns[Math.floor(Math.random() * patterns.length)]; enemy.setMovementPattern(pattern); // Add spawn animation enemy.alpha = 0; enemy.scale.set(0.5); game.addChild(enemy); enemies.push(enemy); // Animate entrance tween(enemy, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOutBack }); } // Spawn asteroid function spawnAsteroid() { var asteroid = new Asteroid(); // Random x position asteroid.x = Math.random() * (2048 - 200) + 100; asteroid.y = -100; game.addChild(asteroid); asteroids.push(asteroid); // Add entry animation asteroid.alpha = 0.5; tween(asteroid, { alpha: 1, rotation: Math.random() * Math.PI * 2 }, { duration: 800, easing: tween.easeOut }); } // Spawn boss function spawnBoss() { var boss = new Boss(); boss.x = 2048 / 2; boss.y = 300; // Adjust boss health based on level boss.health = 500 + level * 100; boss.maxHealth = boss.health; game.addChild(boss); bosses.push(boss); LK.getSound('explosion', { volume: 0.8 }).play(); // Play boss spawn sound // Activate boss after entrance animation tween(boss, { y: 300 }, { duration: 2000, easing: tween.easeOutBack, onFinish: function onFinish() { boss.activate(); } }); } // Check collisions function checkCollisions() { // Player bullets vs enemies for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Check against enemies for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (bullet.intersects(enemy)) { // Create small impact effect var impact = new Explosion(bullet.x, bullet.y, 0.3); impact.duration = 15; // Shorter duration for impact game.addChild(impact); enemy.takeDamage(bullet.damage); bullet.removeFromGame(); break; } } // Check against asteroids for (var j = asteroids.length - 1; j >= 0; j--) { var asteroid = asteroids[j]; if (bullet.intersects(asteroid)) { // Create small impact effect var impact = new Explosion(bullet.x, bullet.y, 0.3); impact.duration = 15; // Shorter duration for impact game.addChild(impact); asteroid.takeDamage(bullet.damage); bullet.removeFromGame(); break; } } // Check against bosses for (var j = bosses.length - 1; j >= 0; j--) { var boss = bosses[j]; if (bullet.intersects(boss) && boss.active) { // Create small impact effect var impact = new Explosion(bullet.x, bullet.y, 0.3); impact.duration = 15; // Shorter duration for impact game.addChild(impact); boss.takeDamage(bullet.damage); bullet.removeFromGame(); break; } } } // Enemy bullets vs player for (var i = enemyBullets.length - 1; i >= 0; i--) { var bullet = enemyBullets[i]; if (bullet.intersects(player)) { player.takeDamage(bullet.damage); bullet.removeFromGame(); } } // Player vs enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (player.intersects(enemy)) { player.takeDamage(20); enemy.takeDamage(enemy.health); // Destroy enemy } } // Player vs asteroids for (var i = asteroids.length - 1; i >= 0; i--) { var asteroid = asteroids[i]; if (player.intersects(asteroid)) { player.takeDamage(30); asteroid.takeDamage(asteroid.health); // Destroy asteroid } } // Player vs powerups for (var i = powerups.length - 1; i >= 0; i--) { var powerup = powerups[i]; if (player.intersects(powerup)) { // Remove the powerup first to prevent potential multiple applications powerup.removeFromGame(); // Then apply the effect player.applyPowerup(); } } } // Handle player movement function handleDrag(x, y) { if (dragNode) { dragNode.x = x; dragNode.y = y; // Constrain player to screen bounds if (dragNode.x < 50) { dragNode.x = 50; } if (dragNode.x > 2048 - 50) { dragNode.x = 2048 - 50; } if (dragNode.y < 50) { dragNode.y = 50; } if (dragNode.y > 2732 - 50) { dragNode.y = 2732 - 50; } } } // Event handlers game.down = function (x, y, obj) { // Only set drag if we're started if (gameStarted) { dragNode = player; handleDrag(x, y); } else { // Start game on first touch gameStarted = true; initPlayer(); initLevel(); } }; game.up = function (x, y, obj) { dragNode = null; }; game.move = function (x, y, obj) { handleDrag(x, y); }; // Main game update loop game.update = function () { if (!gameStarted) { // Show start screen text if (!game.startText) { game.startText = new Text2('Tap to Start\nSpace Defender', { size: 150, fill: 0xFFFFFF }); game.startText.anchor.set(0.5, 0.5); game.startText.x = 2048 / 2; game.startText.y = 2732 / 2; game.addChild(game.startText); // Add background stars for start screen game.stars = []; for (var i = 0; i < 50; i++) { var star = LK.getAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: Math.random() * 0.3 + 0.1, scaleY: Math.random() * 0.3 + 0.1, alpha: Math.random() * 0.7 + 0.3, tint: 0xFFFFFF }); star.x = Math.random() * 2048; star.y = Math.random() * 2732; star.speed = Math.random() * 2 + 1; game.addChild(star); game.stars.push(star); } // Add pulsating effect to start text game.pulseValue = 0; // Add pulsing to the start text using tween tween(game.startText, { alpha: 0.5 }, { duration: 1000, easing: tween.easeInOut, loop: true, yoyo: true }); } // Animate stars in start screen if (game.stars) { // Manual scaling removed, handled by tween animation // game.pulseValue += 0.05; // game.startText.scale.set(1 + Math.sin(game.pulseValue) * 0.05); for (var i = 0; i < game.stars.length; i++) { var star = game.stars[i]; star.y += star.speed; if (star.y > 2732) { star.y = 0; star.x = Math.random() * 2048; } } } return; } else if (game.startText) { game.startText.destroy(); game.startText = null; // Clean up stars if (game.stars) { for (var i = 0; i < game.stars.length; i++) { game.stars[i].destroy(); } game.stars = null; } } // Update UI scoreTxt.setText('Score: ' + LK.getScore()); // Update high score if needed if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); highScoreTxt.setText('High Score: ' + storage.highScore); } // Update progress bar if (!bossSpawned) { var progress = enemiesDefeated / levelEnemiesRequired; progressBar.scaleX = progress; progressTxt.setText('Level Progress: ' + Math.floor(progress * 100) + '%'); } else { progressTxt.setText('BOSS FIGHT!'); if (!progressTxt.pulsating) { progressTxt.pulsating = true; tween(progressTxt.scale, { x: 1.2, y: 1.2 }, { duration: 300, easing: tween.easeInOut, loop: true, yoyo: true }); tween(progressTxt, { alpha: 0.5 }, { duration: 300, easing: tween.easeInOut, loop: true, yoyo: true }); } progressBar.tint = 0xFF0000; progressBar.scaleX = 1; } if (player) { // Update health bar healthBar.scaleX = Math.max(0, player.health / 100); // Auto-fire player.shoot(); } // Spawn enemies if (!bossSpawned) { enemySpawnTimer--; if (enemySpawnTimer <= 0) { spawnEnemy(); // Spawn rate increases with level enemySpawnTimer = Math.max(20, 100 - level * 10); } // Spawn asteroids asteroidSpawnTimer--; if (asteroidSpawnTimer <= 0) { spawnAsteroid(); asteroidSpawnTimer = Math.max(60, 300 - level * 20); } // Check if we should spawn boss if (enemiesDefeated >= levelEnemiesRequired) { bossSpawned = true; spawnBoss(); } } // Update all game objects for (var i = bullets.length - 1; i >= 0; i--) { bullets[i].update(); } for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); } for (var i = enemyBullets.length - 1; i >= 0; i--) { enemyBullets[i].update(); } for (var i = asteroids.length - 1; i >= 0; i--) { asteroids[i].update(); } for (var i = powerups.length - 1; i >= 0; i--) { powerups[i].update(); } for (var i = bosses.length - 1; i >= 0; i--) { bosses[i].update(); } // Check for collisions checkCollisions(); }; // Start game music LK.playMusic('bgMusic', { fade: { start: 0, end: 0.3, duration: 1000 } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Asteroid = Container.expand(function () {
var self = Container.call(this);
// Random size asteroid
var size = Math.random() * 0.7 + 0.7; // 0.7 to 1.4
var asteroidGraphics = self.attachAsset('asteroid', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size,
scaleY: size
});
self.health = 30 * size;
self.speed = 3 / size; // Bigger asteroids move slower
self.rotationSpeed = (Math.random() - 0.5) * 0.05;
self.points = Math.floor(50 * size);
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0x888888, 200);
LK.getSound('enemyShoot', {
volume: 0.5
}).play(); // Play hit sound
tween(asteroidGraphics, {
tint: 0xFFFFFF
}, {
duration: 50,
onFinish: function onFinish() {
asteroidGraphics.tint = 0x999999;
}
});
if (self.health <= 0) {
self.onDestroyed();
}
};
self.onDestroyed = function () {
// Add points to score
LK.setScore(LK.getScore() + self.points);
// Create explosion
var explosion = new Explosion(self.x, self.y, size);
game.addChild(explosion);
LK.getSound('explosion').play();
// Remove from game
var index = asteroids.indexOf(self);
if (index > -1) {
asteroids.splice(index, 1);
self.destroy();
}
};
self.update = function () {
self.y += self.speed;
asteroidGraphics.rotation += self.rotationSpeed;
// Check if asteroid is off-screen
if (self.y > 2732 + 100) {
var index = asteroids.indexOf(self);
if (index > -1) {
asteroids.splice(index, 1);
self.destroy();
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 500;
self.maxHealth = 500;
self.speed = 2;
self.points = 2000;
self.active = false;
self.phase = 1;
self.directionX = 1;
self.shootCooldown = 30;
self.specialAttackCooldown = 300;
// Health bar
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
y: -150
});
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('enemyShoot', {
volume: 0.5
}).play(); // Play hit sound
tween(bossGraphics, {
tint: 0xFFFFFF
}, {
duration: 50,
onFinish: function onFinish() {
bossGraphics.tint = 0xff3300;
}
});
// Update health bar scale
self.healthBar.scaleX = Math.max(0, self.health / self.maxHealth);
// Phase transition at 50% health
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2;
self.speed = 4;
LK.effects.flashObject(self, 0xff00ff, 1000); // Purple flash for phase transition
}
if (self.health <= 0) {
self.onDestroyed();
}
};
self.onDestroyed = function () {
// Add points to score
LK.setScore(LK.getScore() + self.points);
// Create multiple explosions
for (var i = 0; i < 8; i++) {
var explosionX = self.x + (Math.random() * 300 - 150);
var explosionY = self.y + (Math.random() * 200 - 100);
var explosion = new Explosion(explosionX, explosionY, 2);
game.addChild(explosion);
// Stagger the explosions
(function (delay) {
LK.setTimeout(function () {
LK.getSound('explosion').play();
}, delay * 200);
})(i);
}
// Complete level
level++;
if (level > storage.level) {
storage.level = level;
}
// Check high score
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
// Show win screen
LK.showYouWin();
// Remove from game
var index = bosses.indexOf(self);
if (index > -1) {
bosses.splice(index, 1);
self.destroy();
}
};
self.shoot = function () {
// Basic attack - 3 bullets
for (var i = -1; i <= 1; i++) {
var bullet = new EnemyBullet();
bullet.x = self.x + i * 80;
bullet.y = self.y + 100;
game.addChild(bullet);
enemyBullets.push(bullet);
}
LK.getSound('enemyShoot').play();
};
self.specialAttack = function () {
// Special attack based on phase
if (self.phase === 1) {
// Phase 1: Circle of 8 bullets
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var bullet = new EnemyBullet();
bullet.x = self.x + Math.cos(angle) * 100;
bullet.y = self.y + Math.sin(angle) * 100;
bullet.directionX = Math.cos(angle);
bullet.directionY = Math.sin(angle);
game.addChild(bullet);
enemyBullets.push(bullet);
}
} else {
// Phase 2: Targeted volley at player
for (var i = 0; i < 5; i++) {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 100;
// Target the player
var dx = player.x - bullet.x;
var dy = player.y - bullet.y;
var dist = Math.sqrt(dx * dx + dy * dy);
bullet.directionX = dx / dist;
bullet.directionY = dy / dist;
bullet.speed = 8 + i;
game.addChild(bullet);
enemyBullets.push(bullet);
// Timeout was empty and served no purpose, bullet already added.
}
}
LK.getSound('enemyShoot').play();
};
self.update = function () {
if (!self.active) {
return;
}
// Movement
self.x += self.speed * self.directionX;
// Bounce at screen edges
if (self.x < 300) {
self.x = 300;
self.directionX = 1;
} else if (self.x > 2048 - 300) {
self.x = 2048 - 300;
self.directionX = -1;
}
// Regular attacks
self.shootCooldown--;
if (self.shootCooldown <= 0) {
self.shoot();
self.shootCooldown = self.phase === 1 ? 60 : 40;
}
// Special attacks
self.specialAttackCooldown--;
if (self.specialAttackCooldown <= 0) {
self.specialAttack();
self.specialAttackCooldown = 300;
}
};
self.activate = function () {
self.active = true;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 20;
self.speed = 2;
self.shootCooldown = Math.floor(Math.random() * 120) + 60;
self.points = 100;
self.movementPattern = "straight"; // Default movement pattern
self.directionX = 0;
self.directionChangeTimer = 0;
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('enemyShoot', {
volume: 0.5
}).play(); // Play hit sound
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 50,
onFinish: function onFinish() {
enemyGraphics.tint = 0xff0000;
}
});
if (self.health <= 0) {
self.onDestroyed();
}
};
self.onDestroyed = function () {
// Add points to score
LK.setScore(LK.getScore() + self.points);
// Create explosion
var explosion = new Explosion(self.x, self.y, 1);
game.addChild(explosion);
LK.getSound('explosion').play();
// Increment enemies defeated count for level progression
enemiesDefeated++;
// Remove from game first
var index = enemies.indexOf(self);
if (index > -1) {
enemies.splice(index, 1);
// Check if should drop powerup (20% chance) after removal is confirmed
if (Math.random() < 0.2) {
var powerup = new Powerup();
powerup.x = self.x;
powerup.y = self.y;
game.addChild(powerup);
powerups.push(powerup);
}
// Remove from game
self.destroy();
}
};
self.shoot = function () {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 50;
game.addChild(bullet);
enemyBullets.push(bullet);
LK.getSound('enemyShoot').play();
};
self.setMovementPattern = function (pattern) {
self.movementPattern = pattern;
if (pattern === "zigzag") {
self.directionX = Math.random() < 0.5 ? -1 : 1;
self.speed = 3;
} else if (pattern === "circle") {
self.angle = Math.random() * Math.PI * 2;
self.centerX = self.x;
self.centerY = self.y;
self.radius = 100;
self.speed = 0.02;
} else if (pattern === "sine") {
self.startX = self.x;
self.amplitude = 150;
self.frequency = 0.01;
self.angle = Math.random() * Math.PI * 2;
}
};
self.update = function () {
// Movement based on pattern
if (self.movementPattern === "straight") {
self.y += self.speed;
} else if (self.movementPattern === "zigzag") {
self.y += self.speed;
self.x += self.speed * 2 * self.directionX;
self.directionChangeTimer++;
if (self.directionChangeTimer > 60) {
self.directionX *= -1;
self.directionChangeTimer = 0;
}
// Bounce off screen edges
if (self.x < 50) {
self.x = 50;
self.directionX = 1;
} else if (self.x > 2048 - 50) {
self.x = 2048 - 50;
self.directionX = -1;
}
} else if (self.movementPattern === "circle") {
self.angle += self.speed;
self.x = self.centerX + Math.cos(self.angle) * self.radius;
self.y = self.centerY + Math.sin(self.angle) * self.radius + self.speed * 20;
} else if (self.movementPattern === "sine") {
self.y += self.speed * 2;
self.angle += self.frequency;
self.x = self.startX + Math.sin(self.angle) * self.amplitude;
}
// Shooting
self.shootCooldown--;
if (self.shootCooldown <= 0) {
self.shoot();
self.shootCooldown = Math.floor(Math.random() * 120) + 120;
}
// Remove if off screen
if (self.y > 2732 + 100) {
var index = enemies.indexOf(self);
if (index > -1) {
enemies.splice(index, 1);
self.destroy();
}
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 10;
self.directionX = 0;
self.directionY = 1;
self.update = function () {
// Move based on direction
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check if bullet is off-screen
if (self.y > 2732 + 50 || self.y < -50 || self.x < -50 || self.x > 2048 + 50) {
self.removeFromGame();
}
};
self.removeFromGame = function () {
var index = enemyBullets.indexOf(self);
if (index > -1) {
enemyBullets.splice(index, 1);
self.destroy();
}
};
return self;
});
var Explosion = Container.expand(function (x, y, size) {
var self = Container.call(this);
self.x = x;
self.y = y;
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size || 1,
scaleY: size || 1,
alpha: 0.9
});
// Animation for explosion
self.duration = 30; // 0.5 seconds at 60fps
self.timer = 0;
self.update = function () {
self.timer++;
// Scale up and fade out
var progress = self.timer / self.duration;
explosionGraphics.scaleX = (size || 1) * (1 + progress);
explosionGraphics.scaleY = (size || 1) * (1 + progress);
explosionGraphics.alpha = 1 - progress;
if (self.timer >= self.duration) {
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.speed = 15;
self.shootCooldown = 0;
self.invulnerable = false;
self.powerupTimer = 0;
self.powerupActive = false;
self.takeDamage = function (amount) {
if (self.invulnerable) {
return;
}
self.health -= amount;
LK.getSound('enemyShoot', {
volume: 0.7
}).play(); // Play player hit sound
if (self.health <= 0) {
self.health = 0;
self.onDestroyed();
} else {
// Flash player when hit
self.invulnerable = true;
LK.effects.flashObject(self, 0xffffff, 500);
// Screen shake on hit
LK.effects.flashScreen(0xFF0000, 300);
// Create small explosions around player for visual feedback
for (var i = 0; i < 3; i++) {
var explosionX = self.x + (Math.random() * 150 - 75);
var explosionY = self.y + (Math.random() * 150 - 75);
var smallExplosion = new Explosion(explosionX, explosionY, 0.5);
game.addChild(smallExplosion);
}
LK.setTimeout(function () {
self.invulnerable = false;
}, 1000);
}
};
self.applyPowerup = function () {
self.powerupActive = true;
self.powerupTimer = 300; // 5 seconds at 60fps
playerGraphics.tint = 0x00ff00; // Green tint for powered up
LK.getSound('powerup').play();
// Visual effect for power-up
tween(playerGraphics, {
alpha: 0.5,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playerGraphics, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
// Create a powerup aura
if (!self.powerupAura) {
self.powerupAura = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: 0.3,
tint: 0x00FFAA
});
self.addChild(self.powerupAura);
} else {
self.powerupAura.alpha = 0.3;
}
};
self.shoot = function () {
if (self.shootCooldown <= 0) {
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y - 70;
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('playerShoot').play();
// If powered up, shoot 3 bullets instead of 1
if (self.powerupActive) {
var bulletLeft = new PlayerBullet();
bulletLeft.x = self.x - 40;
bulletLeft.y = self.y - 50;
var bulletRight = new PlayerBullet();
bulletRight.x = self.x + 40;
bulletRight.y = self.y - 50;
game.addChild(bulletLeft);
game.addChild(bulletRight);
bullets.push(bulletLeft);
bullets.push(bulletRight);
}
self.shootCooldown = self.powerupActive ? 10 : 15;
}
};
self.onDestroyed = function () {
var explosion = new Explosion(self.x, self.y, 2);
game.addChild(explosion);
LK.getSound('explosion').play();
// Show game over
LK.showGameOver();
};
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.powerupActive) {
self.powerupTimer--;
// Animate the powerup aura
if (self.powerupAura) {
self.powerupAura.rotation += 0.02;
// Pulse the aura
self.powerupAuraTime = (self.powerupAuraTime || 0) + 0.05;
self.powerupAura.alpha = 0.2 + Math.sin(self.powerupAuraTime) * 0.1;
self.powerupAura.scale.set(1.8 + Math.sin(self.powerupAuraTime) * 0.2);
}
// Warning animation when powerup is about to expire
if (self.powerupTimer < 60) {
// Flash the player when powerup is about to end
playerGraphics.alpha = 0.5 + Math.sin(self.powerupTimer * 0.2) * 0.5;
}
if (self.powerupTimer <= 0) {
self.powerupActive = false;
playerGraphics.alpha = 1;
// Remove powerup aura
if (self.powerupAura) {
self.powerupAura.destroy();
self.powerupAura = null;
}
// Reset powerup aura animation timer - Redundant as aura is destroyed
// self.powerupAuraTime = 0;
// The aura is already destroyed above, removing duplicate block.
}
}
};
return self;
});
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -15; // Move upwards
self.damage = 10;
self.update = function () {
self.y += self.speed;
// Check if bullet is off-screen
if (self.y < -50) {
self.removeFromGame();
}
};
self.removeFromGame = function () {
var index = bullets.indexOf(self);
if (index > -1) {
bullets.splice(index, 1);
self.destroy();
}
};
return self;
});
var Powerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.pulsateTimer = 0;
self.update = function () {
self.y += self.speed;
// Pulsate effect
self.pulsateTimer += 0.05;
var scale = 1 + Math.sin(self.pulsateTimer) * 0.2;
powerupGraphics.scaleX = scale;
powerupGraphics.scaleY = scale;
powerupGraphics.alpha = 0.7 + Math.sin(self.pulsateTimer) * 0.3; // Pulsate alpha
// Remove if off screen
if (self.y > 2732 + 50) {
self.removeFromGame();
}
};
self.removeFromGame = function () {
var index = powerups.indexOf(self);
if (index > -1) {
powerups.splice(index, 1);
self.destroy();
}
};
self.alpha = 0;
self.scale.set(0.5);
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000011 // Deep space blue
});
/****
* Game Code
****/
// Game state variables
var player;
var bullets = [];
var enemies = [];
var enemyBullets = [];
var asteroids = [];
var powerups = [];
var bosses = [];
var level = storage.level || 1;
var enemySpawnTimer = 0;
var asteroidSpawnTimer = 0;
var bossSpawned = false;
var gameStarted = false;
var levelEnemiesRequired;
var enemiesDefeated = 0;
var dragNode = null;
// Set up UI
var scoreTxt = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0); // Anchor top-right
scoreTxt.x = -20; // Padding from right edge
scoreTxt.y = 20; // Padding from top edge
LK.gui.topRight.addChild(scoreTxt);
var highScoreTxt = new Text2('High Score: ' + storage.highScore, {
size: 60,
// Increased size
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(1, 0); // Anchor top-right
highScoreTxt.x = -20; // Align with scoreTxt padding
highScoreTxt.y = scoreTxt.y + scoreTxt.height + 10; // Position below scoreTxt
LK.gui.topRight.addChild(highScoreTxt);
var levelTxt = new Text2('Level: ' + level, {
size: 70,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0); // Anchor top-left now
LK.gui.topLeft.addChild(levelTxt);
levelTxt.x = 120; // Move slightly right from absolute left
levelTxt.y = 20; // Move slightly down from absolute top, matching score padding
// Add Glaud text
var glaudTxt = new Text2('Glaud', {
size: 40,
// Small size
fill: 0xFF8C00 // Orange color
});
glaudTxt.anchor.set(0, 0); // Anchor top-left
// Position below levelTxt
glaudTxt.x = levelTxt.x;
glaudTxt.y = levelTxt.y + levelTxt.height + 10; // 10px padding below
LK.gui.topLeft.addChild(glaudTxt);
var healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1,
scaleY: 0.6
});
// Center the health bar horizontally relative to the bottom-center anchor
healthBar.x = 0;
// Position the bar slightly up from the absolute bottom edge
healthBar.y = -50;
LK.gui.bottom.addChild(healthBar);
var healthTxt = new Text2('Health', {
size: 60,
fill: 0xFFFFFF
});
healthTxt.anchor.set(0.5, 1); // Anchor bottom-center
// Position text centered horizontally, just above the health bar
healthTxt.x = healthBar.x;
healthTxt.y = healthBar.y - healthBar.height * healthBar.scaleY / 2 - 10;
LK.gui.bottom.addChild(healthTxt);
// Progress bar for level completion
var progressBarBg = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 0.4,
tint: 0x555555
});
progressBarBg.x = 2048 / 2;
progressBarBg.y = 100;
LK.gui.top.addChild(progressBarBg);
var progressBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
scaleX: 0,
scaleY: 0.3,
tint: 0x00FFFF
});
progressBar.x = 2048 / 2 - progressBarBg.width / 2;
progressBar.y = 100;
LK.gui.top.addChild(progressBar);
var progressTxt = new Text2('Progress', {
size: 50,
// Increased size
fill: 0xFFFFFF
});
progressTxt.anchor.set(0.5, 1); // Anchor bottom-center
progressTxt.x = progressBarBg.x;
// Position text above the background bar
progressTxt.y = progressBarBg.y - progressBarBg.height * progressBarBg.scaleY / 2 - 15;
LK.gui.top.addChild(progressTxt);
// Initialize player
function initPlayer() {
player = new Player();
player.x = 2048 / 2;
player.y = 2732 + 100; // Start off-screen
game.addChild(player);
// Animate player entrance
tween(player, {
y: 2732 - 200,
alpha: 1
}, {
duration: 1000,
easing: tween.easeOutBack
});
}
// Initialize level
function initLevel() {
// Clear any existing enemies and bullets
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].destroy();
}
for (var i = asteroids.length - 1; i >= 0; i--) {
asteroids[i].destroy();
}
for (var i = powerups.length - 1; i >= 0; i--) {
powerups[i].destroy();
}
for (var i = bosses.length - 1; i >= 0; i--) {
bosses[i].destroy();
}
enemies = [];
enemyBullets = [];
asteroids = [];
powerups = [];
bosses = [];
bossSpawned = false;
enemiesDefeated = 0;
// Set level difficulty
levelEnemiesRequired = level * 15;
enemySpawnTimer = 60;
asteroidSpawnTimer = 180;
// Update UI
levelTxt.setText('Level: ' + level);
// Stop pulsating effect on progress text if active
if (progressTxt.pulsating) {
tween.cancel(progressTxt.scale);
tween.cancel(progressTxt);
progressTxt.scale.set(1);
progressTxt.alpha = 1;
progressTxt.pulsating = false;
}
// Start music
LK.playMusic('bgMusic');
}
// Spawn enemy with random pattern
function spawnEnemy() {
var enemy = new Enemy();
// Random x position
enemy.x = Math.random() * (2048 - 200) + 100;
enemy.y = -100;
// Set health and pattern based on level
enemy.health = 20 + level * 5;
// Choose movement pattern
var patterns = ["straight", "zigzag", "sine"];
if (level >= 3) {
patterns.push("circle");
}
var pattern = patterns[Math.floor(Math.random() * patterns.length)];
enemy.setMovementPattern(pattern);
// Add spawn animation
enemy.alpha = 0;
enemy.scale.set(0.5);
game.addChild(enemy);
enemies.push(enemy);
// Animate entrance
tween(enemy, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOutBack
});
}
// Spawn asteroid
function spawnAsteroid() {
var asteroid = new Asteroid();
// Random x position
asteroid.x = Math.random() * (2048 - 200) + 100;
asteroid.y = -100;
game.addChild(asteroid);
asteroids.push(asteroid);
// Add entry animation
asteroid.alpha = 0.5;
tween(asteroid, {
alpha: 1,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut
});
}
// Spawn boss
function spawnBoss() {
var boss = new Boss();
boss.x = 2048 / 2;
boss.y = 300;
// Adjust boss health based on level
boss.health = 500 + level * 100;
boss.maxHealth = boss.health;
game.addChild(boss);
bosses.push(boss);
LK.getSound('explosion', {
volume: 0.8
}).play(); // Play boss spawn sound
// Activate boss after entrance animation
tween(boss, {
y: 300
}, {
duration: 2000,
easing: tween.easeOutBack,
onFinish: function onFinish() {
boss.activate();
}
});
}
// Check collisions
function checkCollisions() {
// Player bullets vs enemies
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check against enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Create small impact effect
var impact = new Explosion(bullet.x, bullet.y, 0.3);
impact.duration = 15; // Shorter duration for impact
game.addChild(impact);
enemy.takeDamage(bullet.damage);
bullet.removeFromGame();
break;
}
}
// Check against asteroids
for (var j = asteroids.length - 1; j >= 0; j--) {
var asteroid = asteroids[j];
if (bullet.intersects(asteroid)) {
// Create small impact effect
var impact = new Explosion(bullet.x, bullet.y, 0.3);
impact.duration = 15; // Shorter duration for impact
game.addChild(impact);
asteroid.takeDamage(bullet.damage);
bullet.removeFromGame();
break;
}
}
// Check against bosses
for (var j = bosses.length - 1; j >= 0; j--) {
var boss = bosses[j];
if (bullet.intersects(boss) && boss.active) {
// Create small impact effect
var impact = new Explosion(bullet.x, bullet.y, 0.3);
impact.duration = 15; // Shorter duration for impact
game.addChild(impact);
boss.takeDamage(bullet.damage);
bullet.removeFromGame();
break;
}
}
}
// Enemy bullets vs player
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
if (bullet.intersects(player)) {
player.takeDamage(bullet.damage);
bullet.removeFromGame();
}
}
// Player vs enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (player.intersects(enemy)) {
player.takeDamage(20);
enemy.takeDamage(enemy.health); // Destroy enemy
}
}
// Player vs asteroids
for (var i = asteroids.length - 1; i >= 0; i--) {
var asteroid = asteroids[i];
if (player.intersects(asteroid)) {
player.takeDamage(30);
asteroid.takeDamage(asteroid.health); // Destroy asteroid
}
}
// Player vs powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var powerup = powerups[i];
if (player.intersects(powerup)) {
// Remove the powerup first to prevent potential multiple applications
powerup.removeFromGame();
// Then apply the effect
player.applyPowerup();
}
}
}
// Handle player movement
function handleDrag(x, y) {
if (dragNode) {
dragNode.x = x;
dragNode.y = y;
// Constrain player to screen bounds
if (dragNode.x < 50) {
dragNode.x = 50;
}
if (dragNode.x > 2048 - 50) {
dragNode.x = 2048 - 50;
}
if (dragNode.y < 50) {
dragNode.y = 50;
}
if (dragNode.y > 2732 - 50) {
dragNode.y = 2732 - 50;
}
}
}
// Event handlers
game.down = function (x, y, obj) {
// Only set drag if we're started
if (gameStarted) {
dragNode = player;
handleDrag(x, y);
} else {
// Start game on first touch
gameStarted = true;
initPlayer();
initLevel();
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
game.move = function (x, y, obj) {
handleDrag(x, y);
};
// Main game update loop
game.update = function () {
if (!gameStarted) {
// Show start screen text
if (!game.startText) {
game.startText = new Text2('Tap to Start\nSpace Defender', {
size: 150,
fill: 0xFFFFFF
});
game.startText.anchor.set(0.5, 0.5);
game.startText.x = 2048 / 2;
game.startText.y = 2732 / 2;
game.addChild(game.startText);
// Add background stars for start screen
game.stars = [];
for (var i = 0; i < 50; i++) {
var star = LK.getAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: Math.random() * 0.3 + 0.1,
scaleY: Math.random() * 0.3 + 0.1,
alpha: Math.random() * 0.7 + 0.3,
tint: 0xFFFFFF
});
star.x = Math.random() * 2048;
star.y = Math.random() * 2732;
star.speed = Math.random() * 2 + 1;
game.addChild(star);
game.stars.push(star);
}
// Add pulsating effect to start text
game.pulseValue = 0;
// Add pulsing to the start text using tween
tween(game.startText, {
alpha: 0.5
}, {
duration: 1000,
easing: tween.easeInOut,
loop: true,
yoyo: true
});
}
// Animate stars in start screen
if (game.stars) {
// Manual scaling removed, handled by tween animation
// game.pulseValue += 0.05;
// game.startText.scale.set(1 + Math.sin(game.pulseValue) * 0.05);
for (var i = 0; i < game.stars.length; i++) {
var star = game.stars[i];
star.y += star.speed;
if (star.y > 2732) {
star.y = 0;
star.x = Math.random() * 2048;
}
}
}
return;
} else if (game.startText) {
game.startText.destroy();
game.startText = null;
// Clean up stars
if (game.stars) {
for (var i = 0; i < game.stars.length; i++) {
game.stars[i].destroy();
}
game.stars = null;
}
}
// Update UI
scoreTxt.setText('Score: ' + LK.getScore());
// Update high score if needed
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
highScoreTxt.setText('High Score: ' + storage.highScore);
}
// Update progress bar
if (!bossSpawned) {
var progress = enemiesDefeated / levelEnemiesRequired;
progressBar.scaleX = progress;
progressTxt.setText('Level Progress: ' + Math.floor(progress * 100) + '%');
} else {
progressTxt.setText('BOSS FIGHT!');
if (!progressTxt.pulsating) {
progressTxt.pulsating = true;
tween(progressTxt.scale, {
x: 1.2,
y: 1.2
}, {
duration: 300,
easing: tween.easeInOut,
loop: true,
yoyo: true
});
tween(progressTxt, {
alpha: 0.5
}, {
duration: 300,
easing: tween.easeInOut,
loop: true,
yoyo: true
});
}
progressBar.tint = 0xFF0000;
progressBar.scaleX = 1;
}
if (player) {
// Update health bar
healthBar.scaleX = Math.max(0, player.health / 100);
// Auto-fire
player.shoot();
}
// Spawn enemies
if (!bossSpawned) {
enemySpawnTimer--;
if (enemySpawnTimer <= 0) {
spawnEnemy();
// Spawn rate increases with level
enemySpawnTimer = Math.max(20, 100 - level * 10);
}
// Spawn asteroids
asteroidSpawnTimer--;
if (asteroidSpawnTimer <= 0) {
spawnAsteroid();
asteroidSpawnTimer = Math.max(60, 300 - level * 20);
}
// Check if we should spawn boss
if (enemiesDefeated >= levelEnemiesRequired) {
bossSpawned = true;
spawnBoss();
}
}
// Update all game objects
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
}
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].update();
}
for (var i = asteroids.length - 1; i >= 0; i--) {
asteroids[i].update();
}
for (var i = powerups.length - 1; i >= 0; i--) {
powerups[i].update();
}
for (var i = bosses.length - 1; i >= 0; i--) {
bosses[i].update();
}
// Check for collisions
checkCollisions();
};
// Start game music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
asteroid. In-Game asset. 2d. High contrast. No shadows
spaceship enemy. In-Game asset. 2d. High contrast. No shadows
enemyBullet. In-Game asset. 2d. High contrast. No shadows
playerBullet. In-Game asset. 2d. High contrast. No shadows
explosion. In-Game asset. 2d. High contrast. No shadows
powerup. In-Game asset. 2d. High contrast. No shadows
boss spaceship. In-Game asset. 2d. High contrast. No shadows