/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ // BigEnemy: shoots large bullets var BigEnemy = Container.expand(function () { var self = Container.call(this); var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); enemyGfx.tint = 0x3f51b5; // blue for big enemy self.radius = enemyGfx.width * 0.75; self.speed = ENEMY_BASE_SPEED * 0.7 + Math.random() * 2; self.moveType = 'straight'; self.shootCooldown = 80 + Math.floor(Math.random() * 40); self.maxHealth = 5; self.health = self.maxHealth; // Health bar var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.18, tint: 0x222222, y: enemyGfx.height * 0.9 }); var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.45, scaleY: 0.12, tint: 0x3f51b5, y: enemyGfx.height * 0.9 }); self.update = function () { self.y += self.speed * 0.8; // Clamp inside screen var margin = self.radius; if (self.x < margin) self.x = margin; if (self.x > GAME_W - margin) self.x = GAME_W - margin; self.shootCooldown--; // Health bar healthBar.scaleX = 1.45 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.takeDamage = function (dmg) { self.health -= dmg; LK.effects.flashObject(self, 0x3f51b5, 100); if (self.health <= 0) self.health = 0; }; return self; }); // BigEnemyBullet: large, slow, dangerous var BigEnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletGfx = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 2.2, tint: 0x3f51b5 }); self.speed = ENEMY_BULLET_SPEED * 0.6; self.dirX = 0; self.dirY = 1; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; }; return self; }); // Enemy (Base class) var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Assign a vivid, random color from a palette for more visible and lively enemies var enemyTints = [0xff1744, // vivid red 0xffe100, // bright yellow 0x00e1ff, // cyan 0x44ff44, // green 0xff00ff, // magenta 0xff8800, // orange 0x9c27b0, // purple 0x00ff88, // teal 0x3f51b5, // blue 0xff4444, // strong red 0xcddc39, // lime 0x607d8b // blue-grey ]; enemyGfx.tint = enemyTints[Math.floor(Math.random() * enemyTints.length)]; self.radius = enemyGfx.width * 0.5; self.speed = ENEMY_BASE_SPEED + Math.random() * 4; self.moveType = Math.random() < 0.5 ? 'straight' : 'sine'; self.sinePhase = Math.random() * Math.PI * 2; self.sineAmp = 120 + Math.random() * 120; self.shootCooldown = 60 + Math.floor(Math.random() * 60); // Health system for enemy self.maxHealth = 1; self.health = self.maxHealth; // Health bar visual (background) var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 0.18, tint: 0x222222, y: enemyGfx.height * 0.7 }); // Health bar visual (foreground) var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.95, scaleY: 0.12, tint: enemyGfx.tint, y: enemyGfx.height * 0.7 }); self.update = function () { if (self.moveType === 'straight') { self.y += self.speed; } else { self.y += self.speed * 0.85; self.x += Math.sin(self.y / 120 + self.sinePhase) * 6; } // Clamp enemy inside left/right screen bounds var margin = self.radius; if (self.x < margin) self.x = margin; if (self.x > GAME_W - margin) self.x = GAME_W - margin; self.shootCooldown--; // Update health bar healthBar.scaleX = 0.95 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.takeDamage = function (dmg) { self.health -= dmg; LK.effects.flashObject(self, enemyGfx.tint, 80); if (self.health <= 0) { self.health = 0; // Death handled in game loop } }; return self; }); // Enemy Bullet var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletGfx = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = ENEMY_BULLET_SPEED; self.dirX = 0; self.dirY = 1; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; }; return self; }); // FastEnemy: moves faster, less health var FastEnemy = Container.expand(function () { var self = Container.call(this); var fastTints = [0x00e1ff, 0xffe100, 0xff00ff, 0x44ff44, 0xff8800, 0x3f51b5]; var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); enemyGfx.tint = fastTints[Math.floor(Math.random() * fastTints.length)]; self.radius = enemyGfx.width * 0.5; self.speed = ENEMY_FAST_SPEED + Math.random() * 4; self.moveType = 'straight'; self.shootCooldown = 40 + Math.floor(Math.random() * 40); self.maxHealth = 1; self.health = self.maxHealth; // Health bar visual (background) var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 0.18, tint: 0x222222, y: enemyGfx.height * 0.7 }); // Health bar visual (foreground) var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.95, scaleY: 0.12, tint: 0x00e1ff, y: enemyGfx.height * 0.7 }); self.update = function () { self.y += self.speed; // Clamp enemy inside left/right screen bounds var margin = self.radius; if (self.x < margin) self.x = margin; if (self.x > GAME_W - margin) self.x = GAME_W - margin; self.shootCooldown--; // Update health bar healthBar.scaleX = 0.95 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.takeDamage = function (dmg) { self.health -= dmg; LK.effects.flashObject(self, 0x00e1ff, 60); if (self.health <= 0) self.health = 0; }; return self; }); // Player Bullet var PlayerBullet = Container.expand(function () { var self = Container.call(this); var bulletGfx = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = PLAYER_BULLET_SPEED; self.update = function () { self.y += self.speed; }; return self; }); // Powerup var Powerup = Container.expand(function () { var self = Container.call(this); // 50 unique powerup types (only keep those that work in this engine) var types = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong']; // Only keep types that are actually implemented and work var workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong']; // Remove types that do not work in this engine (keep only those that can be implemented) workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun']; // Pick a random type, with increased chance for 'rapid' (firerate) and multi-powerup var r = Math.random(); if (r < 0.16) self.type = 'rapid';else if (r < 0.23) self.type = 'shield';else if (r < 0.29) self.type = 'heal';else if (r < 0.34) self.type = 'doubleScore';else if (r < 0.39) self.type = 'slowMotion';else if (r < 0.44) self.type = 'tripleShot';else if (r < 0.48) self.type = 'megaShield';else if (r < 0.52) self.type = 'invincibility';else if (r < 0.56) self.type = 'scoreBomb';else if (r < 0.60) self.type = 'clearBullets';else if (r < 0.64) self.type = 'freezeEnemies';else if (r < 0.68) self.type = 'miniEnemies';else if (r < 0.72) self.type = 'giantShip';else if (r < 0.76) self.type = 'tinyShip';else if (r < 0.80) self.type = 'reverseBullets';else if (r < 0.84) self.type = 'homingBullets';else if (r < 0.88) self.type = 'reflectBullets';else if (r < 0.91) self.type = 'healthBoost';else if (r < 0.94) self.type = 'scoreBoost';else if (r < 0.97) self.type = 'enemySlow';else self.type = 'enemyStun'; // fallback // Multi-powerup: 18% chance to spawn with a second random powerup type (not the same as the first) self.extraTypes = []; if (Math.random() < 0.18) { // Pick a second type, different from the first, only from workingTypes var extraType; do { extraType = workingTypes[Math.floor(Math.random() * workingTypes.length)]; } while (extraType === self.type); self.extraTypes.push(extraType); } var powerupGfx = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); // Tint by type for visual feedback if (self.type === 'rapid') powerupGfx.tint = 0xffe100;else if (self.type === 'shield') powerupGfx.tint = 0x00ffff;else if (self.type === 'heal') powerupGfx.tint = 0x44ff44;else if (self.type === 'doubleScore') powerupGfx.tint = 0xff00ff;else if (self.type === 'slowMotion') powerupGfx.tint = 0x8888ff;else if (self.type === 'tripleShot') powerupGfx.tint = 0xff8800;else if (self.type === 'megaShield') powerupGfx.tint = 0x00ff88;else if (self.type === 'invincibility') powerupGfx.tint = 0xffffff;else if (self.type === 'scoreBomb') powerupGfx.tint = 0xff2222;else if (self.type === 'clearBullets') powerupGfx.tint = 0x00ff00;else if (self.type === 'freezeEnemies') powerupGfx.tint = 0x00e1ff;else if (self.type === 'miniEnemies') powerupGfx.tint = 0xffb300;else if (self.type === 'giantShip') powerupGfx.tint = 0x8e24aa;else if (self.type === 'tinyShip') powerupGfx.tint = 0x3949ab;else if (self.type === 'reverseBullets') powerupGfx.tint = 0x00bcd4;else if (self.type === 'homingBullets') powerupGfx.tint = 0x43a047;else if (self.type === 'reflectBullets') powerupGfx.tint = 0xcddc39;else if (self.type === 'healthBoost') powerupGfx.tint = 0xff1744;else if (self.type === 'scoreBoost') powerupGfx.tint = 0xffea00;else if (self.type === 'enemySlow') powerupGfx.tint = 0x607d8b;else if (self.type === 'enemyStun') powerupGfx.tint = 0x6d4c41; self.speed = 10; self.update = function () { self.y += self.speed; }; return self; }); // Player Ship var Ship = Container.expand(function () { var self = Container.call(this); var shipGfx = self.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5 }); // Use a bright, lively cyan as the default color for the player ship shipGfx.tint = 0x33c1ff; self.radius = shipGfx.width * 0.5; self.shootCooldown = 0; self.rapidFire = false; self.rapidFireTimer = 0; self.shield = false; self.shieldTimer = 0; // Health system for player self.maxHealth = 28; // Reduced survivability for the player ship (harder) self.health = self.maxHealth; // Health bar visual (background) var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 0.25, tint: 0x222222, y: shipGfx.height * 0.7 }); // Health bar visual (foreground) var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 0.18, tint: 0xff4444, y: shipGfx.height * 0.7 }); // Visual shield indicator var shieldGfx = self.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3, tint: 0x00ffff }); shieldGfx.alpha = 0.25; shieldGfx.visible = false; self.update = function () { // Handle powerup timers if (self.rapidFire) { self.rapidFireTimer--; // Visual feedback for rapid fire: pulse ship color shipGfx.tint = LK.ticks % 10 < 5 ? 0xffe100 : 0x33c1ff; if (self.rapidFireTimer <= 0) { self.rapidFire = false; shipGfx.tint = 0x33c1ff; } } else { shipGfx.tint = 0x33c1ff; } if (self.shield) { self.shieldTimer--; shieldGfx.visible = true; if (self.shieldTimer <= 0) { self.shield = false; shieldGfx.visible = false; } } else { shieldGfx.visible = false; } // Update health bar healthBar.scaleX = 2 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.activateRapidFire = function (duration) { self.rapidFire = true; self.rapidFireTimer = duration; }; self.activateShield = function (duration) { self.shield = true; self.shieldTimer = duration; shieldGfx.visible = true; }; self.takeDamage = function (dmg) { if (self.shield) return; self.health -= dmg; LK.effects.flashObject(self, 0xff0000, 200); if (self.health < 0) self.health = 0; }; self.heal = function (amount) { self.health += amount; if (self.health > self.maxHealth) self.health = self.maxHealth; }; return self; }); // SpiralEnemy: moves in a spiral pattern and shoots in bursts var SpiralEnemy = Container.expand(function () { var self = Container.call(this); var spiralTints = [0x9c27b0, 0xff00ff, 0xffe100, 0x00e1ff, 0x44ff44, 0xff8800]; var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); enemyGfx.tint = spiralTints[Math.floor(Math.random() * spiralTints.length)]; self.radius = enemyGfx.width * 0.5; self.speed = 4 + Math.random() * 2; self.angle = Math.random() * Math.PI * 2; self.spiralRadius = 0; self.spiralGrow = 1.2 + Math.random() * 0.7; self.shootCooldown = 40 + Math.floor(Math.random() * 40); self.burstCount = 0; self.maxHealth = 2; self.health = self.maxHealth; // Health bar visual (background) var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 0.18, tint: 0x222222, y: enemyGfx.height * 0.7 }); // Health bar visual (foreground) var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.95, scaleY: 0.12, tint: enemyGfx.tint, y: enemyGfx.height * 0.7 }); self.update = function () { // Spiral movement self.angle += 0.09; self.spiralRadius += self.spiralGrow; self.x += Math.cos(self.angle) * self.spiralGrow * 1.2; self.y += self.speed + Math.sin(self.angle) * 2; // Clamp inside screen var margin = self.radius; if (self.x < margin) self.x = margin; if (self.x > GAME_W - margin) self.x = GAME_W - margin; self.shootCooldown--; // Health bar healthBar.scaleX = 0.95 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.takeDamage = function (dmg) { self.health -= dmg; LK.effects.flashObject(self, 0x9c27b0, 80); if (self.health <= 0) self.health = 0; }; return self; }); // TankEnemy: slow, high health, shoots more var TankEnemy = Container.expand(function () { var self = Container.call(this); var tankTints = [0xff4444, 0x00e1ff, 0xffe100, 0x00ff88, 0x3f51b5, 0xff8800]; var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); enemyGfx.tint = tankTints[Math.floor(Math.random() * tankTints.length)]; self.radius = enemyGfx.width * 0.65; self.speed = ENEMY_TANK_SPEED + Math.random() * 1.2; self.moveType = 'sine'; self.sinePhase = Math.random() * Math.PI * 2; self.sineAmp = 180 + Math.random() * 80; self.shootCooldown = 30 + Math.floor(Math.random() * 30); self.maxHealth = 4; self.health = self.maxHealth; // Health bar visual (background) var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 0.18, tint: 0x222222, y: enemyGfx.height * 0.9 }); // Health bar visual (foreground) var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.25, scaleY: 0.12, tint: 0xff4444, y: enemyGfx.height * 0.9 }); self.update = function () { self.y += self.speed * 0.7; self.x += Math.sin(self.y / 120 + self.sinePhase) * 10; // Clamp enemy inside left/right screen bounds var margin = self.radius; if (self.x < margin) self.x = margin; if (self.x > GAME_W - margin) self.x = GAME_W - margin; self.shootCooldown--; // Update health bar healthBar.scaleX = 1.25 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.takeDamage = function (dmg) { self.health -= dmg; LK.effects.flashObject(self, 0xff4444, 100); if (self.health <= 0) self.health = 0; }; return self; }); // ZigzagEnemy: moves in zigzag, normal health var ZigzagEnemy = Container.expand(function () { var self = Container.call(this); var zigzagTints = [0xffe100, 0xff00ff, 0x44ff44, 0x00e1ff, 0xff8800, 0x9c27b0]; var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); enemyGfx.tint = zigzagTints[Math.floor(Math.random() * zigzagTints.length)]; self.radius = enemyGfx.width * 0.5; self.speed = ENEMY_ZIGZAG_SPEED + Math.random() * 3; self.moveType = 'zigzag'; self.zigzagDir = Math.random() < 0.5 ? -1 : 1; self.zigzagTimer = 0; self.shootCooldown = 60 + Math.floor(Math.random() * 60); self.maxHealth = 2; self.health = self.maxHealth; // Health bar visual (background) var healthBarBg = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 0.18, tint: 0x222222, y: enemyGfx.height * 0.7 }); // Health bar visual (foreground) var healthBar = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.95, scaleY: 0.12, tint: 0xffe100, y: enemyGfx.height * 0.7 }); self.update = function () { self.y += self.speed; self.zigzagTimer++; if (self.zigzagTimer % 30 === 0) self.zigzagDir *= -1; self.x += self.zigzagDir * 18; // Clamp enemy inside left/right screen bounds var margin = self.radius; if (self.x < margin) self.x = margin; if (self.x > GAME_W - margin) self.x = GAME_W - margin; self.shootCooldown--; // Update health bar healthBar.scaleX = 0.95 * (self.health / self.maxHealth); if (self.health < 1) healthBar.scaleX = 0; }; self.takeDamage = function (dmg) { self.health -= dmg; LK.effects.flashObject(self, 0xffe100, 80); if (self.health <= 0) self.health = 0; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Music // Sound effects // Powerup // Enemy bullet // Enemy // Player bullet // Spaceship (player) // Game area var GAME_W = 2048, GAME_H = 2732; // Score var score = 0; // High score (persistent for all users) var highScore = storage.highScore || 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // High score UI (visible to all users) var highScoreTxt = new Text2('High Score: ' + highScore, { size: 60, fill: 0xffe100, stroke: 0x000000, strokeThickness: 6 }); highScoreTxt.anchor.set(0.5, 0); highScoreTxt.x = scoreTxt.x; highScoreTxt.y = scoreTxt.y + 110; LK.gui.top.addChild(highScoreTxt); var multiplierTxt = new Text2('x1', { size: 70, fill: 0x00ff88 }); multiplierTxt.anchor.set(0.5, 0); multiplierTxt.x = scoreTxt.x + 220; multiplierTxt.y = scoreTxt.y + 40; LK.gui.top.addChild(multiplierTxt); // Combo system variables var comboCount = 0; var comboTimer = 0; var lastComboTime = 0; // Double score powerup var doubleScoreActive = false; var doubleScoreTimer = 0; // Slow motion powerup var slowMotionActive = false; var slowMotionTimer = 0; // Triple shot powerup var tripleShotActive = false; var tripleShotTimer = 0; // Invincibility powerup var invincibilityActive = false; var invincibilityTimer = 0; // Player ship var ship = new Ship(); game.addChild(ship); ship.x = GAME_W / 2; ship.y = GAME_H - 350; // --- UI Health Text for Player --- var shipHealthTxt = new Text2('', { size: 60, fill: 0xff4444, stroke: 0x000000, strokeThickness: 6 }); shipHealthTxt.anchor.set(0.5, 1); shipHealthTxt.x = ship.x; shipHealthTxt.y = ship.y - ship.radius - 60; game.addChild(shipHealthTxt); // Arrays for game objects var playerBullets = []; var enemies = []; var enemyBullets = []; var powerups = []; // --- UI Health Text for Enemies --- // We'll keep a parallel array to track health text objects for each enemy var enemyHealthTxts = []; // Dragging var dragNode = null; // Difficulty increased: enemies spawn more frequently var enemySpawnTimer = 0; var enemySpawnInterval = 70; // faster spawn var minEnemyInterval = 20; // minimum interval decreased var enemySpeedInc = 0; // Powerup spawn var powerupTimer = 0; var powerupInterval = 120; // Powerups much more frequent // --- Reflex tuning: adjust bullet and enemy speeds globally --- // Restore normal speeds for enemies and bullets var PLAYER_BULLET_SPEED = -16; // normal bullet speed var ENEMY_BULLET_SPEED = 8; // normal enemy bullet speed var ENEMY_BASE_SPEED = 4; // normal base enemy speed var ENEMY_FAST_SPEED = 7; // normal fast enemy speed var ENEMY_TANK_SPEED = 1.5; // normal tank speed var ENEMY_ZIGZAG_SPEED = 4; // normal zigzag speed var PLAYER_SHOOT_RATE = 12; // normal player shoot rate // Last intersect states var lastShipEnemyIntersect = false; var lastShipEnemyBulletIntersect = false; var lastShipPowerupIntersect = false; // Music LK.playMusic('bgmusic'); // Move handler (drag ship) function handleMove(x, y, obj) { if (dragNode === ship) { // Clamp ship inside game area (with margin) var margin = 80; var nx = Math.max(margin, Math.min(GAME_W - margin, x)); var ny = Math.max(margin, Math.min(GAME_H - margin, y)); ship.x = nx; ship.y = ny; // Touch feedback: scale ship up slightly while dragging, then back ship.scale.set(1.15, 1.15); tween(ship.scale, { x: 1, y: 1 }, { duration: 120 }); } } game.move = handleMove; var dragAnywhereHintShown = false; game.down = function (x, y, obj) { // User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen var dx = x - ship.x, dy = y - ship.y; var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2; var inLowerHalf = y > GAME_H / 2; if (onShip || inLowerHalf) { dragNode = ship; handleMove(x, y, obj); // Visual feedback: flash ship blue for 120ms on drag start LK.effects.flashObject(ship, 0x33c1ff, 120); // Show floating helper text only the first time user drags from lower half (not on ship) if (!onShip && inLowerHalf && !dragAnywhereHintShown) { showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff); dragAnywhereHintShown = true; } } }; game.up = function (x, y, obj) { dragNode = null; }; // Main update loop game.update = function () { // Update ship ship.update(); // --- Update player health UI text --- shipHealthTxt.setText(ship.health + " / " + ship.maxHealth); shipHealthTxt.x = ship.x; shipHealthTxt.y = ship.y - ship.radius - 60; // --- Update enemy health UI text --- // Remove any health text objects for destroyed enemies for (var i = enemyHealthTxts.length - 1; i >= 0; i--) { if (i >= enemies.length || !enemies[i] || enemies[i].destroyed) { if (enemyHealthTxts[i]) { enemyHealthTxts[i].destroy(); } enemyHealthTxts.splice(i, 1); } } // Sync health text objects to enemies for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; var txt = enemyHealthTxts[i]; if (!txt) { txt = new Text2('', { size: 48, fill: 0xffffff, stroke: 0x000000, strokeThickness: 5 }); txt.anchor.set(0.5, 1); game.addChild(txt); enemyHealthTxts[i] = txt; } txt.setText(e.health + " / " + e.maxHealth); txt.x = e.x; txt.y = e.y - (e.radius || 60) - 38; txt.visible = e.health > 0; } // --- Double Score timer --- if (doubleScoreActive) { doubleScoreTimer--; if (doubleScoreTimer <= 0) { doubleScoreActive = false; showFloatingText("Double Score Ended", ship.x, ship.y - 120, 0xff00ff); } } // --- Slow Motion timer --- if (slowMotionActive) { slowMotionTimer--; if (slowMotionTimer <= 0) { slowMotionActive = false; showFloatingText("Speed Restored", ship.x, ship.y - 120, 0x8888ff); } } // --- Triple Shot timer --- if (tripleShotActive) { tripleShotTimer--; if (tripleShotTimer <= 0) { tripleShotActive = false; showFloatingText("Triple Shot Ended", ship.x, ship.y - 120, 0xff8800); } } // --- Invincibility timer --- if (invincibilityActive) { invincibilityTimer--; if (invincibilityTimer <= 0) { invincibilityActive = false; showFloatingText("Invincibility Ended", ship.x, ship.y - 120, 0xffffff); } } // --- Freeze Enemies timer --- if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) { freezeEnemiesTimer--; if (freezeEnemiesTimer <= 0) { showFloatingText("Enemies Unfrozen", ship.x, ship.y - 120, 0x00e1ff); } } // --- Homing Bullets timer --- if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) { homingBulletsTimer--; if (homingBulletsTimer <= 0) { showFloatingText("Homing Ended", ship.x, ship.y - 120, 0x43a047); } } // --- Reflect Bullets timer --- if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) { reflectBulletsTimer--; if (reflectBulletsTimer <= 0) { showFloatingText("Reflect Ended", ship.x, ship.y - 120, 0xcddc39); } } // --- Enemy Slow timer --- if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) { enemySlowTimer--; if (enemySlowTimer <= 0) { showFloatingText("Enemies Normal Speed", ship.x, ship.y - 120, 0x607d8b); } } // --- Enemy Stun timer --- if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) { enemyStunTimer--; if (enemyStunTimer <= 0) { showFloatingText("Enemies Unstunned", ship.x, ship.y - 120, 0x6d4c41); } } // --- Player shooting --- ship.shootCooldown--; var shootRate = ship.rapidFire ? Math.max(6, Math.floor(PLAYER_SHOOT_RATE * 0.33)) : PLAYER_SHOOT_RATE; if (ship.shootCooldown <= 0) { // Auto-fire if (tripleShotActive) { for (var ts = -1; ts <= 1; ts++) { var pb = new PlayerBullet(); pb.x = ship.x + ts * 38; pb.y = ship.y - ship.radius - 30; if (ts !== 0) pb.rotation = ts * 0.18; playerBullets.push(pb); game.addChild(pb); } } else { var pb = new PlayerBullet(); pb.x = ship.x; pb.y = ship.y - ship.radius - 30; playerBullets.push(pb); game.addChild(pb); } ship.shootCooldown = shootRate; LK.getSound('shoot').play(); } // --- 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); } } // --- Update enemies --- for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; // --- Enemy AI: dodge if player is close horizontally --- if (Math.abs(ship.x - e.x) < 180 && Math.abs(ship.y - e.y) < 600) { // Try to dodge left or right, but less distance for fairness if (ship.x < e.x) e.x += 6 + Math.random() * 3;else e.x -= 6 + Math.random() * 3; } // --- Powerup effects: freeze, slow, stun --- var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0; var slowActive = typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0 || slowMotionActive; var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0; if (stunActive) { // Stunned: don't update, don't shoot // Visual feedback: pulse tint if (LK.ticks % 20 < 10) e.tint = 0x6d4c41;else e.tint = 0xffffff; } else if (freezeActive) { // Frozen: don't update, don't shoot if (LK.ticks % 20 < 10) e.tint = 0x00e1ff;else e.tint = 0xffffff; } else { // Slow: update less frequently if (!slowActive || LK.ticks % 2 === 0) { e.update(); } } // Destroy and remove enemies when they move off the bottom of the screen if (e.y > GAME_H + e.radius) { e.destroy(); enemies.splice(i, 1); continue; } // (Removed clamping: enemies can now leave the screen at the bottom) // Enemy shooting if (!freezeActive && !stunActive) { // SpiralEnemy: burst shoot 3 bullets in a spread if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) { for (var b = -1; b <= 1; b++) { var eb = new EnemyBullet(); eb.x = e.x; eb.y = e.y + e.radius + 10; // Spread: aim at ship, but offset angle var dx = ship.x - e.x, dy = ship.y - e.y; var len = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx) + b * 0.18; if (len > 0) { eb.dirX = Math.cos(angle); eb.dirY = Math.sin(angle); } enemyBullets.push(eb); game.addChild(eb); } e.shootCooldown = 120 + Math.floor(Math.random() * 40); } else if (e.shootCooldown <= 0) { // BigEnemy shoots big bullet, others shoot normal var eb; if (e.constructor === BigEnemy) { eb = new BigEnemyBullet(); eb.x = e.x; eb.y = e.y + e.radius + 20; // Aim at ship var dx = ship.x - e.x, dy = ship.y - e.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { eb.dirX = dx / len; eb.dirY = dy / len; } enemyBullets.push(eb); game.addChild(eb); e.shootCooldown = 120 + Math.floor(Math.random() * 40); } else { eb = new EnemyBullet(); eb.x = e.x; eb.y = e.y + e.radius + 10; // Aim at ship var dx = ship.x - e.x, dy = ship.y - e.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { eb.dirX = dx / len; eb.dirY = dy / len; } enemyBullets.push(eb); game.addChild(eb); e.shootCooldown = 90 + Math.floor(Math.random() * 60); } } } } // --- Update enemy bullets --- for (var i = enemyBullets.length - 1; i >= 0; i--) { var eb = enemyBullets[i]; // Slow motion: update less frequently var reflectActive = typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0; if (reflectActive && eb.dirY > 0 && eb.y > ship.y - 200) { // Reflect bullets going downwards, near ship eb.dirY *= -1; showFloatingText("Reflected!", eb.x, eb.y, 0xcddc39); } if (!slowMotionActive || LK.ticks % 2 === 0) { eb.update(); if (eb.x < -100 || eb.x > GAME_W + 100 || eb.y < -100 || eb.y > GAME_H + 100) { eb.destroy(); enemyBullets.splice(i, 1); } } } // --- Homing Bullets effect for player bullets --- if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) { for (var i = 0; i < playerBullets.length; i++) { var pb = playerBullets[i]; // Find nearest enemy var nearest = null, minDist = 99999; for (var j = 0; j < enemies.length; j++) { var dx = enemies[j].x - pb.x; var dy = enemies[j].y - pb.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; nearest = enemies[j]; } } if (nearest && minDist < 400 * 400) { // Adjust bullet direction slightly toward enemy var dx = nearest.x - pb.x; var dy = nearest.y - pb.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { var steer = 0.18; pb.x += dx / len * steer; pb.y += dy / len * steer * 0.5; } } } } // --- Update powerups --- for (var i = powerups.length - 1; i >= 0; i--) { var p = powerups[i]; p.update(); if (p.y > GAME_H + 100) { p.destroy(); powerups.splice(i, 1); } } // --- Collision: Player bullets vs Enemies --- for (var i = playerBullets.length - 1; i >= 0; i--) { var b = playerBullets[i]; for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; if (b.intersects(e)) { // Hit enemy e.takeDamage(2); // Recoil effect for enemy tween(e, { y: e.y - 30 }, { duration: 60, yoyo: true, repeat: 1 }); // Camera shake on hit (simulate with quick flash) LK.getSound('enemyDown').play(); // Combo system variables (global scope, but initialize if undefined) if (typeof comboCount === "undefined") comboCount = 0; if (typeof comboTimer === "undefined") comboTimer = 0; if (typeof lastComboTime === "undefined") lastComboTime = 0; // Score only if enemy dies if (e.health <= 0) { // Combo logic: if last kill was within 2 seconds, increase combo var now = LK.ticks; if (now - lastComboTime < 120) { comboCount++; } else { comboCount = 1; } lastComboTime = now; comboTimer = 120; // 2 seconds to continue combo // Combo bonus: +10 base, +5 per combo after first, multiplied by scoreMultiplier if (typeof scoreMultiplier === "undefined") scoreMultiplier = 1; var comboBonus = 10 + (comboCount > 1 ? (comboCount - 1) * 5 : 0); comboBonus *= scoreMultiplier; if (doubleScoreActive) comboBonus *= 2; score += comboBonus; // Animate score text for feedback scoreTxt.setText(score); if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreTxt !== "undefined") { highScoreTxt.setText("High Score: " + highScore); highScoreTxt.scale.set(1.2, 1.2); tween(highScoreTxt.scale, { x: 1, y: 1 }, { duration: 300 }); } } if (typeof multiplierTxt !== "undefined") { multiplierTxt.setText("x" + scoreMultiplier); multiplierTxt.scale.set(1.3, 1.3); tween(multiplierTxt.scale, { x: 1, y: 1 }, { duration: 200 }); } scoreTxt.scale.set(1.25, 1.25); tween(scoreTxt.scale, { x: 1, y: 1 }, { duration: 200 }); // Show floating text for combo if (comboCount > 1) { showFloatingText("Combo x" + comboCount + "! +" + comboBonus, e.x, e.y - 80, 0xffe100); if (comboCount % 3 === 0) { showFloatingText("Multiplier: x" + scoreMultiplier, e.x, e.y - 160, 0x00ff88); } } else { showFloatingText("+10", e.x, e.y - 80, 0xffffff); } // Fun: random emoji on enemy kill! var emojis = ["🚀", "💥", "✨", "🔥", "😎", "🎉", "🛸", "👾"]; var emoji = emojis[Math.floor(Math.random() * emojis.length)]; showFloatingText(emoji, e.x + (Math.random() * 80 - 40), e.y - 120, 0xffffff); e.destroy(); enemies.splice(j, 1); } b.destroy(); playerBullets.splice(i, 1); break; } } } // --- Combo timer update --- if (typeof comboTimer !== "undefined" && comboTimer > 0) { comboTimer--; if (comboTimer === 0) { comboCount = 0; scoreMultiplier = 1; // Reset multiplier on missed combo } } if (comboCount > 1) { scoreMultiplier = 1 + Math.floor(comboCount / 3); // Every 3 combo increases multiplier by 1 } else { scoreMultiplier = 1; } // --- Collision: Ship vs Enemies --- var shipEnemyIntersect = false; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; if (ship.intersects(e)) { shipEnemyIntersect = true; break; } } if (!lastShipEnemyIntersect && shipEnemyIntersect) { if (invincibilityActive) { LK.effects.flashObject(ship, 0xffffff, 400); showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff); } else if (ship.shield) { // Absorb hit, destroy enemy LK.effects.flashObject(ship, 0x00ffff, 400); for (var i = 0; i < enemies.length; i++) { if (ship.intersects(enemies[i])) { enemies[i].destroy(); enemies.splice(i, 1); break; } } ship.shield = false; ship.shieldTimer = 0; ship.children[1].visible = false; } else { // Take damage ship.takeDamage(2); // (Screen flash removed) LK.getSound('hit').play(); if (ship.health <= 0) { LK.showGameOver(); return; } } } lastShipEnemyIntersect = shipEnemyIntersect; // --- Collision: Ship vs Enemy Bullets --- var shipEnemyBulletIntersect = false; for (var i = 0; i < enemyBullets.length; i++) { var eb = enemyBullets[i]; if (ship.intersects(eb)) { shipEnemyBulletIntersect = true; break; } } if (!lastShipEnemyBulletIntersect && shipEnemyBulletIntersect) { if (invincibilityActive) { LK.effects.flashObject(ship, 0xffffff, 400); showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff); } else if (ship.shield) { LK.effects.flashObject(ship, 0x00ffff, 400); // Remove bullet for (var i = 0; i < enemyBullets.length; i++) { if (ship.intersects(enemyBullets[i])) { enemyBullets[i].destroy(); enemyBullets.splice(i, 1); break; } } ship.shield = false; ship.shieldTimer = 0; ship.children[1].visible = false; } else { // Take damage ship.takeDamage(1); // (Screen flash removed) LK.getSound('hit').play(); if (ship.health <= 0) { LK.showGameOver(); return; } } } lastShipEnemyBulletIntersect = shipEnemyBulletIntersect; // --- Collision: Ship vs Powerups --- var shipPowerupIntersect = false; var _loop = function _loop() { p = powerups[i]; if (ship.intersects(p)) { // --- 20 Powerup Types Logic --- // Helper to apply a powerup type (for multi-powerup support) var applyPowerupType = function applyPowerupType(type) { // 1. rapid if (type === 'rapid') { if (ship.rapidFire) { ship.rapidFireTimer += 240; showFloatingText("Rapid Fire Combo!", ship.x, ship.y - 120, 0xffe100); } else { ship.activateRapidFire(360); showFloatingText("Rapid Fire!", ship.x, ship.y - 120, 0xffe100); } // 2. shield } else if (type === 'shield') { if (ship.shield) { ship.shieldTimer += 320; showFloatingText("Shield Combo!", ship.x, ship.y - 120, 0x00ffff); } else { ship.activateShield(480); showFloatingText("Shield!", ship.x, ship.y - 120, 0x00ffff); } // 3. heal } else if (type === 'heal') { ship.heal(4); showFloatingText("+4 Health", ship.x, ship.y - 120, 0x44ff44); // 4. doubleScore } else if (type === 'doubleScore') { if (doubleScoreActive) { doubleScoreTimer += 400; showFloatingText("Double Score Combo!", ship.x, ship.y - 120, 0xff00ff); } else { doubleScoreActive = true; doubleScoreTimer = 600; showFloatingText("Double Score!", ship.x, ship.y - 120, 0xff00ff); } // 5. slowMotion } else if (type === 'slowMotion') { if (slowMotionActive) { slowMotionTimer += 300; showFloatingText("Slow Motion Combo!", ship.x, ship.y - 120, 0x8888ff); } else { slowMotionActive = true; slowMotionTimer = 420; showFloatingText("Slow Motion!", ship.x, ship.y - 120, 0x8888ff); } // 6. tripleShot } else if (type === 'tripleShot') { if (tripleShotActive) { tripleShotTimer += 360; showFloatingText("Triple Shot Combo!", ship.x, ship.y - 120, 0xff8800); } else { tripleShotActive = true; tripleShotTimer = 540; showFloatingText("Triple Shot!", ship.x, ship.y - 120, 0xff8800); } // 7. megaShield } else if (type === 'megaShield') { if (ship.shield) { ship.shieldTimer += 800; showFloatingText("Mega Shield Combo!", ship.x, ship.y - 120, 0x00ff88); } else { ship.activateShield(1200); showFloatingText("Mega Shield!", ship.x, ship.y - 120, 0x00ff88); } // 8. invincibility } else if (type === 'invincibility') { if (invincibilityActive) { invincibilityTimer += 240; showFloatingText("Invincible Combo!", ship.x, ship.y - 120, 0xffffff); } else { invincibilityActive = true; invincibilityTimer = 360; showFloatingText("Invincible!", ship.x, ship.y - 120, 0xffffff); } // 9. scoreBomb } else if (type === 'scoreBomb') { var killed = 0; for (var si = enemies.length - 1; si >= 0; si--) { if (enemies[si].y > 0) { enemies[si].destroy(); enemies.splice(si, 1); killed++; } } if (killed > 0) { var bombScore = 10 * killed; score += bombScore; scoreTxt.setText(score); showFloatingText("Score Bomb! +" + bombScore, ship.x, ship.y - 120, 0xff2222); } else { showFloatingText("Score Bomb! (No targets)", ship.x, ship.y - 120, 0xff2222); } // 10. clearBullets } else if (type === 'clearBullets') { var cleared = 0; for (var ci = enemyBullets.length - 1; ci >= 0; ci--) { enemyBullets[ci].destroy(); enemyBullets.splice(ci, 1); cleared++; } showFloatingText("Cleared " + cleared + " Bullets!", ship.x, ship.y - 120, 0x00ff00); // 11. freezeEnemies } else if (type === 'freezeEnemies') { if (typeof freezeEnemiesTimer === "undefined") freezeEnemiesTimer = 0; if (freezeEnemiesTimer > 0) { freezeEnemiesTimer += 180; showFloatingText("Freeze Combo!", ship.x, ship.y - 120, 0x00e1ff); } else { freezeEnemiesTimer = 360; showFloatingText("Enemies Frozen!", ship.x, ship.y - 120, 0x00e1ff); } // 12. miniEnemies } else if (type === 'miniEnemies') { for (var mi = 0; mi < enemies.length; mi++) { enemies[mi].scale.set(0.6, 0.6); enemies[mi].radius *= 0.6; } showFloatingText("Mini Enemies!", ship.x, ship.y - 120, 0xffb300); // 13. giantShip } else if (type === 'giantShip') { ship.scale.set(1.7, 1.7); showFloatingText("Giant Ship!", ship.x, ship.y - 120, 0x8e24aa); tween(ship.scale, { x: 1, y: 1 }, { duration: 1200 }); // 14. tinyShip } else if (type === 'tinyShip') { ship.scale.set(0.5, 0.5); showFloatingText("Tiny Ship!", ship.x, ship.y - 120, 0x3949ab); tween(ship.scale, { x: 1, y: 1 }, { duration: 1200 }); // 15. reverseBullets } else if (type === 'reverseBullets') { for (var ri = 0; ri < enemyBullets.length; ri++) { enemyBullets[ri].dirY *= -1; } showFloatingText("Reverse Bullets!", ship.x, ship.y - 120, 0x00bcd4); // 16. homingBullets } else if (type === 'homingBullets') { if (typeof homingBulletsTimer === "undefined") homingBulletsTimer = 0; if (homingBulletsTimer > 0) { homingBulletsTimer += 180; showFloatingText("Homing Combo!", ship.x, ship.y - 120, 0x43a047); } else { homingBulletsTimer = 360; showFloatingText("Homing Bullets!", ship.x, ship.y - 120, 0x43a047); } // 17. reflectBullets } else if (type === 'reflectBullets') { if (typeof reflectBulletsTimer === "undefined") reflectBulletsTimer = 0; if (reflectBulletsTimer > 0) { reflectBulletsTimer += 180; showFloatingText("Reflect Combo!", ship.x, ship.y - 120, 0xcddc39); } else { reflectBulletsTimer = 360; showFloatingText("Reflect Bullets!", ship.x, ship.y - 120, 0xcddc39); } // 18. healthBoost } else if (type === 'healthBoost') { ship.maxHealth += 4; ship.heal(4); showFloatingText("Max Health Up!", ship.x, ship.y - 120, 0xff1744); // 19. scoreBoost } else if (type === 'scoreBoost') { score += 100; scoreTxt.setText(score); showFloatingText("+100 Score!", ship.x, ship.y - 120, 0xffea00); // 20. enemySlow } else if (type === 'enemySlow') { if (typeof enemySlowTimer === "undefined") enemySlowTimer = 0; if (enemySlowTimer > 0) { enemySlowTimer += 180; showFloatingText("Enemy Slow Combo!", ship.x, ship.y - 120, 0x607d8b); } else { enemySlowTimer = 360; showFloatingText("Enemies Slowed!", ship.x, ship.y - 120, 0x607d8b); } // 21. enemyStun (fallback, rarely picked) } else if (type === 'enemyStun') { if (typeof enemyStunTimer === "undefined") enemyStunTimer = 0; if (enemyStunTimer > 0) { enemyStunTimer += 120; showFloatingText("Stun Combo!", ship.x, ship.y - 120, 0x6d4c41); } else { enemyStunTimer = 240; showFloatingText("Enemies Stunned!", ship.x, ship.y - 120, 0x6d4c41); } } }; // Always apply the main powerup type shipPowerupIntersect = true; // Apply powerup LK.getSound('powerup').play(); // Fun: confetti burst effect on powerup collect! for (confetti = 0; confetti < 18; confetti++) { (function () { var part = new Container(); var c = part.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4 + Math.random() * 0.5, scaleY: 0.4 + Math.random() * 0.5, tint: 0xffe100 + Math.floor(Math.random() * 0xffffff) }); part.x = ship.x; part.y = ship.y - 40; part.alpha = 0.85; game.addChild(part); var angle = Math.random() * Math.PI * 2; var dist = 120 + Math.random() * 80; var dx = Math.cos(angle) * dist; var dy = Math.sin(angle) * dist; tween(part, { x: part.x + dx, y: part.y + dy, alpha: 0 }, { duration: 600 + Math.random() * 400, onFinish: function onFinish() { part.destroy(); } }); })(); } applyPowerupType(p.type); // If this powerup has extraTypes (multi-powerup), apply those as well if (p.extraTypes && p.extraTypes.length > 0) { for (et = 0; et < p.extraTypes.length; et++) { applyPowerupType(p.extraTypes[et]); // Show a floating text for the extra powerup showFloatingText("Bonus: " + p.extraTypes[et], ship.x, ship.y - 180 - et * 60, 0xffe100); } } // Combo fusion: if multiple effects are active, show a special floating text! comboActiveCount = 0; if (ship.rapidFire) comboActiveCount++; if (doubleScoreActive) comboActiveCount++; if (tripleShotActive) comboActiveCount++; if (slowMotionActive) comboActiveCount++; if (invincibilityActive) comboActiveCount++; if (ship.shield) comboActiveCount++; if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) comboActiveCount++; if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) comboActiveCount++; if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) comboActiveCount++; if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) comboActiveCount++; if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) comboActiveCount++; if (comboActiveCount >= 3) { showFloatingText("Combo Fusion! (" + comboActiveCount + ")", ship.x, ship.y - 220, 0xffe100); } p.destroy(); powerups.splice(i, 1); } }, p, confetti, et, comboActiveCount; for (var i = powerups.length - 1; i >= 0; i--) { _loop(); } lastShipPowerupIntersect = shipPowerupIntersect; // --- Mission system: show floating text for milestones --- if (score > 0 && score % 100 === 0 && !game['milestone_' + score]) { showFloatingText("Milestone: " + score + "!", GAME_W / 2, 320, 0xffe100); game['milestone_' + score] = true; } // --- Background color cycling every 5000 points after 5000 --- // Define a palette of beautiful background colors, starting with sky blue if (typeof bgColors === "undefined") { var bgColors = [0x87ceeb, // sky blue 0x1a237e, // indigo 0x004d40, // teal dark 0x263238, // blue grey 0x880e4f, // deep pink 0x1565c0, // blue 0x2e7d32, // green 0xff6f00, // orange 0x4a148c, // purple 0x212121, // dark grey 0x00695c, // teal 0x3949ab, // blue indigo 0xc62828, // red 0x00838f, // cyan 0x6d4c41, // brown 0x283593, // deep blue 0x000000 // black (always last) ]; var lastBgScoreStep = 0; // Set initial background to sky blue game.setBackgroundColor(bgColors[0]); } // Calculate which color to use based on score var bgScoreStep = score < 5000 ? 0 : 1 + Math.floor((score - 5000) / 5000); if (bgScoreStep !== lastBgScoreStep) { var paletteLen = bgColors.length; var colorIdx; if (bgScoreStep < paletteLen - 1) { colorIdx = bgScoreStep; } else if (bgScoreStep === paletteLen - 1) { colorIdx = paletteLen - 1; // black } else { // After black, restart from the beginning (skip black until next full cycle) colorIdx = (bgScoreStep - (paletteLen - 1)) % (paletteLen - 1); } game.setBackgroundColor(bgColors[colorIdx]); lastBgScoreStep = bgScoreStep; } // --- Feature selection popup removed --- // --- Enemy spawn --- enemySpawnTimer--; if (enemySpawnTimer <= 0) { // Randomly pick enemy type var enemyTypeRand = Math.random(); var e; if (enemyTypeRand < 0.18) { e = new FastEnemy(); } else if (enemyTypeRand < 0.36) { e = new TankEnemy(); } else if (enemyTypeRand < 0.54) { e = new ZigzagEnemy(); } else if (enemyTypeRand < 0.70) { e = new Enemy(); } else { e = new BigEnemy(); } e.x = 180 + Math.random() * (GAME_W - 360); e.y = -100; // Removed enemy speed scaling with score to keep game speed consistent enemies.push(e); game.addChild(e); // Decrease interval as score increases enemySpawnInterval = Math.max(minEnemyInterval, 60 - Math.floor(score / 100) * 4); enemySpawnTimer = enemySpawnInterval; } // --- Powerup spawn --- powerupTimer--; if (powerupTimer <= 0) { var p = new Powerup(); p.x = 180 + Math.random() * (GAME_W - 360); p.y = -80; powerups.push(p); game.addChild(p); powerupInterval = 120 + Math.floor(Math.random() * 120); powerupTimer = powerupInterval; } }; // Floating text helper for feedback function showFloatingText(txt, x, y, color) { var t = new Text2(txt, { size: 90, fill: color || 0xffffff, stroke: 0x000000, strokeThickness: 8 }); t.anchor.set(0.5, 1); t.x = x; t.y = y; game.addChild(t); tween(t, { y: y - 120, alpha: 0 }, { duration: 700, onFinish: function onFinish() { t.destroy(); } }); } // --- Start Menu Overlay --- var menuOverlay = new Container(); menuOverlay.interactive = true; menuOverlay.x = 0; menuOverlay.y = 0; menuOverlay.width = GAME_W; menuOverlay.height = GAME_H; // Dim background var menuBg = LK.getAsset('playerBullet', { anchorX: 0, anchorY: 0, scaleX: GAME_W / 30, scaleY: GAME_H / 60, tint: 0x000000, alpha: 0.82, x: 0, y: 0 }); menuOverlay.addChild(menuBg); // Title var menuTitle = new Text2("SPACE BLASTERS", { size: 160, fill: 0xffe100, stroke: 0x000000, strokeThickness: 12, align: "center" }); menuTitle.anchor.set(0.5, 0.5); menuTitle.x = GAME_W / 2; menuTitle.y = GAME_H / 2 - 320; menuOverlay.addChild(menuTitle); // Instructions var menuInstructions = new Text2("Drag the ship to move\nAuto-fire enabled\nCollect powerups!", { size: 90, fill: 0xffffff, stroke: 0x000000, strokeThickness: 8, align: "center" }); menuInstructions.anchor.set(0.5, 0.5); menuInstructions.x = GAME_W / 2; menuInstructions.y = GAME_H / 2 - 80; menuOverlay.addChild(menuInstructions); // Start button var startBtn = new Text2("TAP TO START", { size: 110, fill: 0x00ff88, stroke: 0x000000, strokeThickness: 10, align: "center" }); startBtn.anchor.set(0.5, 0.5); startBtn.x = GAME_W / 2; startBtn.y = GAME_H / 2 + 260; menuOverlay.addChild(startBtn); game.addChild(menuOverlay); // Pause game logic until menu is dismissed var gamePausedForMenu = true; var origGameUpdate = game.update; game.update = function () { if (gamePausedForMenu) return; origGameUpdate.call(game); }; // Dismiss menu on tap/click anywhere menuOverlay.down = function (x, y, obj) { if (!gamePausedForMenu) return; gamePausedForMenu = false; tween(menuOverlay, { alpha: 0 }, { duration: 400, onFinish: function onFinish() { menuOverlay.destroy(); } }); }; // Forward input to menu overlay game.down = function (x, y, obj) { if (gamePausedForMenu && menuOverlay.down) { menuOverlay.down(x, y, obj); return; } // User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen var dx = x - ship.x, dy = y - ship.y; var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2; var inLowerHalf = y > GAME_H / 2; if (onShip || inLowerHalf) { dragNode = ship; handleMove(x, y, obj); LK.effects.flashObject(ship, 0x33c1ff, 120); if (!onShip && inLowerHalf && !dragAnywhereHintShown) { showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff); dragAnywhereHintShown = true; } } }; game.move = function (x, y, obj) { if (gamePausedForMenu) return; handleMove(x, y, obj); }; game.up = function (x, y, obj) { if (gamePausedForMenu) return; dragNode = null; }; // Define freezeActive and stunActive for menu overlay logic var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0; var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; if (!freezeActive && !stunActive) { // SpiralEnemy: burst shoot 3 bullets in a spread if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) { for (var b = -1; b <= 1; b++) { var eb = new EnemyBullet(); eb.x = e.x; eb.y = e.y + e.radius + 10; // Spread: aim at ship, but offset angle var dx = ship.x - e.x, dy = ship.y - e.y; var len = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx) + b * 0.18; if (len > 0) { eb.dirX = Math.cos(angle); eb.dirY = Math.sin(angle); } enemyBullets.push(eb); game.addChild(eb); } e.shootCooldown = 120 + Math.floor(Math.random() * 40); } else if (e.shootCooldown <= 0) { var eb = new EnemyBullet(); eb.x = e.x; eb.y = e.y + e.radius + 10; // Aim at ship var dx = ship.x - e.x, dy = ship.y - e.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { eb.dirX = dx / len; eb.dirY = dy / len; } enemyBullets.push(eb); game.addChild(eb); e.shootCooldown = 90 + Math.floor(Math.random() * 60); } } }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// BigEnemy: shoots large bullets
var BigEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
enemyGfx.tint = 0x3f51b5; // blue for big enemy
self.radius = enemyGfx.width * 0.75;
self.speed = ENEMY_BASE_SPEED * 0.7 + Math.random() * 2;
self.moveType = 'straight';
self.shootCooldown = 80 + Math.floor(Math.random() * 40);
self.maxHealth = 5;
self.health = self.maxHealth;
// Health bar
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.9
});
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.45,
scaleY: 0.12,
tint: 0x3f51b5,
y: enemyGfx.height * 0.9
});
self.update = function () {
self.y += self.speed * 0.8;
// Clamp inside screen
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Health bar
healthBar.scaleX = 1.45 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x3f51b5, 100);
if (self.health <= 0) self.health = 0;
};
return self;
});
// BigEnemyBullet: large, slow, dangerous
var BigEnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2,
tint: 0x3f51b5
});
self.speed = ENEMY_BULLET_SPEED * 0.6;
self.dirX = 0;
self.dirY = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// Enemy (Base class)
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign a vivid, random color from a palette for more visible and lively enemies
var enemyTints = [0xff1744,
// vivid red
0xffe100,
// bright yellow
0x00e1ff,
// cyan
0x44ff44,
// green
0xff00ff,
// magenta
0xff8800,
// orange
0x9c27b0,
// purple
0x00ff88,
// teal
0x3f51b5,
// blue
0xff4444,
// strong red
0xcddc39,
// lime
0x607d8b // blue-grey
];
enemyGfx.tint = enemyTints[Math.floor(Math.random() * enemyTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_BASE_SPEED + Math.random() * 4;
self.moveType = Math.random() < 0.5 ? 'straight' : 'sine';
self.sinePhase = Math.random() * Math.PI * 2;
self.sineAmp = 120 + Math.random() * 120;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
// Health system for enemy
self.maxHealth = 1;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: enemyGfx.tint,
y: enemyGfx.height * 0.7
});
self.update = function () {
if (self.moveType === 'straight') {
self.y += self.speed;
} else {
self.y += self.speed * 0.85;
self.x += Math.sin(self.y / 120 + self.sinePhase) * 6;
}
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, enemyGfx.tint, 80);
if (self.health <= 0) {
self.health = 0;
// Death handled in game loop
}
};
return self;
});
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = ENEMY_BULLET_SPEED;
self.dirX = 0;
self.dirY = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// FastEnemy: moves faster, less health
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var fastTints = [0x00e1ff, 0xffe100, 0xff00ff, 0x44ff44, 0xff8800, 0x3f51b5];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = fastTints[Math.floor(Math.random() * fastTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_FAST_SPEED + Math.random() * 4;
self.moveType = 'straight';
self.shootCooldown = 40 + Math.floor(Math.random() * 40);
self.maxHealth = 1;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: 0x00e1ff,
y: enemyGfx.height * 0.7
});
self.update = function () {
self.y += self.speed;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x00e1ff, 60);
if (self.health <= 0) self.health = 0;
};
return self;
});
// Player Bullet
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = PLAYER_BULLET_SPEED;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Powerup
var Powerup = Container.expand(function () {
var self = Container.call(this);
// 50 unique powerup types (only keep those that work in this engine)
var types = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong'];
// Only keep types that are actually implemented and work
var workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong'];
// Remove types that do not work in this engine (keep only those that can be implemented)
workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun'];
// Pick a random type, with increased chance for 'rapid' (firerate) and multi-powerup
var r = Math.random();
if (r < 0.16) self.type = 'rapid';else if (r < 0.23) self.type = 'shield';else if (r < 0.29) self.type = 'heal';else if (r < 0.34) self.type = 'doubleScore';else if (r < 0.39) self.type = 'slowMotion';else if (r < 0.44) self.type = 'tripleShot';else if (r < 0.48) self.type = 'megaShield';else if (r < 0.52) self.type = 'invincibility';else if (r < 0.56) self.type = 'scoreBomb';else if (r < 0.60) self.type = 'clearBullets';else if (r < 0.64) self.type = 'freezeEnemies';else if (r < 0.68) self.type = 'miniEnemies';else if (r < 0.72) self.type = 'giantShip';else if (r < 0.76) self.type = 'tinyShip';else if (r < 0.80) self.type = 'reverseBullets';else if (r < 0.84) self.type = 'homingBullets';else if (r < 0.88) self.type = 'reflectBullets';else if (r < 0.91) self.type = 'healthBoost';else if (r < 0.94) self.type = 'scoreBoost';else if (r < 0.97) self.type = 'enemySlow';else self.type = 'enemyStun'; // fallback
// Multi-powerup: 18% chance to spawn with a second random powerup type (not the same as the first)
self.extraTypes = [];
if (Math.random() < 0.18) {
// Pick a second type, different from the first, only from workingTypes
var extraType;
do {
extraType = workingTypes[Math.floor(Math.random() * workingTypes.length)];
} while (extraType === self.type);
self.extraTypes.push(extraType);
}
var powerupGfx = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint by type for visual feedback
if (self.type === 'rapid') powerupGfx.tint = 0xffe100;else if (self.type === 'shield') powerupGfx.tint = 0x00ffff;else if (self.type === 'heal') powerupGfx.tint = 0x44ff44;else if (self.type === 'doubleScore') powerupGfx.tint = 0xff00ff;else if (self.type === 'slowMotion') powerupGfx.tint = 0x8888ff;else if (self.type === 'tripleShot') powerupGfx.tint = 0xff8800;else if (self.type === 'megaShield') powerupGfx.tint = 0x00ff88;else if (self.type === 'invincibility') powerupGfx.tint = 0xffffff;else if (self.type === 'scoreBomb') powerupGfx.tint = 0xff2222;else if (self.type === 'clearBullets') powerupGfx.tint = 0x00ff00;else if (self.type === 'freezeEnemies') powerupGfx.tint = 0x00e1ff;else if (self.type === 'miniEnemies') powerupGfx.tint = 0xffb300;else if (self.type === 'giantShip') powerupGfx.tint = 0x8e24aa;else if (self.type === 'tinyShip') powerupGfx.tint = 0x3949ab;else if (self.type === 'reverseBullets') powerupGfx.tint = 0x00bcd4;else if (self.type === 'homingBullets') powerupGfx.tint = 0x43a047;else if (self.type === 'reflectBullets') powerupGfx.tint = 0xcddc39;else if (self.type === 'healthBoost') powerupGfx.tint = 0xff1744;else if (self.type === 'scoreBoost') powerupGfx.tint = 0xffea00;else if (self.type === 'enemySlow') powerupGfx.tint = 0x607d8b;else if (self.type === 'enemyStun') powerupGfx.tint = 0x6d4c41;
self.speed = 10;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Ship
var Ship = Container.expand(function () {
var self = Container.call(this);
var shipGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a bright, lively cyan as the default color for the player ship
shipGfx.tint = 0x33c1ff;
self.radius = shipGfx.width * 0.5;
self.shootCooldown = 0;
self.rapidFire = false;
self.rapidFireTimer = 0;
self.shield = false;
self.shieldTimer = 0;
// Health system for player
self.maxHealth = 28; // Reduced survivability for the player ship (harder)
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 0.25,
tint: 0x222222,
y: shipGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.18,
tint: 0xff4444,
y: shipGfx.height * 0.7
});
// Visual shield indicator
var shieldGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00ffff
});
shieldGfx.alpha = 0.25;
shieldGfx.visible = false;
self.update = function () {
// Handle powerup timers
if (self.rapidFire) {
self.rapidFireTimer--;
// Visual feedback for rapid fire: pulse ship color
shipGfx.tint = LK.ticks % 10 < 5 ? 0xffe100 : 0x33c1ff;
if (self.rapidFireTimer <= 0) {
self.rapidFire = false;
shipGfx.tint = 0x33c1ff;
}
} else {
shipGfx.tint = 0x33c1ff;
}
if (self.shield) {
self.shieldTimer--;
shieldGfx.visible = true;
if (self.shieldTimer <= 0) {
self.shield = false;
shieldGfx.visible = false;
}
} else {
shieldGfx.visible = false;
}
// Update health bar
healthBar.scaleX = 2 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.activateRapidFire = function (duration) {
self.rapidFire = true;
self.rapidFireTimer = duration;
};
self.activateShield = function (duration) {
self.shield = true;
self.shieldTimer = duration;
shieldGfx.visible = true;
};
self.takeDamage = function (dmg) {
if (self.shield) return;
self.health -= dmg;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health < 0) self.health = 0;
};
self.heal = function (amount) {
self.health += amount;
if (self.health > self.maxHealth) self.health = self.maxHealth;
};
return self;
});
// SpiralEnemy: moves in a spiral pattern and shoots in bursts
var SpiralEnemy = Container.expand(function () {
var self = Container.call(this);
var spiralTints = [0x9c27b0, 0xff00ff, 0xffe100, 0x00e1ff, 0x44ff44, 0xff8800];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = spiralTints[Math.floor(Math.random() * spiralTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = 4 + Math.random() * 2;
self.angle = Math.random() * Math.PI * 2;
self.spiralRadius = 0;
self.spiralGrow = 1.2 + Math.random() * 0.7;
self.shootCooldown = 40 + Math.floor(Math.random() * 40);
self.burstCount = 0;
self.maxHealth = 2;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: enemyGfx.tint,
y: enemyGfx.height * 0.7
});
self.update = function () {
// Spiral movement
self.angle += 0.09;
self.spiralRadius += self.spiralGrow;
self.x += Math.cos(self.angle) * self.spiralGrow * 1.2;
self.y += self.speed + Math.sin(self.angle) * 2;
// Clamp inside screen
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x9c27b0, 80);
if (self.health <= 0) self.health = 0;
};
return self;
});
// TankEnemy: slow, high health, shoots more
var TankEnemy = Container.expand(function () {
var self = Container.call(this);
var tankTints = [0xff4444, 0x00e1ff, 0xffe100, 0x00ff88, 0x3f51b5, 0xff8800];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
enemyGfx.tint = tankTints[Math.floor(Math.random() * tankTints.length)];
self.radius = enemyGfx.width * 0.65;
self.speed = ENEMY_TANK_SPEED + Math.random() * 1.2;
self.moveType = 'sine';
self.sinePhase = Math.random() * Math.PI * 2;
self.sineAmp = 180 + Math.random() * 80;
self.shootCooldown = 30 + Math.floor(Math.random() * 30);
self.maxHealth = 4;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.9
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.25,
scaleY: 0.12,
tint: 0xff4444,
y: enemyGfx.height * 0.9
});
self.update = function () {
self.y += self.speed * 0.7;
self.x += Math.sin(self.y / 120 + self.sinePhase) * 10;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 1.25 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0xff4444, 100);
if (self.health <= 0) self.health = 0;
};
return self;
});
// ZigzagEnemy: moves in zigzag, normal health
var ZigzagEnemy = Container.expand(function () {
var self = Container.call(this);
var zigzagTints = [0xffe100, 0xff00ff, 0x44ff44, 0x00e1ff, 0xff8800, 0x9c27b0];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = zigzagTints[Math.floor(Math.random() * zigzagTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_ZIGZAG_SPEED + Math.random() * 3;
self.moveType = 'zigzag';
self.zigzagDir = Math.random() < 0.5 ? -1 : 1;
self.zigzagTimer = 0;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
self.maxHealth = 2;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: 0xffe100,
y: enemyGfx.height * 0.7
});
self.update = function () {
self.y += self.speed;
self.zigzagTimer++;
if (self.zigzagTimer % 30 === 0) self.zigzagDir *= -1;
self.x += self.zigzagDir * 18;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0xffe100, 80);
if (self.health <= 0) self.health = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Music
// Sound effects
// Powerup
// Enemy bullet
// Enemy
// Player bullet
// Spaceship (player)
// Game area
var GAME_W = 2048,
GAME_H = 2732;
// Score
var score = 0;
// High score (persistent for all users)
var highScore = storage.highScore || 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score UI (visible to all users)
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xffe100,
stroke: 0x000000,
strokeThickness: 6
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.x = scoreTxt.x;
highScoreTxt.y = scoreTxt.y + 110;
LK.gui.top.addChild(highScoreTxt);
var multiplierTxt = new Text2('x1', {
size: 70,
fill: 0x00ff88
});
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.x = scoreTxt.x + 220;
multiplierTxt.y = scoreTxt.y + 40;
LK.gui.top.addChild(multiplierTxt);
// Combo system variables
var comboCount = 0;
var comboTimer = 0;
var lastComboTime = 0;
// Double score powerup
var doubleScoreActive = false;
var doubleScoreTimer = 0;
// Slow motion powerup
var slowMotionActive = false;
var slowMotionTimer = 0;
// Triple shot powerup
var tripleShotActive = false;
var tripleShotTimer = 0;
// Invincibility powerup
var invincibilityActive = false;
var invincibilityTimer = 0;
// Player ship
var ship = new Ship();
game.addChild(ship);
ship.x = GAME_W / 2;
ship.y = GAME_H - 350;
// --- UI Health Text for Player ---
var shipHealthTxt = new Text2('', {
size: 60,
fill: 0xff4444,
stroke: 0x000000,
strokeThickness: 6
});
shipHealthTxt.anchor.set(0.5, 1);
shipHealthTxt.x = ship.x;
shipHealthTxt.y = ship.y - ship.radius - 60;
game.addChild(shipHealthTxt);
// Arrays for game objects
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerups = [];
// --- UI Health Text for Enemies ---
// We'll keep a parallel array to track health text objects for each enemy
var enemyHealthTxts = [];
// Dragging
var dragNode = null;
// Difficulty increased: enemies spawn more frequently
var enemySpawnTimer = 0;
var enemySpawnInterval = 70; // faster spawn
var minEnemyInterval = 20; // minimum interval decreased
var enemySpeedInc = 0;
// Powerup spawn
var powerupTimer = 0;
var powerupInterval = 120; // Powerups much more frequent
// --- Reflex tuning: adjust bullet and enemy speeds globally ---
// Restore normal speeds for enemies and bullets
var PLAYER_BULLET_SPEED = -16; // normal bullet speed
var ENEMY_BULLET_SPEED = 8; // normal enemy bullet speed
var ENEMY_BASE_SPEED = 4; // normal base enemy speed
var ENEMY_FAST_SPEED = 7; // normal fast enemy speed
var ENEMY_TANK_SPEED = 1.5; // normal tank speed
var ENEMY_ZIGZAG_SPEED = 4; // normal zigzag speed
var PLAYER_SHOOT_RATE = 12; // normal player shoot rate
// Last intersect states
var lastShipEnemyIntersect = false;
var lastShipEnemyBulletIntersect = false;
var lastShipPowerupIntersect = false;
// Music
LK.playMusic('bgmusic');
// Move handler (drag ship)
function handleMove(x, y, obj) {
if (dragNode === ship) {
// Clamp ship inside game area (with margin)
var margin = 80;
var nx = Math.max(margin, Math.min(GAME_W - margin, x));
var ny = Math.max(margin, Math.min(GAME_H - margin, y));
ship.x = nx;
ship.y = ny;
// Touch feedback: scale ship up slightly while dragging, then back
ship.scale.set(1.15, 1.15);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 120
});
}
}
game.move = handleMove;
var dragAnywhereHintShown = false;
game.down = function (x, y, obj) {
// User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen
var dx = x - ship.x,
dy = y - ship.y;
var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2;
var inLowerHalf = y > GAME_H / 2;
if (onShip || inLowerHalf) {
dragNode = ship;
handleMove(x, y, obj);
// Visual feedback: flash ship blue for 120ms on drag start
LK.effects.flashObject(ship, 0x33c1ff, 120);
// Show floating helper text only the first time user drags from lower half (not on ship)
if (!onShip && inLowerHalf && !dragAnywhereHintShown) {
showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff);
dragAnywhereHintShown = true;
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
// Update ship
ship.update();
// --- Update player health UI text ---
shipHealthTxt.setText(ship.health + " / " + ship.maxHealth);
shipHealthTxt.x = ship.x;
shipHealthTxt.y = ship.y - ship.radius - 60;
// --- Update enemy health UI text ---
// Remove any health text objects for destroyed enemies
for (var i = enemyHealthTxts.length - 1; i >= 0; i--) {
if (i >= enemies.length || !enemies[i] || enemies[i].destroyed) {
if (enemyHealthTxts[i]) {
enemyHealthTxts[i].destroy();
}
enemyHealthTxts.splice(i, 1);
}
}
// Sync health text objects to enemies
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var txt = enemyHealthTxts[i];
if (!txt) {
txt = new Text2('', {
size: 48,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 5
});
txt.anchor.set(0.5, 1);
game.addChild(txt);
enemyHealthTxts[i] = txt;
}
txt.setText(e.health + " / " + e.maxHealth);
txt.x = e.x;
txt.y = e.y - (e.radius || 60) - 38;
txt.visible = e.health > 0;
}
// --- Double Score timer ---
if (doubleScoreActive) {
doubleScoreTimer--;
if (doubleScoreTimer <= 0) {
doubleScoreActive = false;
showFloatingText("Double Score Ended", ship.x, ship.y - 120, 0xff00ff);
}
}
// --- Slow Motion timer ---
if (slowMotionActive) {
slowMotionTimer--;
if (slowMotionTimer <= 0) {
slowMotionActive = false;
showFloatingText("Speed Restored", ship.x, ship.y - 120, 0x8888ff);
}
}
// --- Triple Shot timer ---
if (tripleShotActive) {
tripleShotTimer--;
if (tripleShotTimer <= 0) {
tripleShotActive = false;
showFloatingText("Triple Shot Ended", ship.x, ship.y - 120, 0xff8800);
}
}
// --- Invincibility timer ---
if (invincibilityActive) {
invincibilityTimer--;
if (invincibilityTimer <= 0) {
invincibilityActive = false;
showFloatingText("Invincibility Ended", ship.x, ship.y - 120, 0xffffff);
}
}
// --- Freeze Enemies timer ---
if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) {
freezeEnemiesTimer--;
if (freezeEnemiesTimer <= 0) {
showFloatingText("Enemies Unfrozen", ship.x, ship.y - 120, 0x00e1ff);
}
}
// --- Homing Bullets timer ---
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) {
homingBulletsTimer--;
if (homingBulletsTimer <= 0) {
showFloatingText("Homing Ended", ship.x, ship.y - 120, 0x43a047);
}
}
// --- Reflect Bullets timer ---
if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) {
reflectBulletsTimer--;
if (reflectBulletsTimer <= 0) {
showFloatingText("Reflect Ended", ship.x, ship.y - 120, 0xcddc39);
}
}
// --- Enemy Slow timer ---
if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) {
enemySlowTimer--;
if (enemySlowTimer <= 0) {
showFloatingText("Enemies Normal Speed", ship.x, ship.y - 120, 0x607d8b);
}
}
// --- Enemy Stun timer ---
if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) {
enemyStunTimer--;
if (enemyStunTimer <= 0) {
showFloatingText("Enemies Unstunned", ship.x, ship.y - 120, 0x6d4c41);
}
}
// --- Player shooting ---
ship.shootCooldown--;
var shootRate = ship.rapidFire ? Math.max(6, Math.floor(PLAYER_SHOOT_RATE * 0.33)) : PLAYER_SHOOT_RATE;
if (ship.shootCooldown <= 0) {
// Auto-fire
if (tripleShotActive) {
for (var ts = -1; ts <= 1; ts++) {
var pb = new PlayerBullet();
pb.x = ship.x + ts * 38;
pb.y = ship.y - ship.radius - 30;
if (ts !== 0) pb.rotation = ts * 0.18;
playerBullets.push(pb);
game.addChild(pb);
}
} else {
var pb = new PlayerBullet();
pb.x = ship.x;
pb.y = ship.y - ship.radius - 30;
playerBullets.push(pb);
game.addChild(pb);
}
ship.shootCooldown = shootRate;
LK.getSound('shoot').play();
}
// --- 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);
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// --- Enemy AI: dodge if player is close horizontally ---
if (Math.abs(ship.x - e.x) < 180 && Math.abs(ship.y - e.y) < 600) {
// Try to dodge left or right, but less distance for fairness
if (ship.x < e.x) e.x += 6 + Math.random() * 3;else e.x -= 6 + Math.random() * 3;
}
// --- Powerup effects: freeze, slow, stun ---
var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0;
var slowActive = typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0 || slowMotionActive;
var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0;
if (stunActive) {
// Stunned: don't update, don't shoot
// Visual feedback: pulse tint
if (LK.ticks % 20 < 10) e.tint = 0x6d4c41;else e.tint = 0xffffff;
} else if (freezeActive) {
// Frozen: don't update, don't shoot
if (LK.ticks % 20 < 10) e.tint = 0x00e1ff;else e.tint = 0xffffff;
} else {
// Slow: update less frequently
if (!slowActive || LK.ticks % 2 === 0) {
e.update();
}
}
// Destroy and remove enemies when they move off the bottom of the screen
if (e.y > GAME_H + e.radius) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// (Removed clamping: enemies can now leave the screen at the bottom)
// Enemy shooting
if (!freezeActive && !stunActive) {
// SpiralEnemy: burst shoot 3 bullets in a spread
if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) {
for (var b = -1; b <= 1; b++) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Spread: aim at ship, but offset angle
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) + b * 0.18;
if (len > 0) {
eb.dirX = Math.cos(angle);
eb.dirY = Math.sin(angle);
}
enemyBullets.push(eb);
game.addChild(eb);
}
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else if (e.shootCooldown <= 0) {
// BigEnemy shoots big bullet, others shoot normal
var eb;
if (e.constructor === BigEnemy) {
eb = new BigEnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 20;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else {
eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 90 + Math.floor(Math.random() * 60);
}
}
}
}
// --- Update enemy bullets ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
// Slow motion: update less frequently
var reflectActive = typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0;
if (reflectActive && eb.dirY > 0 && eb.y > ship.y - 200) {
// Reflect bullets going downwards, near ship
eb.dirY *= -1;
showFloatingText("Reflected!", eb.x, eb.y, 0xcddc39);
}
if (!slowMotionActive || LK.ticks % 2 === 0) {
eb.update();
if (eb.x < -100 || eb.x > GAME_W + 100 || eb.y < -100 || eb.y > GAME_H + 100) {
eb.destroy();
enemyBullets.splice(i, 1);
}
}
}
// --- Homing Bullets effect for player bullets ---
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) {
for (var i = 0; i < playerBullets.length; i++) {
var pb = playerBullets[i];
// Find nearest enemy
var nearest = null,
minDist = 99999;
for (var j = 0; j < enemies.length; j++) {
var dx = enemies[j].x - pb.x;
var dy = enemies[j].y - pb.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
nearest = enemies[j];
}
}
if (nearest && minDist < 400 * 400) {
// Adjust bullet direction slightly toward enemy
var dx = nearest.x - pb.x;
var dy = nearest.y - pb.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
var steer = 0.18;
pb.x += dx / len * steer;
pb.y += dy / len * steer * 0.5;
}
}
}
}
// --- Update powerups ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
if (p.y > GAME_H + 100) {
p.destroy();
powerups.splice(i, 1);
}
}
// --- Collision: Player bullets vs Enemies ---
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
for (var j = enemies.length - 1; j >= 0; j--) {
var e = enemies[j];
if (b.intersects(e)) {
// Hit enemy
e.takeDamage(2);
// Recoil effect for enemy
tween(e, {
y: e.y - 30
}, {
duration: 60,
yoyo: true,
repeat: 1
});
// Camera shake on hit (simulate with quick flash)
LK.getSound('enemyDown').play();
// Combo system variables (global scope, but initialize if undefined)
if (typeof comboCount === "undefined") comboCount = 0;
if (typeof comboTimer === "undefined") comboTimer = 0;
if (typeof lastComboTime === "undefined") lastComboTime = 0;
// Score only if enemy dies
if (e.health <= 0) {
// Combo logic: if last kill was within 2 seconds, increase combo
var now = LK.ticks;
if (now - lastComboTime < 120) {
comboCount++;
} else {
comboCount = 1;
}
lastComboTime = now;
comboTimer = 120; // 2 seconds to continue combo
// Combo bonus: +10 base, +5 per combo after first, multiplied by scoreMultiplier
if (typeof scoreMultiplier === "undefined") scoreMultiplier = 1;
var comboBonus = 10 + (comboCount > 1 ? (comboCount - 1) * 5 : 0);
comboBonus *= scoreMultiplier;
if (doubleScoreActive) comboBonus *= 2;
score += comboBonus;
// Animate score text for feedback
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText("High Score: " + highScore);
highScoreTxt.scale.set(1.2, 1.2);
tween(highScoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 300
});
}
}
if (typeof multiplierTxt !== "undefined") {
multiplierTxt.setText("x" + scoreMultiplier);
multiplierTxt.scale.set(1.3, 1.3);
tween(multiplierTxt.scale, {
x: 1,
y: 1
}, {
duration: 200
});
}
scoreTxt.scale.set(1.25, 1.25);
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 200
});
// Show floating text for combo
if (comboCount > 1) {
showFloatingText("Combo x" + comboCount + "! +" + comboBonus, e.x, e.y - 80, 0xffe100);
if (comboCount % 3 === 0) {
showFloatingText("Multiplier: x" + scoreMultiplier, e.x, e.y - 160, 0x00ff88);
}
} else {
showFloatingText("+10", e.x, e.y - 80, 0xffffff);
}
// Fun: random emoji on enemy kill!
var emojis = ["🚀", "💥", "✨", "🔥", "😎", "🎉", "🛸", "👾"];
var emoji = emojis[Math.floor(Math.random() * emojis.length)];
showFloatingText(emoji, e.x + (Math.random() * 80 - 40), e.y - 120, 0xffffff);
e.destroy();
enemies.splice(j, 1);
}
b.destroy();
playerBullets.splice(i, 1);
break;
}
}
}
// --- Combo timer update ---
if (typeof comboTimer !== "undefined" && comboTimer > 0) {
comboTimer--;
if (comboTimer === 0) {
comboCount = 0;
scoreMultiplier = 1; // Reset multiplier on missed combo
}
}
if (comboCount > 1) {
scoreMultiplier = 1 + Math.floor(comboCount / 3); // Every 3 combo increases multiplier by 1
} else {
scoreMultiplier = 1;
}
// --- Collision: Ship vs Enemies ---
var shipEnemyIntersect = false;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (ship.intersects(e)) {
shipEnemyIntersect = true;
break;
}
}
if (!lastShipEnemyIntersect && shipEnemyIntersect) {
if (invincibilityActive) {
LK.effects.flashObject(ship, 0xffffff, 400);
showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff);
} else if (ship.shield) {
// Absorb hit, destroy enemy
LK.effects.flashObject(ship, 0x00ffff, 400);
for (var i = 0; i < enemies.length; i++) {
if (ship.intersects(enemies[i])) {
enemies[i].destroy();
enemies.splice(i, 1);
break;
}
}
ship.shield = false;
ship.shieldTimer = 0;
ship.children[1].visible = false;
} else {
// Take damage
ship.takeDamage(2);
// (Screen flash removed)
LK.getSound('hit').play();
if (ship.health <= 0) {
LK.showGameOver();
return;
}
}
}
lastShipEnemyIntersect = shipEnemyIntersect;
// --- Collision: Ship vs Enemy Bullets ---
var shipEnemyBulletIntersect = false;
for (var i = 0; i < enemyBullets.length; i++) {
var eb = enemyBullets[i];
if (ship.intersects(eb)) {
shipEnemyBulletIntersect = true;
break;
}
}
if (!lastShipEnemyBulletIntersect && shipEnemyBulletIntersect) {
if (invincibilityActive) {
LK.effects.flashObject(ship, 0xffffff, 400);
showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff);
} else if (ship.shield) {
LK.effects.flashObject(ship, 0x00ffff, 400);
// Remove bullet
for (var i = 0; i < enemyBullets.length; i++) {
if (ship.intersects(enemyBullets[i])) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
break;
}
}
ship.shield = false;
ship.shieldTimer = 0;
ship.children[1].visible = false;
} else {
// Take damage
ship.takeDamage(1);
// (Screen flash removed)
LK.getSound('hit').play();
if (ship.health <= 0) {
LK.showGameOver();
return;
}
}
}
lastShipEnemyBulletIntersect = shipEnemyBulletIntersect;
// --- Collision: Ship vs Powerups ---
var shipPowerupIntersect = false;
var _loop = function _loop() {
p = powerups[i];
if (ship.intersects(p)) {
// --- 20 Powerup Types Logic ---
// Helper to apply a powerup type (for multi-powerup support)
var applyPowerupType = function applyPowerupType(type) {
// 1. rapid
if (type === 'rapid') {
if (ship.rapidFire) {
ship.rapidFireTimer += 240;
showFloatingText("Rapid Fire Combo!", ship.x, ship.y - 120, 0xffe100);
} else {
ship.activateRapidFire(360);
showFloatingText("Rapid Fire!", ship.x, ship.y - 120, 0xffe100);
}
// 2. shield
} else if (type === 'shield') {
if (ship.shield) {
ship.shieldTimer += 320;
showFloatingText("Shield Combo!", ship.x, ship.y - 120, 0x00ffff);
} else {
ship.activateShield(480);
showFloatingText("Shield!", ship.x, ship.y - 120, 0x00ffff);
}
// 3. heal
} else if (type === 'heal') {
ship.heal(4);
showFloatingText("+4 Health", ship.x, ship.y - 120, 0x44ff44);
// 4. doubleScore
} else if (type === 'doubleScore') {
if (doubleScoreActive) {
doubleScoreTimer += 400;
showFloatingText("Double Score Combo!", ship.x, ship.y - 120, 0xff00ff);
} else {
doubleScoreActive = true;
doubleScoreTimer = 600;
showFloatingText("Double Score!", ship.x, ship.y - 120, 0xff00ff);
}
// 5. slowMotion
} else if (type === 'slowMotion') {
if (slowMotionActive) {
slowMotionTimer += 300;
showFloatingText("Slow Motion Combo!", ship.x, ship.y - 120, 0x8888ff);
} else {
slowMotionActive = true;
slowMotionTimer = 420;
showFloatingText("Slow Motion!", ship.x, ship.y - 120, 0x8888ff);
}
// 6. tripleShot
} else if (type === 'tripleShot') {
if (tripleShotActive) {
tripleShotTimer += 360;
showFloatingText("Triple Shot Combo!", ship.x, ship.y - 120, 0xff8800);
} else {
tripleShotActive = true;
tripleShotTimer = 540;
showFloatingText("Triple Shot!", ship.x, ship.y - 120, 0xff8800);
}
// 7. megaShield
} else if (type === 'megaShield') {
if (ship.shield) {
ship.shieldTimer += 800;
showFloatingText("Mega Shield Combo!", ship.x, ship.y - 120, 0x00ff88);
} else {
ship.activateShield(1200);
showFloatingText("Mega Shield!", ship.x, ship.y - 120, 0x00ff88);
}
// 8. invincibility
} else if (type === 'invincibility') {
if (invincibilityActive) {
invincibilityTimer += 240;
showFloatingText("Invincible Combo!", ship.x, ship.y - 120, 0xffffff);
} else {
invincibilityActive = true;
invincibilityTimer = 360;
showFloatingText("Invincible!", ship.x, ship.y - 120, 0xffffff);
}
// 9. scoreBomb
} else if (type === 'scoreBomb') {
var killed = 0;
for (var si = enemies.length - 1; si >= 0; si--) {
if (enemies[si].y > 0) {
enemies[si].destroy();
enemies.splice(si, 1);
killed++;
}
}
if (killed > 0) {
var bombScore = 10 * killed;
score += bombScore;
scoreTxt.setText(score);
showFloatingText("Score Bomb! +" + bombScore, ship.x, ship.y - 120, 0xff2222);
} else {
showFloatingText("Score Bomb! (No targets)", ship.x, ship.y - 120, 0xff2222);
}
// 10. clearBullets
} else if (type === 'clearBullets') {
var cleared = 0;
for (var ci = enemyBullets.length - 1; ci >= 0; ci--) {
enemyBullets[ci].destroy();
enemyBullets.splice(ci, 1);
cleared++;
}
showFloatingText("Cleared " + cleared + " Bullets!", ship.x, ship.y - 120, 0x00ff00);
// 11. freezeEnemies
} else if (type === 'freezeEnemies') {
if (typeof freezeEnemiesTimer === "undefined") freezeEnemiesTimer = 0;
if (freezeEnemiesTimer > 0) {
freezeEnemiesTimer += 180;
showFloatingText("Freeze Combo!", ship.x, ship.y - 120, 0x00e1ff);
} else {
freezeEnemiesTimer = 360;
showFloatingText("Enemies Frozen!", ship.x, ship.y - 120, 0x00e1ff);
}
// 12. miniEnemies
} else if (type === 'miniEnemies') {
for (var mi = 0; mi < enemies.length; mi++) {
enemies[mi].scale.set(0.6, 0.6);
enemies[mi].radius *= 0.6;
}
showFloatingText("Mini Enemies!", ship.x, ship.y - 120, 0xffb300);
// 13. giantShip
} else if (type === 'giantShip') {
ship.scale.set(1.7, 1.7);
showFloatingText("Giant Ship!", ship.x, ship.y - 120, 0x8e24aa);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 1200
});
// 14. tinyShip
} else if (type === 'tinyShip') {
ship.scale.set(0.5, 0.5);
showFloatingText("Tiny Ship!", ship.x, ship.y - 120, 0x3949ab);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 1200
});
// 15. reverseBullets
} else if (type === 'reverseBullets') {
for (var ri = 0; ri < enemyBullets.length; ri++) {
enemyBullets[ri].dirY *= -1;
}
showFloatingText("Reverse Bullets!", ship.x, ship.y - 120, 0x00bcd4);
// 16. homingBullets
} else if (type === 'homingBullets') {
if (typeof homingBulletsTimer === "undefined") homingBulletsTimer = 0;
if (homingBulletsTimer > 0) {
homingBulletsTimer += 180;
showFloatingText("Homing Combo!", ship.x, ship.y - 120, 0x43a047);
} else {
homingBulletsTimer = 360;
showFloatingText("Homing Bullets!", ship.x, ship.y - 120, 0x43a047);
}
// 17. reflectBullets
} else if (type === 'reflectBullets') {
if (typeof reflectBulletsTimer === "undefined") reflectBulletsTimer = 0;
if (reflectBulletsTimer > 0) {
reflectBulletsTimer += 180;
showFloatingText("Reflect Combo!", ship.x, ship.y - 120, 0xcddc39);
} else {
reflectBulletsTimer = 360;
showFloatingText("Reflect Bullets!", ship.x, ship.y - 120, 0xcddc39);
}
// 18. healthBoost
} else if (type === 'healthBoost') {
ship.maxHealth += 4;
ship.heal(4);
showFloatingText("Max Health Up!", ship.x, ship.y - 120, 0xff1744);
// 19. scoreBoost
} else if (type === 'scoreBoost') {
score += 100;
scoreTxt.setText(score);
showFloatingText("+100 Score!", ship.x, ship.y - 120, 0xffea00);
// 20. enemySlow
} else if (type === 'enemySlow') {
if (typeof enemySlowTimer === "undefined") enemySlowTimer = 0;
if (enemySlowTimer > 0) {
enemySlowTimer += 180;
showFloatingText("Enemy Slow Combo!", ship.x, ship.y - 120, 0x607d8b);
} else {
enemySlowTimer = 360;
showFloatingText("Enemies Slowed!", ship.x, ship.y - 120, 0x607d8b);
}
// 21. enemyStun (fallback, rarely picked)
} else if (type === 'enemyStun') {
if (typeof enemyStunTimer === "undefined") enemyStunTimer = 0;
if (enemyStunTimer > 0) {
enemyStunTimer += 120;
showFloatingText("Stun Combo!", ship.x, ship.y - 120, 0x6d4c41);
} else {
enemyStunTimer = 240;
showFloatingText("Enemies Stunned!", ship.x, ship.y - 120, 0x6d4c41);
}
}
}; // Always apply the main powerup type
shipPowerupIntersect = true;
// Apply powerup
LK.getSound('powerup').play();
// Fun: confetti burst effect on powerup collect!
for (confetti = 0; confetti < 18; confetti++) {
(function () {
var part = new Container();
var c = part.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4 + Math.random() * 0.5,
scaleY: 0.4 + Math.random() * 0.5,
tint: 0xffe100 + Math.floor(Math.random() * 0xffffff)
});
part.x = ship.x;
part.y = ship.y - 40;
part.alpha = 0.85;
game.addChild(part);
var angle = Math.random() * Math.PI * 2;
var dist = 120 + Math.random() * 80;
var dx = Math.cos(angle) * dist;
var dy = Math.sin(angle) * dist;
tween(part, {
x: part.x + dx,
y: part.y + dy,
alpha: 0
}, {
duration: 600 + Math.random() * 400,
onFinish: function onFinish() {
part.destroy();
}
});
})();
}
applyPowerupType(p.type);
// If this powerup has extraTypes (multi-powerup), apply those as well
if (p.extraTypes && p.extraTypes.length > 0) {
for (et = 0; et < p.extraTypes.length; et++) {
applyPowerupType(p.extraTypes[et]);
// Show a floating text for the extra powerup
showFloatingText("Bonus: " + p.extraTypes[et], ship.x, ship.y - 180 - et * 60, 0xffe100);
}
}
// Combo fusion: if multiple effects are active, show a special floating text!
comboActiveCount = 0;
if (ship.rapidFire) comboActiveCount++;
if (doubleScoreActive) comboActiveCount++;
if (tripleShotActive) comboActiveCount++;
if (slowMotionActive) comboActiveCount++;
if (invincibilityActive) comboActiveCount++;
if (ship.shield) comboActiveCount++;
if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) comboActiveCount++;
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) comboActiveCount++;
if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) comboActiveCount++;
if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) comboActiveCount++;
if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) comboActiveCount++;
if (comboActiveCount >= 3) {
showFloatingText("Combo Fusion! (" + comboActiveCount + ")", ship.x, ship.y - 220, 0xffe100);
}
p.destroy();
powerups.splice(i, 1);
}
},
p,
confetti,
et,
comboActiveCount;
for (var i = powerups.length - 1; i >= 0; i--) {
_loop();
}
lastShipPowerupIntersect = shipPowerupIntersect;
// --- Mission system: show floating text for milestones ---
if (score > 0 && score % 100 === 0 && !game['milestone_' + score]) {
showFloatingText("Milestone: " + score + "!", GAME_W / 2, 320, 0xffe100);
game['milestone_' + score] = true;
}
// --- Background color cycling every 5000 points after 5000 ---
// Define a palette of beautiful background colors, starting with sky blue
if (typeof bgColors === "undefined") {
var bgColors = [0x87ceeb,
// sky blue
0x1a237e,
// indigo
0x004d40,
// teal dark
0x263238,
// blue grey
0x880e4f,
// deep pink
0x1565c0,
// blue
0x2e7d32,
// green
0xff6f00,
// orange
0x4a148c,
// purple
0x212121,
// dark grey
0x00695c,
// teal
0x3949ab,
// blue indigo
0xc62828,
// red
0x00838f,
// cyan
0x6d4c41,
// brown
0x283593,
// deep blue
0x000000 // black (always last)
];
var lastBgScoreStep = 0;
// Set initial background to sky blue
game.setBackgroundColor(bgColors[0]);
}
// Calculate which color to use based on score
var bgScoreStep = score < 5000 ? 0 : 1 + Math.floor((score - 5000) / 5000);
if (bgScoreStep !== lastBgScoreStep) {
var paletteLen = bgColors.length;
var colorIdx;
if (bgScoreStep < paletteLen - 1) {
colorIdx = bgScoreStep;
} else if (bgScoreStep === paletteLen - 1) {
colorIdx = paletteLen - 1; // black
} else {
// After black, restart from the beginning (skip black until next full cycle)
colorIdx = (bgScoreStep - (paletteLen - 1)) % (paletteLen - 1);
}
game.setBackgroundColor(bgColors[colorIdx]);
lastBgScoreStep = bgScoreStep;
}
// --- Feature selection popup removed ---
// --- Enemy spawn ---
enemySpawnTimer--;
if (enemySpawnTimer <= 0) {
// Randomly pick enemy type
var enemyTypeRand = Math.random();
var e;
if (enemyTypeRand < 0.18) {
e = new FastEnemy();
} else if (enemyTypeRand < 0.36) {
e = new TankEnemy();
} else if (enemyTypeRand < 0.54) {
e = new ZigzagEnemy();
} else if (enemyTypeRand < 0.70) {
e = new Enemy();
} else {
e = new BigEnemy();
}
e.x = 180 + Math.random() * (GAME_W - 360);
e.y = -100;
// Removed enemy speed scaling with score to keep game speed consistent
enemies.push(e);
game.addChild(e);
// Decrease interval as score increases
enemySpawnInterval = Math.max(minEnemyInterval, 60 - Math.floor(score / 100) * 4);
enemySpawnTimer = enemySpawnInterval;
}
// --- Powerup spawn ---
powerupTimer--;
if (powerupTimer <= 0) {
var p = new Powerup();
p.x = 180 + Math.random() * (GAME_W - 360);
p.y = -80;
powerups.push(p);
game.addChild(p);
powerupInterval = 120 + Math.floor(Math.random() * 120);
powerupTimer = powerupInterval;
}
};
// Floating text helper for feedback
function showFloatingText(txt, x, y, color) {
var t = new Text2(txt, {
size: 90,
fill: color || 0xffffff,
stroke: 0x000000,
strokeThickness: 8
});
t.anchor.set(0.5, 1);
t.x = x;
t.y = y;
game.addChild(t);
tween(t, {
y: y - 120,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
t.destroy();
}
});
}
// --- Start Menu Overlay ---
var menuOverlay = new Container();
menuOverlay.interactive = true;
menuOverlay.x = 0;
menuOverlay.y = 0;
menuOverlay.width = GAME_W;
menuOverlay.height = GAME_H;
// Dim background
var menuBg = LK.getAsset('playerBullet', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_W / 30,
scaleY: GAME_H / 60,
tint: 0x000000,
alpha: 0.82,
x: 0,
y: 0
});
menuOverlay.addChild(menuBg);
// Title
var menuTitle = new Text2("SPACE BLASTERS", {
size: 160,
fill: 0xffe100,
stroke: 0x000000,
strokeThickness: 12,
align: "center"
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = GAME_W / 2;
menuTitle.y = GAME_H / 2 - 320;
menuOverlay.addChild(menuTitle);
// Instructions
var menuInstructions = new Text2("Drag the ship to move\nAuto-fire enabled\nCollect powerups!", {
size: 90,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
menuInstructions.anchor.set(0.5, 0.5);
menuInstructions.x = GAME_W / 2;
menuInstructions.y = GAME_H / 2 - 80;
menuOverlay.addChild(menuInstructions);
// Start button
var startBtn = new Text2("TAP TO START", {
size: 110,
fill: 0x00ff88,
stroke: 0x000000,
strokeThickness: 10,
align: "center"
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = GAME_W / 2;
startBtn.y = GAME_H / 2 + 260;
menuOverlay.addChild(startBtn);
game.addChild(menuOverlay);
// Pause game logic until menu is dismissed
var gamePausedForMenu = true;
var origGameUpdate = game.update;
game.update = function () {
if (gamePausedForMenu) return;
origGameUpdate.call(game);
};
// Dismiss menu on tap/click anywhere
menuOverlay.down = function (x, y, obj) {
if (!gamePausedForMenu) return;
gamePausedForMenu = false;
tween(menuOverlay, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
menuOverlay.destroy();
}
});
};
// Forward input to menu overlay
game.down = function (x, y, obj) {
if (gamePausedForMenu && menuOverlay.down) {
menuOverlay.down(x, y, obj);
return;
}
// User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen
var dx = x - ship.x,
dy = y - ship.y;
var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2;
var inLowerHalf = y > GAME_H / 2;
if (onShip || inLowerHalf) {
dragNode = ship;
handleMove(x, y, obj);
LK.effects.flashObject(ship, 0x33c1ff, 120);
if (!onShip && inLowerHalf && !dragAnywhereHintShown) {
showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff);
dragAnywhereHintShown = true;
}
}
};
game.move = function (x, y, obj) {
if (gamePausedForMenu) return;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
if (gamePausedForMenu) return;
dragNode = null;
};
// Define freezeActive and stunActive for menu overlay logic
var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0;
var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (!freezeActive && !stunActive) {
// SpiralEnemy: burst shoot 3 bullets in a spread
if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) {
for (var b = -1; b <= 1; b++) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Spread: aim at ship, but offset angle
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) + b * 0.18;
if (len > 0) {
eb.dirX = Math.cos(angle);
eb.dirY = Math.sin(angle);
}
enemyBullets.push(eb);
game.addChild(eb);
}
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else if (e.shootCooldown <= 0) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 90 + Math.floor(Math.random() * 60);
}
}
}