/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Big Enemy var BigEnemy = Container.expand(function () { var self = Container.call(this); // Add left wing var leftWing = self.attachAsset('bigEnemy', { anchorX: 0.5, anchorY: 0.5 }); leftWing.width = 80; leftWing.height = 180; leftWing.x = -110; leftWing.y = 30; leftWing.rotation = -0.28; leftWing.alpha = 0.7; // Add right wing var rightWing = self.attachAsset('bigEnemy', { anchorX: 0.5, anchorY: 0.5 }); rightWing.width = 80; rightWing.height = 180; rightWing.x = 110; rightWing.y = 30; rightWing.rotation = 0.28; rightWing.alpha = 0.7; var gfx = self.attachAsset('bigEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.width = gfx.width; self.height = gfx.height; self.scaleX = 1; self.scaleY = 1; self.alpha = 1; self.speedY = 4; self.hp = 6; self.maxHp = 6; self.shootCooldown = 0; self.shootRate = 70 + Math.floor(Math.random() * 40); self.value = 5; self.update = function () { if (self.lastY === undefined) self.lastY = self.y; var stopY = 2732 / 2.5; // stops a bit higher than regular enemy if (self.y < stopY) { self.y += self.speedY; self._stopped = false; } else { self.y = stopY; // After stopping, move in a slow horizontal oscillation (side-to-side) if (!self._stopped) { self._stopped = true; self._moveTimer = 0; self._baseX = self.x; } self._moveTimer++; // Oscillate left/right with a slow sine wave self.x = self._baseX + Math.sin(self._moveTimer / 60) * 120; } if (self.shootCooldown > 0) self.shootCooldown--; if (self.shootCooldown === 0 && Math.random() < 0.04) { self.shoot(); self.shootCooldown = self.shootRate; } self.lastY = self.y; }; self.shoot = function () { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + self.height / 2 + 10; bullet.speedY = 22; // slightly faster bullet enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); }; self.takeDamage = function (amount) { self.hp -= amount; LK.getSound('enemyHit').play(); LK.effects.flashObject(self, 0xffffff, 200); if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Bomb: Explosive weapon that destroys enemies in an area var Bomb = Container.expand(function () { var self = Container.call(this); // Add blast radius indicator (initially invisible) var blastRadiusGfx = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, color: 0xffff00 }); blastRadiusGfx.width = 400; // 2x blast radius blastRadiusGfx.height = 400; // 2x blast radius blastRadiusGfx.alpha = 0; blastRadiusGfx.tint = 0xffff00; // Yellow color var gfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); gfx.width = 60; gfx.height = 60; gfx.tint = 0xff3300; self.width = gfx.width; self.height = gfx.height; self.speedY = -18; self.speedX = 0; self.blastRadius = 200; self.owner = null; // set to ship self.update = function () { self.x += self.speedX; self.y += self.speedY; // Check for collision with enemies directly in update method for (var i = 0; i < enemies.length; i++) { if (self.intersects(enemies[i])) { self.explode(); break; } } }; self.explode = function () { // Show blast radius with animation blastRadiusGfx.alpha = 0.7; // Animate the blast radius expanding and fading tween(blastRadiusGfx, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut }); // --- Yellow explosion effect --- // Create a temporary yellow explosion circle var explosion = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, color: 0xffff00 }); // Set the explosion to 200x200 as requested explosion.width = 200; explosion.height = 200; explosion.alpha = 0.85; explosion.tint = 0xffff00; explosion.x = 0; explosion.y = 0; // Animate the explosion: keep it visible for about 2 seconds, then fade out tween(explosion, { alpha: 0.85 }, { duration: 1800, easing: tween.linear, onFinish: function onFinish() { tween(explosion, { alpha: 0 }, { duration: 200, easing: tween.linear, onFinish: function onFinish() { if (explosion && explosion.parent) explosion.parent.removeChild(explosion); } }); } }); // Flash effect for explosion LK.effects.flashObject(self, 0xffaa00, 200); self.destroyed = true; }; return self; }); // Enemy var Enemy = Container.expand(function () { var self = Container.call(this); // Add left wing var leftWing = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); leftWing.width = 40; leftWing.height = 80; leftWing.x = -50; leftWing.y = 10; leftWing.rotation = -0.32; leftWing.alpha = 0.7; // Add right wing var rightWing = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); rightWing.width = 40; rightWing.height = 80; rightWing.x = 50; rightWing.y = 10; rightWing.rotation = 0.32; rightWing.alpha = 0.7; // Main enemy body var gfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.width = gfx.width; self.height = gfx.height; self.scaleX = 1; self.scaleY = 1; self.alpha = 1; self.speedY = 6; self.hp = 1; self.maxHp = 1; self.shootCooldown = 0; self.shootRate = 90 + Math.floor(Math.random() * 60); self.value = 1; self.update = function () { // Store lastY for transition detection if (self.lastY === undefined) self.lastY = self.y; var stopY = 2732 / 2; // halfway down the screen if (self.y < stopY) { self.y += self.speedY; self._stopped = false; } else { self.y = stopY; // After stopping, move in a small horizontal wiggle if (!self._stopped) { self._stopped = true; self._moveTimer = 0; self._baseX = self.x; } self._moveTimer++; // Wiggle left/right with a small sine wave self.x = self._baseX + Math.sin(self._moveTimer / 18) * 40; } if (self.shootCooldown > 0) self.shootCooldown--; if (self.shootCooldown === 0 && Math.random() < 0.02) { self.shoot(); self.shootCooldown = self.shootRate; } self.lastY = self.y; }; self.shoot = function () { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + self.height / 2 + 10; enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); }; self.takeDamage = function (amount) { self.hp -= amount; LK.getSound('enemyHit').play(); LK.effects.flashObject(self, 0xffffff, 200); if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Enemy Bullet var EnemyBullet = Container.expand(function () { var self = Container.call(this); var gfx = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.width = gfx.width; self.height = gfx.height; self.speedY = 18; self.update = function () { self.y += self.speedY; }; return self; }); // FastEnemy: Quick enemy with less HP var FastEnemy = Container.expand(function () { var self = Container.call(this); // Add left wing var leftWing = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); leftWing.width = 32; leftWing.height = 64; leftWing.x = -40; leftWing.y = 8; leftWing.rotation = -0.32; leftWing.alpha = 0.7; // Add right wing var rightWing = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); rightWing.width = 32; rightWing.height = 64; rightWing.x = 40; rightWing.y = 8; rightWing.rotation = 0.32; rightWing.alpha = 0.7; var gfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); gfx.tint = 0xff6600; // Orange color for fast enemy self.width = gfx.width; self.height = gfx.height; self.scaleX = 0.8; // Smaller than regular enemy self.scaleY = 0.8; self.alpha = 1; self.speedY = 8; // Slightly slower than before, but still faster than regular enemy self.hp = 1; self.maxHp = 1; self.shootCooldown = 0; self.shootRate = 120 + Math.floor(Math.random() * 60); // Shoots less often self.value = 2; // Worth more points than regular enemy self.update = function () { // Store lastY for transition detection if (self.lastY === undefined) self.lastY = self.y; var stopY = 2732 / 2.2; // Stops slightly higher than regular enemy // Move in a zigzag pattern if (typeof self.zigzagDir === "undefined") { self.zigzagDir = Math.random() < 0.5 ? 1 : -1; self.zigzagTimer = 0; } self.zigzagTimer++; if (self.zigzagTimer > 30) { self.zigzagDir *= -1; self.zigzagTimer = 0; } if (self.y < stopY) { self.y += self.speedY; // Add zigzag motion self.x += self.zigzagDir * 4; // Ensure enemy stays within screen bounds if (self.x < self.width / 2) { self.x = self.width / 2; self.zigzagDir *= -1; } else if (self.x > 2048 - self.width / 2) { self.x = 2048 - self.width / 2; self.zigzagDir *= -1; } self._stopped = false; } else { self.y = stopY; // After stopping, move in a fast horizontal zigzag if (!self._stopped) { self._stopped = true; self._moveTimer = 0; self._baseX = self.x; self._zigzagDir = Math.random() < 0.5 ? 1 : -1; } self._moveTimer++; // Fast zigzag left/right, bounce at screen edges self.x += self._zigzagDir * 12; if (self.x < self.width / 2) { self.x = self.width / 2; self._zigzagDir *= -1; } else if (self.x > 2048 - self.width / 2) { self.x = 2048 - self.width / 2; self._zigzagDir *= -1; } } if (self.shootCooldown > 0) self.shootCooldown--; if (self.shootCooldown === 0 && Math.random() < 0.02) { self.shoot(); self.shootCooldown = self.shootRate; } self.lastY = self.y; }; self.shoot = function () { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + self.height / 2 + 10; bullet.speedY = 24; // Faster bullets enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); }; self.takeDamage = function (amount) { self.hp -= amount; LK.getSound('enemyHit').play(); LK.effects.flashObject(self, 0xffffff, 200); if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Laser: Piercing straight beam var Laser = Container.expand(function () { var self = Container.call(this); // Determine laser width based on ship.laserSize, default to 30, max 300 var laserWidth = 30; if (typeof ship !== "undefined" && typeof ship.laserSize !== "undefined") { laserWidth = Math.min(300, ship.laserSize); } var gfx = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.0 }); gfx.width = laserWidth; gfx.height = 400; gfx.tint = 0x00ffff; self.width = gfx.width; self.height = gfx.height; self.speedY = -40; self.lifetime = 60; self.update = function () { self.y += self.speedY; self.lifetime--; if (self.lifetime <= 0) { self.destroyed = true; } }; return self; }); // Player Bullet var PlayerBullet = Container.expand(function () { var self = Container.call(this); var gfx = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Determine bullet size based on ship.bulletSize var sizeMultiplier = 1; if (typeof ship !== "undefined" && typeof ship.bulletSize !== "undefined") { sizeMultiplier = ship.bulletSize; } gfx.width = gfx.width * sizeMultiplier; gfx.height = gfx.height * sizeMultiplier; self.width = gfx.width; self.height = gfx.height; self.speedY = -28; self.speedX = 0; // Set bullet damage based on ship.bulletDamage, default to 1 self.damage = 1; if (typeof ship !== "undefined" && typeof ship.bulletDamage !== "undefined") { self.damage = ship.bulletDamage; } self.update = function () { self.y += self.speedY; self.x += self.speedX; }; return self; }); // Player Ship var Ship = Container.expand(function () { var self = Container.call(this); // Add left wing var leftWing = self.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5 }); leftWing.width = 60; leftWing.height = 120; leftWing.x = -70; leftWing.y = 20; leftWing.rotation = -0.35; leftWing.alpha = 0.7; // Add right wing var rightWing = self.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5 }); rightWing.width = 60; rightWing.height = 120; rightWing.x = 70; rightWing.y = 20; rightWing.rotation = 0.35; rightWing.alpha = 0.7; // Main ship body var shipGfx = self.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5 }); self.width = shipGfx.width; self.height = shipGfx.height; self.fireCooldown = 0; self.fireRate = 30; // ticks between shots self.bulletSpeed = -28; self.bulletCount = 1; self.bulletSpread = 0; self.bulletSize = 1; // Default bullet size multiplier self.bulletDamage = 1; // Default bullet damage self.doubleShot = false; self.tripleShot = false; self.laser = false; self.hp = 100; self.maxHp = 100; self.invulnTicks = 0; self.update = function () { if (self.fireCooldown > 0) self.fireCooldown--; if (self.invulnTicks > 0) { self.invulnTicks--; shipGfx.alpha = LK.ticks % 8 < 4 ? 0.4 : 1; leftWing.alpha = shipGfx.alpha * 0.7; rightWing.alpha = shipGfx.alpha * 0.7; } else { shipGfx.alpha = 1; leftWing.alpha = 0.7; rightWing.alpha = 0.7; } }; self.shoot = function () { if (self.fireCooldown > 0) return; self.fireCooldown = self.fireRate; self._buzzSawFiredThisShot = false; self._bombFiredThisShot = false; var shots = []; var spread = self.bulletSpread; var count = self.bulletCount; if (self.tripleShot) { count = 3; spread = 0.25; } else if (self.doubleShot) { count = 2; spread = 0.18; } // Laser upgrade if (self.laser) { var laser = new Laser(); laser.x = self.x; laser.y = self.y - self.height / 2 - 10; playerBullets.push(laser); game.addChild(laser); shots.push(laser); } // Bomb upgrade if (self.bomb && !self._bombFiredThisShot) { var bomb = new Bomb(); bomb.x = self.x; bomb.y = self.y - self.height / 2 - 10; bomb.owner = self; // Add a subtle yellow pulsing glow to indicate it's powerful for (var i = 0; i < bomb.children.length; i++) { if (bomb.children[i].width === 400 && bomb.children[i].tint === 0xffff00) { var blastRadiusGfx = bomb.children[i]; blastRadiusGfx.alpha = 0.1; // Pulse animation tween(blastRadiusGfx, { alpha: 0.25 }, { duration: 400, easing: tween.linear, onFinish: function onFinish() { tween(blastRadiusGfx, { alpha: 0.1 }, { duration: 400, easing: tween.linear }); } }); break; } } playerBullets.push(bomb); game.addChild(bomb); shots.push(bomb); self._bombFiredThisShot = true; } // Normal bullets for (var i = 0; i < count; i++) { var angle = (i - (count - 1) / 2) * spread; var bullet = new PlayerBullet(); bullet.x = self.x; bullet.y = self.y - self.height / 2 - 10; bullet.speedY = self.bulletSpeed * Math.cos(angle); bullet.speedX = self.bulletSpeed * Math.sin(angle); playerBullets.push(bullet); game.addChild(bullet); shots.push(bullet); } LK.getSound('shoot').play(); return shots; }; self.takeDamage = function (amount) { if (self.invulnTicks > 0) return; self.hp -= amount; self.invulnTicks = 60; LK.effects.flashObject(self, 0xff0000, 400); if (self.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } }; return self; }); // TankEnemy: Slow but tough enemy var TankEnemy = Container.expand(function () { var self = Container.call(this); // Add left wing var leftWing = self.attachAsset('tankEnemy', { anchorX: 0.5, anchorY: 0.5 }); leftWing.width = 60; leftWing.height = 140; leftWing.x = -90; leftWing.y = 20; leftWing.rotation = -0.25; leftWing.alpha = 0.7; // Add right wing var rightWing = self.attachAsset('tankEnemy', { anchorX: 0.5, anchorY: 0.5 }); rightWing.width = 60; rightWing.height = 140; rightWing.x = 90; rightWing.y = 20; rightWing.rotation = 0.25; rightWing.alpha = 0.7; var gfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); gfx.tint = 0x888888; // Gray color for tank enemy // Make it larger gfx.width = gfx.width * 1.5; gfx.height = gfx.height * 1.5; self.width = gfx.width; self.height = gfx.height; self.scaleX = 1; self.scaleY = 1; self.alpha = 1; self.speedY = 3; // Slower than regular enemy self.hp = 4; // More HP than regular enemy self.maxHp = 4; self.shootCooldown = 0; self.shootRate = 80 + Math.floor(Math.random() * 40); // Shoots more often self.value = 3; // Worth more points than regular enemy self.update = function () { // Store lastY for transition detection if (self.lastY === undefined) self.lastY = self.y; var stopY = 2732 / 2.1; // Stops at a different position if (self.y < stopY) { self.y += self.speedY; self._stopped = false; } else { self.y = stopY; // After stopping, move in a slow, heavy "lurch" pattern (pause, then move a bit, then pause) if (!self._stopped) { self._stopped = true; self._moveTimer = 0; self._baseX = self.x; self._lurchDir = Math.random() < 0.5 ? 1 : -1; } self._moveTimer++; // Every 90 frames, lurch left or right by 60px, then pause if (self._moveTimer % 120 === 0) { self._lurchDir *= -1; } if (self._moveTimer % 120 < 30) { // Lurch for 30 frames self.x = self._baseX + self._lurchDir * 60; } else { // Pause for 90 frames self.x = self._baseX; } } if (self.shootCooldown > 0) self.shootCooldown--; if (self.shootCooldown === 0 && Math.random() < 0.03) { self.shoot(); self.shootCooldown = self.shootRate; } self.lastY = self.y; }; self.shoot = function () { // Tank enemy shoots multiple bullets in a spread for (var i = -1; i <= 1; i++) { var bullet = new EnemyBullet(); bullet.x = self.x + i * 20; bullet.y = self.y + self.height / 2 + 10; enemyBullets.push(bullet); game.addChild(bullet); } LK.getSound('enemyShoot').play(); }; self.takeDamage = function (amount) { self.hp -= amount; LK.getSound('enemyHit').play(); LK.effects.flashObject(self, 0xffffff, 200); if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Upgrade Card var UpgradeCard = Container.expand(function () { var self = Container.call(this); var highlight = self.attachAsset('upgradeHighlight', { anchorX: 0.5, anchorY: 0.5 }); highlight.visible = false; var card = self.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5 }); self.text = new Text2('', { size: 70, fill: 0xFFFFFF, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); self.text.anchor.set(0.5, 0.5); self.addChild(self.text); self.setText = function (t) { self.text.setText(t); }; self.setHighlighted = function (v) { highlight.visible = v; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game state variables // Ship (player) // Enemy // Bullet (player) // Enemy bullet // Upgrade card // Upgrade highlight // Sound effects // Music var ship; var playerBullets = []; var enemies = []; var enemyBullets = []; var wave = 1; var enemiesToSpawn = 0; var enemiesDefeated = 0; var enemiesPerWave = 8; var waveInProgress = false; var upgradePending = false; var upgradeOptions = []; var upgradeCards = []; var dragging = false; var dragOffsetX = 0; var dragOffsetY = 0; var score = 0; var scoreTxt; var hpTxt; var waveTxt; var upgradeGroup; var lastTouch = { x: 0, y: 0 }; // Set up background color game.setBackgroundColor(0x0a0a18); // --- Starfield background --- var starCount = 120; var stars = []; for (var i = 0; i < starCount; i++) { var star = new Container(); // Use a white ellipse for stars, randomize size for variety var size = 2 + Math.random() * 3; var gfx = star.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, width: size, height: size, color: 0xffffff }); star.x = Math.random() * 2048; star.y = Math.random() * 2732; star.alpha = 0.4 + Math.random() * 0.6; star.speed = 0.7 + Math.random() * 1.3; // Parallax: slower for dimmer stars stars.push(star); // Add behind everything game.addChildAt(star, 0); } // Animate stars in game.update var oldGameUpdate = game.update; game.update = function () { // Move stars downward, wrap to top for (var i = 0; i < stars.length; i++) { var s = stars[i]; s.y += s.speed; if (s.y > 2732) { s.y = -5; s.x = Math.random() * 2048; } } if (typeof oldGameUpdate === "function") oldGameUpdate(); }; // Score display scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // HP display hpTxt = new Text2('HP: 3', { size: 70, fill: "#fff", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); hpTxt.anchor.set(0.5, 0); LK.gui.top.addChild(hpTxt); hpTxt.y = 100; // Wave display waveTxt = new Text2('Wave: 1', { size: 70, fill: "#fff", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); waveTxt.anchor.set(0.5, 0); LK.gui.top.addChild(waveTxt); waveTxt.y = 200; // Hide UI during menu, show after game start scoreTxt.visible = false; hpTxt.visible = false; waveTxt.visible = false; // Upgrade group (overlay) upgradeGroup = new Container(); upgradeGroup.visible = false; game.addChild(upgradeGroup); // Initialize player ship ship = new Ship(); game.addChild(ship); ship.x = 2048 / 2; ship.y = 2732 - 180; // Start first wave // startWave(); // Handle dragging ship game.down = function (x, y, obj) { if (upgradePending) return; if (pointInShip(x, y)) { dragging = true; dragOffsetX = ship.x - x; dragOffsetY = ship.y - y; } lastTouch.x = x; lastTouch.y = y; }; game.move = function (x, y, obj) { if (upgradePending) { // Handle upgrade card selection for (var i = 0; i < upgradeCards.length; i++) { var card = upgradeCards[i]; var bounds = getCardBounds(card); if (x >= bounds.x && x <= bounds.x + bounds.w && y >= bounds.y && y <= bounds.y + bounds.h) { for (var j = 0; j < upgradeCards.length; j++) { upgradeCards[j].setHighlighted(j === i); } } } lastTouch.x = x; lastTouch.y = y; return; } if (dragging) { var nx = x + dragOffsetX; var ny = y + dragOffsetY; // Clamp to screen nx = Math.max(ship.width / 2, Math.min(2048 - ship.width / 2, nx)); ny = Math.max(2732 - 400, Math.min(2732 - ship.height / 2, ny)); ship.x = nx; ship.y = ny; } lastTouch.x = x; lastTouch.y = y; }; game.up = function (x, y, obj) { if (upgradePending) { // Check if an upgrade card was selected for (var i = 0; i < upgradeCards.length; i++) { var card = upgradeCards[i]; var bounds = getCardBounds(card); if (x >= bounds.x && x <= bounds.x + bounds.w && y >= bounds.y && y <= bounds.y + bounds.h) { selectUpgrade(i); break; } } return; } dragging = false; }; // Main game update loop game.update = function () { if (upgradePending) return; // Ship update ship.update(); // Timers for laser and bomb firing if (typeof laserTimer === "undefined") { laserTimer = 0; } if (typeof bombTimer === "undefined") { bombTimer = 0; } // Track bomb upgrade count if (typeof ship.bombCount === "undefined") { ship.bombCount = ship.bomb ? 1 : 0; } if (ship.bomb && ship.bombCount === 0) ship.bombCount = 1; // Ship auto-fire (normal bullets only) if (ship.fireCooldown === 0) { // Temporarily disable laser for normal auto-fire var hadLaser = ship.laser, hadBomb = ship.bomb; var fireLaser = false, fireBomb = false; if (ship.laser) { if (laserTimer <= 0) { fireLaser = true; laserTimer = 300; } else { ship.laser = false; } } if (ship.bomb) { if (bombTimer <= 0) { fireBomb = true; bombTimer = 300; // 5 seconds at 60fps } else { ship.bomb = false; } } ship.shoot(); // Restore laser/bomb flags for next time ship.laser = hadLaser; ship.bomb = hadBomb; // If it's time, fire laser if (fireLaser) { var oldLaser = ship.laser; ship.laser = true; ship.shoot(); ship.laser = oldLaser; } if (fireBomb) { var oldBomb = ship.bomb; ship.bomb = true; // Fire as many bombs as upgrades selected var bombCount = ship.bombCount || 1; for (var i = 0; i < bombCount; i++) { ship.shoot(); } ship.bomb = oldBomb; } } if (laserTimer > 0) laserTimer--; if (bombTimer > 0) bombTimer--; // Player bullets for (var i = playerBullets.length - 1; i >= 0; i--) { var b = playerBullets[i]; b.update(); // Remove if off screen or destroyed if (b.y < -b.height || b.y > 2732 + (b.height || 0) || b.x < -200 || b.x > 2048 + 200 || b.destroyed) { b.destroy(); playerBullets.splice(i, 1); continue; } // Check collision with enemies for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; // Laser: deal damage, pierce, don't destroy on hit if (b instanceof Laser && b.intersects(e)) { e.takeDamage(2); if (e.hp <= 0 && !e.destroyed) { e.destroyed = true; } // Don't remove laser, allow piercing continue; } // Bomb: explode on impact, damage all enemies in radius if (b instanceof Bomb && b.intersects(e)) { // First handle the enemy that was directly hit e.takeDamage(5); // Increased direct hit damage if (e.hp <= 0 && !e.destroyed) { e.destroyed = true; } // Then trigger explosion for area damage b.explode(); // Get all enemies in blast radius for (var k = enemies.length - 1; k >= 0; k--) { if (k === j) continue; // Skip the one we already hit var target = enemies[k]; var dx = target.x - b.x; var dy = target.y - b.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= b.blastRadius) { // Damage falls off with distance but starts higher var damageMultiplier = 1 - distance / b.blastRadius; var damage = Math.max(2, Math.floor(4 * damageMultiplier)); // Increased area damage target.takeDamage(damage); } } playerBullets.splice(i, 1); break; } // Normal bullet if (b.intersects(e)) { var dmg = typeof b.damage !== "undefined" ? b.damage : 1; e.takeDamage(dmg); b.destroy(); playerBullets.splice(i, 1); if (e.hp <= 0 && !e.destroyed) { e.destroyed = true; } break; } } } // Enemies for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); // Remove if off screen if (e.y > 2732 + e.height) { e.destroy(); enemies.splice(i, 1); continue; } // Remove if destroyed if (e.destroyed) { score += e.value; enemiesDefeated++; scoreTxt.setText('Score: ' + score); e.destroy(); enemies.splice(i, 1); LK.getSound('hit').play(); // Check for wave end if (enemiesDefeated >= enemiesPerWave) { setTimeout(showUpgrade, 600); } continue; } // Check collision with ship if (e.intersects(ship) && ship.invulnTicks === 0) { ship.takeDamage(1); e.destroyed = true; } } // Enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var eb = enemyBullets[i]; eb.update(); if (eb.y > 2732 + eb.height) { eb.destroy(); enemyBullets.splice(i, 1); continue; } if (eb.intersects(ship) && ship.invulnTicks === 0) { ship.takeDamage(1); eb.destroy(); enemyBullets.splice(i, 1); continue; } } // Spawn enemies if (waveInProgress && enemiesToSpawn > 0 && enemies.length < Math.min(5 + wave, 10)) { spawnEnemy(); } // Update HP and wave display hpTxt.setText('HP: ' + ship.hp); waveTxt.setText('Wave: ' + wave); }; // Start a new wave function startWave() { waveInProgress = true; enemiesToSpawn = enemiesPerWave; enemiesDefeated = 0; waveTxt.setText('Wave: ' + wave); } // Spawn a single enemy function spawnEnemy() { // Choose enemy type based on wave and random chance var rand = Math.random(); var enemyType; // Wave 1: Only basic enemies if (wave === 1) { enemyType = "basic"; } // Wave 2-3: Introduce fast enemies else if (wave <= 3) { if (rand < 0.2) enemyType = "fast";else enemyType = "basic"; } // Wave 4-5: Introduce big enemies and tank enemies else if (wave <= 5) { if (rand < 0.2) enemyType = "big";else if (rand < 0.35) enemyType = "fast";else if (rand < 0.45) enemyType = "tank";else enemyType = "basic"; } // Wave 6+: More variety with increasing difficulty else { if (rand < 0.25) enemyType = "big";else if (rand < 0.45) enemyType = "fast";else if (rand < 0.65) enemyType = "tank";else enemyType = "basic"; } // Force a big enemy to appear in later waves if (wave > 4 && enemiesToSpawn === enemiesPerWave - 1 && enemyType !== "big") { enemyType = "big"; } var e; // Create the selected enemy type if (enemyType === "big") { e = new BigEnemy(); e.x = 200 + Math.random() * (2048 - 400); e.y = -e.height / 2 - 10; // Scale up HP and value with wave e.hp = 6 + Math.floor(wave * 1.5); e.maxHp = e.hp; e.speedY = 4 + Math.floor(wave / 6); e.shootRate = Math.max(40, 90 - wave * 2 + Math.floor(Math.random() * 30)); e.value = 5 + Math.floor(wave * 1.2); } else if (enemyType === "fast") { e = new FastEnemy(); e.x = 150 + Math.random() * (2048 - 300); e.y = -e.height / 2 - 10; // Scale up with wave e.hp = Math.max(1, Math.floor(wave / 3)); e.maxHp = e.hp; e.speedY = 10 + Math.floor(wave / 4); e.shootRate = Math.max(100, 120 - wave * 3 + Math.floor(Math.random() * 40)); e.value = 2 + Math.floor(wave / 3); } else if (enemyType === "tank") { e = new TankEnemy(); e.x = 250 + Math.random() * (2048 - 500); e.y = -e.height / 2 - 10; // Scale up with wave e.hp = 4 + Math.floor(wave * 1.2); e.maxHp = e.hp; e.speedY = 3 + Math.floor(wave / 8); e.shootRate = Math.max(50, 80 - wave * 2 + Math.floor(Math.random() * 30)); e.value = 3 + Math.floor(wave / 2); } else { e = new Enemy(); e.x = 180 + Math.random() * (2048 - 360); e.y = -e.height / 2 - 10; // Increase difficulty per wave e.hp = 1 + Math.floor(wave / 2); e.maxHp = e.hp; e.speedY = 6 + Math.floor(wave / 3); e.shootRate = Math.max(60, 120 - wave * 4 + Math.floor(Math.random() * 40)); e.value = 1 + Math.floor(wave / 2); } enemies.push(e); game.addChild(e); enemiesToSpawn--; } // Show upgrade selection function showUpgrade() { if (upgradePending) return; upgradePending = true; waveInProgress = false; upgradeGroup.visible = true; // Generate 3 upgrade options upgradeOptions = getUpgradeOptions(); // Remove old cards for (var i = 0; i < upgradeCards.length; i++) { upgradeCards[i].destroy(); } upgradeCards = []; // Layout cards in 3 rows for better visibility var centerX = 2048 / 2; var centerY = 2732 / 2; var cardSpacingY = 380; var cardSpacingX = 0; // All cards centered horizontally for (var i = 0; i < 3; i++) { var card = new UpgradeCard(); card.x = centerX + cardSpacingX; card.y = centerY + (i - 1) * cardSpacingY; card.setText(upgradeOptions[i].label); card.setHighlighted(false); // Color special upgrades yellow if (upgradeOptions[i].special) { // Find the card background and set its tint to yellow for (var j = 0; j < card.children.length; j++) { if (card.children[j] && card.children[j].tint !== undefined) { card.children[j].tint = 0xffe066; // yellow } } // Also set text color to dark for contrast if (card.text && card.text.style) { card.text.style.fill = "#664400"; card.text.dirty = true; } } upgradeGroup.addChild(card); upgradeCards.push(card); } } // Select an upgrade function selectUpgrade(idx) { if (!upgradePending) return; var upg = upgradeOptions[idx]; applyUpgrade(upg); LK.getSound('upgrade').play(); // Hide upgrade UI for (var i = 0; i < upgradeCards.length; i++) { upgradeCards[i].destroy(); } upgradeCards = []; upgradeGroup.visible = false; upgradePending = false; // Next wave wave++; enemiesPerWave = Math.min(20, 8 + Math.floor(wave * 1.5)); startWave(); } // Generate upgrade options function getUpgradeOptions() { var pool = [{ label: "+1 Max HP", apply: function apply() { ship.maxHp += 1; ship.hp = ship.maxHp; } }, { label: "+2 Max HP", special: true, apply: function apply() { ship.maxHp += 2; ship.hp = ship.maxHp; } }, { label: "+1 Bullet per shot", apply: function apply() { ship.bulletCount = Math.min(5, ship.bulletCount + 1); } }, { label: "+2 Bullets per shot", special: true, apply: function apply() { ship.bulletCount = Math.min(5, ship.bulletCount + 2); } }, { label: "Faster Fire Rate", apply: function apply() { ship.fireRate = Math.max(8, ship.fireRate - 4); } }, { label: "Much Faster Fire Rate", special: true, apply: function apply() { ship.fireRate = Math.max(8, ship.fireRate - 8); } }, { label: "Wider Spread", apply: function apply() { ship.bulletSpread = Math.min(0.5, ship.bulletSpread + 0.08); } }, { label: "Much Wider Spread", special: true, apply: function apply() { ship.bulletSpread = Math.min(0.5, ship.bulletSpread + 0.16); } }, { label: "Double Shot", apply: function apply() { ship.doubleShot = true; } }, { label: "Triple Shot", apply: function apply() { ship.tripleShot = true; } }, { label: "Laser (Piercing Beam)", apply: function apply() { ship.laser = true; if (typeof ship.laserSize === "undefined") { ship.laserSize = 30; ship.laserUpgradeCount = 1; } else { ship.laserUpgradeCount++; // Each upgrade increases width by 60, up to 300 ship.laserSize = Math.min(300, 30 + (ship.laserUpgradeCount - 1) * 60); } } }, { label: "Laser x2 (Piercing Beam)", special: true, apply: function apply() { ship.laser = true; if (typeof ship.laserUpgradeCount === "undefined") { ship.laserUpgradeCount = 2; ship.laserSize = Math.min(300, 30 + (ship.laserUpgradeCount - 1) * 60); } else { ship.laserUpgradeCount += 2; ship.laserSize = Math.min(300, 30 + (ship.laserUpgradeCount - 1) * 60); } } }, { label: "Bomb (Area Explosion)", apply: function apply() { ship.bomb = true; if (typeof ship.bombCount === "undefined") { ship.bombCount = 1; } else { ship.bombCount++; } } }, { label: "Bomb x2 (Area Explosion)", special: true, apply: function apply() { ship.bomb = true; if (typeof ship.bombCount === "undefined") { ship.bombCount = 2; } else { ship.bombCount += 2; } } }, { label: "Big Bullets", apply: function apply() { if (typeof ship.bulletSize === "undefined") { ship.bulletSize = 1.5; // First upgrade: 50% bigger } else { ship.bulletSize += 0.5; // Each upgrade makes bullets bigger } } }, { label: "Huge Bullets", special: true, apply: function apply() { if (typeof ship.bulletSize === "undefined") { ship.bulletSize = 2.0; } else { ship.bulletSize += 1.0; } } }, { label: "Big Bullets", apply: function apply() { if (typeof ship.bulletSize === "undefined") { ship.bulletSize = 1.5; // First upgrade: 50% bigger } else { ship.bulletSize += 0.5; // Each upgrade makes bullets bigger } } }, { label: "Bullet Damage Up", apply: function apply() { if (typeof ship.bulletDamage === "undefined") { ship.bulletDamage = 2; } else { ship.bulletDamage += 1; } } }, { label: "Bullet Damage Up x2", special: true, apply: function apply() { if (typeof ship.bulletDamage === "undefined") { ship.bulletDamage = 3; } else { ship.bulletDamage += 2; } } }]; // Remove upgrades already owned if (ship.doubleShot) pool = pool.filter(function (u) { return u.label !== "Double Shot"; }); if (ship.tripleShot) pool = pool.filter(function (u) { return u.label !== "Triple Shot"; }); // Buzz Saw and Laser upgrades are always available for selection // Pick 3 random upgrades, but guarantee at least one special (yellow) if possible var specials = pool.filter(function (u) { return u.special; }); var normals = pool.filter(function (u) { return !u.special; }); var options = []; var used = {}; // 30% chance to include a special, or always if none chosen yet and available if (specials.length > 0 && Math.random() < 0.3) { var idx = Math.floor(Math.random() * specials.length); options.push(specials[idx]); used[specials[idx].label] = true; } while (options.length < 3) { var poolToUse = Math.random() < 0.3 && specials.length > 0 ? specials : normals; var idx = Math.floor(Math.random() * poolToUse.length); var upg = poolToUse[idx]; if (!used[upg.label]) { options.push(upg); used[upg.label] = true; } } return options; } // Apply upgrade function applyUpgrade(upg) { upg.apply(); } // Utility: check if point is in ship function pointInShip(x, y) { return x >= ship.x - ship.width / 2 && x <= ship.x + ship.width / 2 && y >= ship.y - ship.height / 2 && y <= ship.y + ship.height / 2; } // Utility: get card bounds function getCardBounds(card) { return { x: card.x - 310, y: card.y - 160, w: 620, h: 320 }; } // Utility: setTimeout using LK function setTimeout(fn, ms) { return LK.setTimeout(fn, ms); } // Start music LK.playMusic('bgmusic', { fade: { start: 0, end: 1, duration: 1200 } }); // --- Ship Selection Menu --- // --- Game Title and Instructions --- var titleTxt = new Text2("GALACTIC DEFENSE", { size: 120, fill: "#fff", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); titleTxt.anchor.set(0.5, 0); titleTxt.x = 2048 / 2; titleTxt.y = 80; game.addChild(titleTxt); var instrTxt = new Text2("Choose your ship and tap Start!\nDrag your ship to dodge and shoot.\nSurvive waves, collect upgrades.", { size: 60, fill: "#fff", font: "'GillSans',Arial,'Arial Black',Tahoma,sans-serif" }); instrTxt.anchor.set(0.5, 0); instrTxt.x = 2048 / 2; instrTxt.y = 240; game.addChild(instrTxt); // Define available ships var shipTypes = [{ name: "Balanced", color: 0x66ccff, hp: 100, fireRate: 30, bulletCount: 1, bulletSpread: 0, bulletSize: 1, bulletDamage: 1, desc: "Balanced all-rounder" }, { name: "Tank", color: 0x44bb44, hp: 160, fireRate: 44, bulletCount: 1, bulletSpread: 0, bulletSize: 1.2, bulletDamage: 2, desc: "High HP, slow fire, strong bullets" }, { name: "Rapid", color: 0xffaa00, hp: 70, fireRate: 14, bulletCount: 1, bulletSpread: 0, bulletSize: 0.8, bulletDamage: 1, desc: "Low HP, very fast fire" }, { name: "Spread", color: 0xdd33cc, hp: 90, fireRate: 32, bulletCount: 3, bulletSpread: 0.22, bulletSize: 0.9, bulletDamage: 1, desc: "Triple shot, moderate stats" }]; var selectedShipType = 0; // Ship selection group var shipSelectGroup = new Container(); game.addChild(shipSelectGroup); // Create ship cards var shipCards = []; var cardSpacing = 500; var centerX = 2048 / 2; var centerY = 2732 / 2 - 200; for (var i = 0; i < shipTypes.length; i++) { var card = new Container(); var cardGfx = card.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5 }); cardGfx.width = 540; cardGfx.height = 670; cardGfx.tint = shipTypes[i].color; // Ship name var nameTxt = new Text2(shipTypes[i].name, { size: 90, fill: "#fff", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); nameTxt.anchor.set(0.5, 0); nameTxt.y = -270; card.addChild(nameTxt); // Ship preview var preview = card.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5 }); preview.width = 180; preview.height = 180; preview.tint = shipTypes[i].color; preview.y = -100; // Ship stats var statsTxt = new Text2("HP: " + shipTypes[i].hp + "\nFire: " + (60 / shipTypes[i].fireRate).toFixed(1) + "/s" + "\nBullets: " + shipTypes[i].bulletCount + "\nDamage: " + shipTypes[i].bulletDamage, { size: 48, fill: "#fff", font: "'GillSans',Arial,'Arial Black',Tahoma,sans-serif" }); statsTxt.anchor.set(0.5, 0); statsTxt.y = 100; card.addChild(statsTxt); // Description var descTxt = new Text2(shipTypes[i].desc, { size: 52, fill: "#fff", font: "'GillSans',Arial,'Arial Black',Tahoma,sans-serif" }); descTxt.anchor.set(0.5, 0); descTxt.y = 300; card.addChild(descTxt); // Arrange in rows of 2 var row = Math.floor(i / 2); var col = i % 2; var rowSpacing = 800; var colSpacing = 700; // Center the two columns var totalCols = 2; var xOffset = (col - (totalCols - 1) / 2) * colSpacing; var yOffset = row * rowSpacing; card.x = centerX + xOffset; card.y = centerY + yOffset; card.cardIndex = i; shipSelectGroup.addChild(card); shipCards.push(card); } // Glow effect for selected card var selectGlow = new Container(); var glowGfx = selectGlow.attachAsset('ship', { anchorX: 0.5, anchorY: 0.5 }); glowGfx.width = 320; glowGfx.height = 320; glowGfx.tint = 0xffff66; glowGfx.alpha = 0.45; selectGlow.visible = true; shipSelectGroup.addChild(selectGlow); // Animate the glow (pulsing) function pulseGlow() { tween(glowGfx, { alpha: 0.15 }, { duration: 700, easing: tween.linear, onFinish: function onFinish() { tween(glowGfx, { alpha: 0.45 }, { duration: 700, easing: tween.linear, onFinish: pulseGlow }); } }); } pulseGlow(); function updateShipHighlight() { selectGlow.x = shipCards[selectedShipType].x; selectGlow.y = shipCards[selectedShipType].y - 100; // Center behind ship preview } updateShipHighlight(); // Ship card touch selection shipSelectGroup.down = function (x, y, obj) { for (var i = 0; i < shipCards.length; i++) { var card = shipCards[i]; var bx = card.x - 210, by = card.y - 260, bw = 420, bh = 520; if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) { selectedShipType = i; updateShipHighlight(); break; } } }; // Ship card swipe (left/right) shipSelectGroup.move = function (x, y, obj) { // Optional: could implement swipe, but for now tap to select }; // --- Start Button --- // --- Polished Start Button --- var startButton = new Container(); // Shadow effect (soft drop shadow) var shadowGfx = startButton.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5 }); shadowGfx.width = 420; shadowGfx.height = 220; shadowGfx.tint = 0x222222; shadowGfx.alpha = 0.28; shadowGfx.y = 16; // Main button with rounded look (simulate with slightly larger highlight behind) var buttonHighlight = startButton.attachAsset('upgradeHighlight', { anchorX: 0.5, anchorY: 0.5 }); buttonHighlight.width = 440; buttonHighlight.height = 240; buttonHighlight.tint = 0x66ff99; buttonHighlight.alpha = 0.18; // Main button var buttonGfx = startButton.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5 }); buttonGfx.width = 400; buttonGfx.height = 200; buttonGfx.tint = 0x00e676; // Modern green // Animated pulse for highlight function pulseButtonHighlight() { tween(buttonHighlight, { alpha: 0.32 }, { duration: 700, easing: tween.linear, onFinish: function onFinish() { tween(buttonHighlight, { alpha: 0.18 }, { duration: 700, easing: tween.linear, onFinish: pulseButtonHighlight }); } }); } pulseButtonHighlight(); // Button text var buttonText = new Text2('Start', { size: 100, fill: "#fff", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif" }); buttonText.anchor.set(0.5, 0.5); buttonText.y = 0; startButton.addChild(buttonText); // Subtle shine effect (simulated with a semi-transparent white ellipse) var shine = startButton.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); shine.width = 320; shine.height = 60; shine.tint = 0xffffff; shine.alpha = 0.13; shine.y = -40; // Position the start button higher above the ship cards startButton.x = 2048 / 2; startButton.y = 2732 / 2 - 600; game.addChild(startButton); // Animated press feedback startButton.down = function (x, y, obj) { // Animate press: shrink and darken tween(buttonGfx, { scaleX: 0.96, scaleY: 0.96, tint: 0x00b050 }, { duration: 80, easing: tween.linear }); tween(buttonHighlight, { scaleX: 0.98, scaleY: 0.98, alpha: 0.32 }, { duration: 80, easing: tween.linear }); tween(shadowGfx, { alpha: 0.38 }, { duration: 80, easing: tween.linear }); // After short delay, restore and trigger start LK.setTimeout(function () { tween(buttonGfx, { scaleX: 1, scaleY: 1, tint: 0x00e676 }, { duration: 120, easing: tween.linear }); tween(buttonHighlight, { scaleX: 1, scaleY: 1, alpha: 0.18 }, { duration: 120, easing: tween.linear }); tween(shadowGfx, { alpha: 0.28 }, { duration: 120, easing: tween.linear }); // Hide menu UI startButton.visible = false; shipSelectGroup.visible = false; if (typeof titleTxt !== "undefined") titleTxt.visible = false; if (typeof instrTxt !== "undefined") instrTxt.visible = false; // Show game UI scoreTxt.visible = true; hpTxt.visible = true; waveTxt.visible = true; // Initialize game state wave = 1; enemiesToSpawn = 0; enemiesDefeated = 0; enemiesPerWave = 8; waveInProgress = false; upgradePending = false; upgradeOptions = []; upgradeCards = []; dragging = false; dragOffsetX = 0; dragOffsetY = 0; score = 0; scoreTxt.setText('Score: 0'); // Set up ship with selected type var t = shipTypes[selectedShipType]; // Remove old ship if present if (ship && typeof ship.destroy === "function") ship.destroy(); ship = new Ship(); game.addChild(ship); ship.x = 2048 / 2; ship.y = 2732 - 180; ship.hp = t.hp; ship.maxHp = t.hp; ship.fireRate = t.fireRate; ship.bulletCount = t.bulletCount; ship.bulletSpread = t.bulletSpread; ship.bulletSize = t.bulletSize; ship.bulletDamage = t.bulletDamage; // Tint ship body and wings for (var i = 0; i < ship.children.length; i++) { if (ship.children[i] && ship.children[i].tint !== undefined) { ship.children[i].tint = t.color; } } hpTxt.setText('HP: ' + ship.hp); waveTxt.setText('Wave: ' + wave); // Start first wave startWave(); }, 120); }; // Add event listener for the start button startButton.down = function (x, y, obj) { // Hide menu UI startButton.visible = false; shipSelectGroup.visible = false; if (typeof titleTxt !== "undefined") titleTxt.visible = false; if (typeof instrTxt !== "undefined") instrTxt.visible = false; // Show game UI scoreTxt.visible = true; hpTxt.visible = true; waveTxt.visible = true; // Initialize game state wave = 1; enemiesToSpawn = 0; enemiesDefeated = 0; enemiesPerWave = 8; waveInProgress = false; upgradePending = false; upgradeOptions = []; upgradeCards = []; dragging = false; dragOffsetX = 0; dragOffsetY = 0; score = 0; scoreTxt.setText('Score: 0'); // Set up ship with selected type var t = shipTypes[selectedShipType]; // Remove old ship if present if (ship && typeof ship.destroy === "function") ship.destroy(); ship = new Ship(); game.addChild(ship); ship.x = 2048 / 2; ship.y = 2732 - 180; ship.hp = t.hp; ship.maxHp = t.hp; ship.fireRate = t.fireRate; ship.bulletCount = t.bulletCount; ship.bulletSpread = t.bulletSpread; ship.bulletSize = t.bulletSize; ship.bulletDamage = t.bulletDamage; // Tint ship body and wings for (var i = 0; i < ship.children.length; i++) { if (ship.children[i] && ship.children[i].tint !== undefined) { ship.children[i].tint = t.color; } } hpTxt.setText('HP: ' + ship.hp); waveTxt.setText('Wave: ' + wave); // Start first wave startWave(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Big Enemy
var BigEnemy = Container.expand(function () {
var self = Container.call(this);
// Add left wing
var leftWing = self.attachAsset('bigEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
leftWing.width = 80;
leftWing.height = 180;
leftWing.x = -110;
leftWing.y = 30;
leftWing.rotation = -0.28;
leftWing.alpha = 0.7;
// Add right wing
var rightWing = self.attachAsset('bigEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
rightWing.width = 80;
rightWing.height = 180;
rightWing.x = 110;
rightWing.y = 30;
rightWing.rotation = 0.28;
rightWing.alpha = 0.7;
var gfx = self.attachAsset('bigEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = gfx.width;
self.height = gfx.height;
self.scaleX = 1;
self.scaleY = 1;
self.alpha = 1;
self.speedY = 4;
self.hp = 6;
self.maxHp = 6;
self.shootCooldown = 0;
self.shootRate = 70 + Math.floor(Math.random() * 40);
self.value = 5;
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
var stopY = 2732 / 2.5; // stops a bit higher than regular enemy
if (self.y < stopY) {
self.y += self.speedY;
self._stopped = false;
} else {
self.y = stopY;
// After stopping, move in a slow horizontal oscillation (side-to-side)
if (!self._stopped) {
self._stopped = true;
self._moveTimer = 0;
self._baseX = self.x;
}
self._moveTimer++;
// Oscillate left/right with a slow sine wave
self.x = self._baseX + Math.sin(self._moveTimer / 60) * 120;
}
if (self.shootCooldown > 0) self.shootCooldown--;
if (self.shootCooldown === 0 && Math.random() < 0.04) {
self.shoot();
self.shootCooldown = self.shootRate;
}
self.lastY = self.y;
};
self.shoot = function () {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + self.height / 2 + 10;
bullet.speedY = 22; // slightly faster bullet
enemyBullets.push(bullet);
game.addChild(bullet);
LK.getSound('enemyShoot').play();
};
self.takeDamage = function (amount) {
self.hp -= amount;
LK.getSound('enemyHit').play();
LK.effects.flashObject(self, 0xffffff, 200);
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Bomb: Explosive weapon that destroys enemies in an area
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Add blast radius indicator (initially invisible)
var blastRadiusGfx = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xffff00
});
blastRadiusGfx.width = 400; // 2x blast radius
blastRadiusGfx.height = 400; // 2x blast radius
blastRadiusGfx.alpha = 0;
blastRadiusGfx.tint = 0xffff00; // Yellow color
var gfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
gfx.width = 60;
gfx.height = 60;
gfx.tint = 0xff3300;
self.width = gfx.width;
self.height = gfx.height;
self.speedY = -18;
self.speedX = 0;
self.blastRadius = 200;
self.owner = null; // set to ship
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
// Check for collision with enemies directly in update method
for (var i = 0; i < enemies.length; i++) {
if (self.intersects(enemies[i])) {
self.explode();
break;
}
}
};
self.explode = function () {
// Show blast radius with animation
blastRadiusGfx.alpha = 0.7;
// Animate the blast radius expanding and fading
tween(blastRadiusGfx, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut
});
// --- Yellow explosion effect ---
// Create a temporary yellow explosion circle
var explosion = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xffff00
});
// Set the explosion to 200x200 as requested
explosion.width = 200;
explosion.height = 200;
explosion.alpha = 0.85;
explosion.tint = 0xffff00;
explosion.x = 0;
explosion.y = 0;
// Animate the explosion: keep it visible for about 2 seconds, then fade out
tween(explosion, {
alpha: 0.85
}, {
duration: 1800,
easing: tween.linear,
onFinish: function onFinish() {
tween(explosion, {
alpha: 0
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
if (explosion && explosion.parent) explosion.parent.removeChild(explosion);
}
});
}
});
// Flash effect for explosion
LK.effects.flashObject(self, 0xffaa00, 200);
self.destroyed = true;
};
return self;
});
// Enemy
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Add left wing
var leftWing = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
leftWing.width = 40;
leftWing.height = 80;
leftWing.x = -50;
leftWing.y = 10;
leftWing.rotation = -0.32;
leftWing.alpha = 0.7;
// Add right wing
var rightWing = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
rightWing.width = 40;
rightWing.height = 80;
rightWing.x = 50;
rightWing.y = 10;
rightWing.rotation = 0.32;
rightWing.alpha = 0.7;
// Main enemy body
var gfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = gfx.width;
self.height = gfx.height;
self.scaleX = 1;
self.scaleY = 1;
self.alpha = 1;
self.speedY = 6;
self.hp = 1;
self.maxHp = 1;
self.shootCooldown = 0;
self.shootRate = 90 + Math.floor(Math.random() * 60);
self.value = 1;
self.update = function () {
// Store lastY for transition detection
if (self.lastY === undefined) self.lastY = self.y;
var stopY = 2732 / 2; // halfway down the screen
if (self.y < stopY) {
self.y += self.speedY;
self._stopped = false;
} else {
self.y = stopY;
// After stopping, move in a small horizontal wiggle
if (!self._stopped) {
self._stopped = true;
self._moveTimer = 0;
self._baseX = self.x;
}
self._moveTimer++;
// Wiggle left/right with a small sine wave
self.x = self._baseX + Math.sin(self._moveTimer / 18) * 40;
}
if (self.shootCooldown > 0) self.shootCooldown--;
if (self.shootCooldown === 0 && Math.random() < 0.02) {
self.shoot();
self.shootCooldown = self.shootRate;
}
self.lastY = self.y;
};
self.shoot = function () {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + self.height / 2 + 10;
enemyBullets.push(bullet);
game.addChild(bullet);
LK.getSound('enemyShoot').play();
};
self.takeDamage = function (amount) {
self.hp -= amount;
LK.getSound('enemyHit').play();
LK.effects.flashObject(self, 0xffffff, 200);
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var gfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = gfx.width;
self.height = gfx.height;
self.speedY = 18;
self.update = function () {
self.y += self.speedY;
};
return self;
});
// FastEnemy: Quick enemy with less HP
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
// Add left wing
var leftWing = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
leftWing.width = 32;
leftWing.height = 64;
leftWing.x = -40;
leftWing.y = 8;
leftWing.rotation = -0.32;
leftWing.alpha = 0.7;
// Add right wing
var rightWing = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
rightWing.width = 32;
rightWing.height = 64;
rightWing.x = 40;
rightWing.y = 8;
rightWing.rotation = 0.32;
rightWing.alpha = 0.7;
var gfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
gfx.tint = 0xff6600; // Orange color for fast enemy
self.width = gfx.width;
self.height = gfx.height;
self.scaleX = 0.8; // Smaller than regular enemy
self.scaleY = 0.8;
self.alpha = 1;
self.speedY = 8; // Slightly slower than before, but still faster than regular enemy
self.hp = 1;
self.maxHp = 1;
self.shootCooldown = 0;
self.shootRate = 120 + Math.floor(Math.random() * 60); // Shoots less often
self.value = 2; // Worth more points than regular enemy
self.update = function () {
// Store lastY for transition detection
if (self.lastY === undefined) self.lastY = self.y;
var stopY = 2732 / 2.2; // Stops slightly higher than regular enemy
// Move in a zigzag pattern
if (typeof self.zigzagDir === "undefined") {
self.zigzagDir = Math.random() < 0.5 ? 1 : -1;
self.zigzagTimer = 0;
}
self.zigzagTimer++;
if (self.zigzagTimer > 30) {
self.zigzagDir *= -1;
self.zigzagTimer = 0;
}
if (self.y < stopY) {
self.y += self.speedY;
// Add zigzag motion
self.x += self.zigzagDir * 4;
// Ensure enemy stays within screen bounds
if (self.x < self.width / 2) {
self.x = self.width / 2;
self.zigzagDir *= -1;
} else if (self.x > 2048 - self.width / 2) {
self.x = 2048 - self.width / 2;
self.zigzagDir *= -1;
}
self._stopped = false;
} else {
self.y = stopY;
// After stopping, move in a fast horizontal zigzag
if (!self._stopped) {
self._stopped = true;
self._moveTimer = 0;
self._baseX = self.x;
self._zigzagDir = Math.random() < 0.5 ? 1 : -1;
}
self._moveTimer++;
// Fast zigzag left/right, bounce at screen edges
self.x += self._zigzagDir * 12;
if (self.x < self.width / 2) {
self.x = self.width / 2;
self._zigzagDir *= -1;
} else if (self.x > 2048 - self.width / 2) {
self.x = 2048 - self.width / 2;
self._zigzagDir *= -1;
}
}
if (self.shootCooldown > 0) self.shootCooldown--;
if (self.shootCooldown === 0 && Math.random() < 0.02) {
self.shoot();
self.shootCooldown = self.shootRate;
}
self.lastY = self.y;
};
self.shoot = function () {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + self.height / 2 + 10;
bullet.speedY = 24; // Faster bullets
enemyBullets.push(bullet);
game.addChild(bullet);
LK.getSound('enemyShoot').play();
};
self.takeDamage = function (amount) {
self.hp -= amount;
LK.getSound('enemyHit').play();
LK.effects.flashObject(self, 0xffffff, 200);
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Laser: Piercing straight beam
var Laser = Container.expand(function () {
var self = Container.call(this);
// Determine laser width based on ship.laserSize, default to 30, max 300
var laserWidth = 30;
if (typeof ship !== "undefined" && typeof ship.laserSize !== "undefined") {
laserWidth = Math.min(300, ship.laserSize);
}
var gfx = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.0
});
gfx.width = laserWidth;
gfx.height = 400;
gfx.tint = 0x00ffff;
self.width = gfx.width;
self.height = gfx.height;
self.speedY = -40;
self.lifetime = 60;
self.update = function () {
self.y += self.speedY;
self.lifetime--;
if (self.lifetime <= 0) {
self.destroyed = true;
}
};
return self;
});
// Player Bullet
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var gfx = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Determine bullet size based on ship.bulletSize
var sizeMultiplier = 1;
if (typeof ship !== "undefined" && typeof ship.bulletSize !== "undefined") {
sizeMultiplier = ship.bulletSize;
}
gfx.width = gfx.width * sizeMultiplier;
gfx.height = gfx.height * sizeMultiplier;
self.width = gfx.width;
self.height = gfx.height;
self.speedY = -28;
self.speedX = 0;
// Set bullet damage based on ship.bulletDamage, default to 1
self.damage = 1;
if (typeof ship !== "undefined" && typeof ship.bulletDamage !== "undefined") {
self.damage = ship.bulletDamage;
}
self.update = function () {
self.y += self.speedY;
self.x += self.speedX;
};
return self;
});
// Player Ship
var Ship = Container.expand(function () {
var self = Container.call(this);
// Add left wing
var leftWing = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
leftWing.width = 60;
leftWing.height = 120;
leftWing.x = -70;
leftWing.y = 20;
leftWing.rotation = -0.35;
leftWing.alpha = 0.7;
// Add right wing
var rightWing = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
rightWing.width = 60;
rightWing.height = 120;
rightWing.x = 70;
rightWing.y = 20;
rightWing.rotation = 0.35;
rightWing.alpha = 0.7;
// Main ship body
var shipGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shipGfx.width;
self.height = shipGfx.height;
self.fireCooldown = 0;
self.fireRate = 30; // ticks between shots
self.bulletSpeed = -28;
self.bulletCount = 1;
self.bulletSpread = 0;
self.bulletSize = 1; // Default bullet size multiplier
self.bulletDamage = 1; // Default bullet damage
self.doubleShot = false;
self.tripleShot = false;
self.laser = false;
self.hp = 100;
self.maxHp = 100;
self.invulnTicks = 0;
self.update = function () {
if (self.fireCooldown > 0) self.fireCooldown--;
if (self.invulnTicks > 0) {
self.invulnTicks--;
shipGfx.alpha = LK.ticks % 8 < 4 ? 0.4 : 1;
leftWing.alpha = shipGfx.alpha * 0.7;
rightWing.alpha = shipGfx.alpha * 0.7;
} else {
shipGfx.alpha = 1;
leftWing.alpha = 0.7;
rightWing.alpha = 0.7;
}
};
self.shoot = function () {
if (self.fireCooldown > 0) return;
self.fireCooldown = self.fireRate;
self._buzzSawFiredThisShot = false;
self._bombFiredThisShot = false;
var shots = [];
var spread = self.bulletSpread;
var count = self.bulletCount;
if (self.tripleShot) {
count = 3;
spread = 0.25;
} else if (self.doubleShot) {
count = 2;
spread = 0.18;
}
// Laser upgrade
if (self.laser) {
var laser = new Laser();
laser.x = self.x;
laser.y = self.y - self.height / 2 - 10;
playerBullets.push(laser);
game.addChild(laser);
shots.push(laser);
}
// Bomb upgrade
if (self.bomb && !self._bombFiredThisShot) {
var bomb = new Bomb();
bomb.x = self.x;
bomb.y = self.y - self.height / 2 - 10;
bomb.owner = self;
// Add a subtle yellow pulsing glow to indicate it's powerful
for (var i = 0; i < bomb.children.length; i++) {
if (bomb.children[i].width === 400 && bomb.children[i].tint === 0xffff00) {
var blastRadiusGfx = bomb.children[i];
blastRadiusGfx.alpha = 0.1;
// Pulse animation
tween(blastRadiusGfx, {
alpha: 0.25
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
tween(blastRadiusGfx, {
alpha: 0.1
}, {
duration: 400,
easing: tween.linear
});
}
});
break;
}
}
playerBullets.push(bomb);
game.addChild(bomb);
shots.push(bomb);
self._bombFiredThisShot = true;
}
// Normal bullets
for (var i = 0; i < count; i++) {
var angle = (i - (count - 1) / 2) * spread;
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y - self.height / 2 - 10;
bullet.speedY = self.bulletSpeed * Math.cos(angle);
bullet.speedX = self.bulletSpeed * Math.sin(angle);
playerBullets.push(bullet);
game.addChild(bullet);
shots.push(bullet);
}
LK.getSound('shoot').play();
return shots;
};
self.takeDamage = function (amount) {
if (self.invulnTicks > 0) return;
self.hp -= amount;
self.invulnTicks = 60;
LK.effects.flashObject(self, 0xff0000, 400);
if (self.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
return self;
});
// TankEnemy: Slow but tough enemy
var TankEnemy = Container.expand(function () {
var self = Container.call(this);
// Add left wing
var leftWing = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
leftWing.width = 60;
leftWing.height = 140;
leftWing.x = -90;
leftWing.y = 20;
leftWing.rotation = -0.25;
leftWing.alpha = 0.7;
// Add right wing
var rightWing = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
rightWing.width = 60;
rightWing.height = 140;
rightWing.x = 90;
rightWing.y = 20;
rightWing.rotation = 0.25;
rightWing.alpha = 0.7;
var gfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
gfx.tint = 0x888888; // Gray color for tank enemy
// Make it larger
gfx.width = gfx.width * 1.5;
gfx.height = gfx.height * 1.5;
self.width = gfx.width;
self.height = gfx.height;
self.scaleX = 1;
self.scaleY = 1;
self.alpha = 1;
self.speedY = 3; // Slower than regular enemy
self.hp = 4; // More HP than regular enemy
self.maxHp = 4;
self.shootCooldown = 0;
self.shootRate = 80 + Math.floor(Math.random() * 40); // Shoots more often
self.value = 3; // Worth more points than regular enemy
self.update = function () {
// Store lastY for transition detection
if (self.lastY === undefined) self.lastY = self.y;
var stopY = 2732 / 2.1; // Stops at a different position
if (self.y < stopY) {
self.y += self.speedY;
self._stopped = false;
} else {
self.y = stopY;
// After stopping, move in a slow, heavy "lurch" pattern (pause, then move a bit, then pause)
if (!self._stopped) {
self._stopped = true;
self._moveTimer = 0;
self._baseX = self.x;
self._lurchDir = Math.random() < 0.5 ? 1 : -1;
}
self._moveTimer++;
// Every 90 frames, lurch left or right by 60px, then pause
if (self._moveTimer % 120 === 0) {
self._lurchDir *= -1;
}
if (self._moveTimer % 120 < 30) {
// Lurch for 30 frames
self.x = self._baseX + self._lurchDir * 60;
} else {
// Pause for 90 frames
self.x = self._baseX;
}
}
if (self.shootCooldown > 0) self.shootCooldown--;
if (self.shootCooldown === 0 && Math.random() < 0.03) {
self.shoot();
self.shootCooldown = self.shootRate;
}
self.lastY = self.y;
};
self.shoot = function () {
// Tank enemy shoots multiple bullets in a spread
for (var i = -1; i <= 1; i++) {
var bullet = new EnemyBullet();
bullet.x = self.x + i * 20;
bullet.y = self.y + self.height / 2 + 10;
enemyBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('enemyShoot').play();
};
self.takeDamage = function (amount) {
self.hp -= amount;
LK.getSound('enemyHit').play();
LK.effects.flashObject(self, 0xffffff, 200);
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Upgrade Card
var UpgradeCard = Container.expand(function () {
var self = Container.call(this);
var highlight = self.attachAsset('upgradeHighlight', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.visible = false;
var card = self.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5
});
self.text = new Text2('', {
size: 70,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
self.text.anchor.set(0.5, 0.5);
self.addChild(self.text);
self.setText = function (t) {
self.text.setText(t);
};
self.setHighlighted = function (v) {
highlight.visible = v;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
// Ship (player)
// Enemy
// Bullet (player)
// Enemy bullet
// Upgrade card
// Upgrade highlight
// Sound effects
// Music
var ship;
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var wave = 1;
var enemiesToSpawn = 0;
var enemiesDefeated = 0;
var enemiesPerWave = 8;
var waveInProgress = false;
var upgradePending = false;
var upgradeOptions = [];
var upgradeCards = [];
var dragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var score = 0;
var scoreTxt;
var hpTxt;
var waveTxt;
var upgradeGroup;
var lastTouch = {
x: 0,
y: 0
};
// Set up background color
game.setBackgroundColor(0x0a0a18);
// --- Starfield background ---
var starCount = 120;
var stars = [];
for (var i = 0; i < starCount; i++) {
var star = new Container();
// Use a white ellipse for stars, randomize size for variety
var size = 2 + Math.random() * 3;
var gfx = star.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: size,
height: size,
color: 0xffffff
});
star.x = Math.random() * 2048;
star.y = Math.random() * 2732;
star.alpha = 0.4 + Math.random() * 0.6;
star.speed = 0.7 + Math.random() * 1.3; // Parallax: slower for dimmer stars
stars.push(star);
// Add behind everything
game.addChildAt(star, 0);
}
// Animate stars in game.update
var oldGameUpdate = game.update;
game.update = function () {
// Move stars downward, wrap to top
for (var i = 0; i < stars.length; i++) {
var s = stars[i];
s.y += s.speed;
if (s.y > 2732) {
s.y = -5;
s.x = Math.random() * 2048;
}
}
if (typeof oldGameUpdate === "function") oldGameUpdate();
};
// Score display
scoreTxt = new Text2('Score: 0', {
size: 90,
fill: "#fff",
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// HP display
hpTxt = new Text2('HP: 3', {
size: 70,
fill: "#fff",
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
hpTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(hpTxt);
hpTxt.y = 100;
// Wave display
waveTxt = new Text2('Wave: 1', {
size: 70,
fill: "#fff",
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
waveTxt.y = 200;
// Hide UI during menu, show after game start
scoreTxt.visible = false;
hpTxt.visible = false;
waveTxt.visible = false;
// Upgrade group (overlay)
upgradeGroup = new Container();
upgradeGroup.visible = false;
game.addChild(upgradeGroup);
// Initialize player ship
ship = new Ship();
game.addChild(ship);
ship.x = 2048 / 2;
ship.y = 2732 - 180;
// Start first wave
// startWave();
// Handle dragging ship
game.down = function (x, y, obj) {
if (upgradePending) return;
if (pointInShip(x, y)) {
dragging = true;
dragOffsetX = ship.x - x;
dragOffsetY = ship.y - y;
}
lastTouch.x = x;
lastTouch.y = y;
};
game.move = function (x, y, obj) {
if (upgradePending) {
// Handle upgrade card selection
for (var i = 0; i < upgradeCards.length; i++) {
var card = upgradeCards[i];
var bounds = getCardBounds(card);
if (x >= bounds.x && x <= bounds.x + bounds.w && y >= bounds.y && y <= bounds.y + bounds.h) {
for (var j = 0; j < upgradeCards.length; j++) {
upgradeCards[j].setHighlighted(j === i);
}
}
}
lastTouch.x = x;
lastTouch.y = y;
return;
}
if (dragging) {
var nx = x + dragOffsetX;
var ny = y + dragOffsetY;
// Clamp to screen
nx = Math.max(ship.width / 2, Math.min(2048 - ship.width / 2, nx));
ny = Math.max(2732 - 400, Math.min(2732 - ship.height / 2, ny));
ship.x = nx;
ship.y = ny;
}
lastTouch.x = x;
lastTouch.y = y;
};
game.up = function (x, y, obj) {
if (upgradePending) {
// Check if an upgrade card was selected
for (var i = 0; i < upgradeCards.length; i++) {
var card = upgradeCards[i];
var bounds = getCardBounds(card);
if (x >= bounds.x && x <= bounds.x + bounds.w && y >= bounds.y && y <= bounds.y + bounds.h) {
selectUpgrade(i);
break;
}
}
return;
}
dragging = false;
};
// Main game update loop
game.update = function () {
if (upgradePending) return;
// Ship update
ship.update();
// Timers for laser and bomb firing
if (typeof laserTimer === "undefined") {
laserTimer = 0;
}
if (typeof bombTimer === "undefined") {
bombTimer = 0;
}
// Track bomb upgrade count
if (typeof ship.bombCount === "undefined") {
ship.bombCount = ship.bomb ? 1 : 0;
}
if (ship.bomb && ship.bombCount === 0) ship.bombCount = 1;
// Ship auto-fire (normal bullets only)
if (ship.fireCooldown === 0) {
// Temporarily disable laser for normal auto-fire
var hadLaser = ship.laser,
hadBomb = ship.bomb;
var fireLaser = false,
fireBomb = false;
if (ship.laser) {
if (laserTimer <= 0) {
fireLaser = true;
laserTimer = 300;
} else {
ship.laser = false;
}
}
if (ship.bomb) {
if (bombTimer <= 0) {
fireBomb = true;
bombTimer = 300; // 5 seconds at 60fps
} else {
ship.bomb = false;
}
}
ship.shoot();
// Restore laser/bomb flags for next time
ship.laser = hadLaser;
ship.bomb = hadBomb;
// If it's time, fire laser
if (fireLaser) {
var oldLaser = ship.laser;
ship.laser = true;
ship.shoot();
ship.laser = oldLaser;
}
if (fireBomb) {
var oldBomb = ship.bomb;
ship.bomb = true;
// Fire as many bombs as upgrades selected
var bombCount = ship.bombCount || 1;
for (var i = 0; i < bombCount; i++) {
ship.shoot();
}
ship.bomb = oldBomb;
}
}
if (laserTimer > 0) laserTimer--;
if (bombTimer > 0) bombTimer--;
// Player bullets
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
b.update();
// Remove if off screen or destroyed
if (b.y < -b.height || b.y > 2732 + (b.height || 0) || b.x < -200 || b.x > 2048 + 200 || b.destroyed) {
b.destroy();
playerBullets.splice(i, 1);
continue;
}
// Check collision with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var e = enemies[j];
// Laser: deal damage, pierce, don't destroy on hit
if (b instanceof Laser && b.intersects(e)) {
e.takeDamage(2);
if (e.hp <= 0 && !e.destroyed) {
e.destroyed = true;
}
// Don't remove laser, allow piercing
continue;
}
// Bomb: explode on impact, damage all enemies in radius
if (b instanceof Bomb && b.intersects(e)) {
// First handle the enemy that was directly hit
e.takeDamage(5); // Increased direct hit damage
if (e.hp <= 0 && !e.destroyed) {
e.destroyed = true;
}
// Then trigger explosion for area damage
b.explode();
// Get all enemies in blast radius
for (var k = enemies.length - 1; k >= 0; k--) {
if (k === j) continue; // Skip the one we already hit
var target = enemies[k];
var dx = target.x - b.x;
var dy = target.y - b.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= b.blastRadius) {
// Damage falls off with distance but starts higher
var damageMultiplier = 1 - distance / b.blastRadius;
var damage = Math.max(2, Math.floor(4 * damageMultiplier)); // Increased area damage
target.takeDamage(damage);
}
}
playerBullets.splice(i, 1);
break;
}
// Normal bullet
if (b.intersects(e)) {
var dmg = typeof b.damage !== "undefined" ? b.damage : 1;
e.takeDamage(dmg);
b.destroy();
playerBullets.splice(i, 1);
if (e.hp <= 0 && !e.destroyed) {
e.destroyed = true;
}
break;
}
}
}
// Enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
// Remove if off screen
if (e.y > 2732 + e.height) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Remove if destroyed
if (e.destroyed) {
score += e.value;
enemiesDefeated++;
scoreTxt.setText('Score: ' + score);
e.destroy();
enemies.splice(i, 1);
LK.getSound('hit').play();
// Check for wave end
if (enemiesDefeated >= enemiesPerWave) {
setTimeout(showUpgrade, 600);
}
continue;
}
// Check collision with ship
if (e.intersects(ship) && ship.invulnTicks === 0) {
ship.takeDamage(1);
e.destroyed = true;
}
}
// Enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
eb.update();
if (eb.y > 2732 + eb.height) {
eb.destroy();
enemyBullets.splice(i, 1);
continue;
}
if (eb.intersects(ship) && ship.invulnTicks === 0) {
ship.takeDamage(1);
eb.destroy();
enemyBullets.splice(i, 1);
continue;
}
}
// Spawn enemies
if (waveInProgress && enemiesToSpawn > 0 && enemies.length < Math.min(5 + wave, 10)) {
spawnEnemy();
}
// Update HP and wave display
hpTxt.setText('HP: ' + ship.hp);
waveTxt.setText('Wave: ' + wave);
};
// Start a new wave
function startWave() {
waveInProgress = true;
enemiesToSpawn = enemiesPerWave;
enemiesDefeated = 0;
waveTxt.setText('Wave: ' + wave);
}
// Spawn a single enemy
function spawnEnemy() {
// Choose enemy type based on wave and random chance
var rand = Math.random();
var enemyType;
// Wave 1: Only basic enemies
if (wave === 1) {
enemyType = "basic";
}
// Wave 2-3: Introduce fast enemies
else if (wave <= 3) {
if (rand < 0.2) enemyType = "fast";else enemyType = "basic";
}
// Wave 4-5: Introduce big enemies and tank enemies
else if (wave <= 5) {
if (rand < 0.2) enemyType = "big";else if (rand < 0.35) enemyType = "fast";else if (rand < 0.45) enemyType = "tank";else enemyType = "basic";
}
// Wave 6+: More variety with increasing difficulty
else {
if (rand < 0.25) enemyType = "big";else if (rand < 0.45) enemyType = "fast";else if (rand < 0.65) enemyType = "tank";else enemyType = "basic";
}
// Force a big enemy to appear in later waves
if (wave > 4 && enemiesToSpawn === enemiesPerWave - 1 && enemyType !== "big") {
enemyType = "big";
}
var e;
// Create the selected enemy type
if (enemyType === "big") {
e = new BigEnemy();
e.x = 200 + Math.random() * (2048 - 400);
e.y = -e.height / 2 - 10;
// Scale up HP and value with wave
e.hp = 6 + Math.floor(wave * 1.5);
e.maxHp = e.hp;
e.speedY = 4 + Math.floor(wave / 6);
e.shootRate = Math.max(40, 90 - wave * 2 + Math.floor(Math.random() * 30));
e.value = 5 + Math.floor(wave * 1.2);
} else if (enemyType === "fast") {
e = new FastEnemy();
e.x = 150 + Math.random() * (2048 - 300);
e.y = -e.height / 2 - 10;
// Scale up with wave
e.hp = Math.max(1, Math.floor(wave / 3));
e.maxHp = e.hp;
e.speedY = 10 + Math.floor(wave / 4);
e.shootRate = Math.max(100, 120 - wave * 3 + Math.floor(Math.random() * 40));
e.value = 2 + Math.floor(wave / 3);
} else if (enemyType === "tank") {
e = new TankEnemy();
e.x = 250 + Math.random() * (2048 - 500);
e.y = -e.height / 2 - 10;
// Scale up with wave
e.hp = 4 + Math.floor(wave * 1.2);
e.maxHp = e.hp;
e.speedY = 3 + Math.floor(wave / 8);
e.shootRate = Math.max(50, 80 - wave * 2 + Math.floor(Math.random() * 30));
e.value = 3 + Math.floor(wave / 2);
} else {
e = new Enemy();
e.x = 180 + Math.random() * (2048 - 360);
e.y = -e.height / 2 - 10;
// Increase difficulty per wave
e.hp = 1 + Math.floor(wave / 2);
e.maxHp = e.hp;
e.speedY = 6 + Math.floor(wave / 3);
e.shootRate = Math.max(60, 120 - wave * 4 + Math.floor(Math.random() * 40));
e.value = 1 + Math.floor(wave / 2);
}
enemies.push(e);
game.addChild(e);
enemiesToSpawn--;
}
// Show upgrade selection
function showUpgrade() {
if (upgradePending) return;
upgradePending = true;
waveInProgress = false;
upgradeGroup.visible = true;
// Generate 3 upgrade options
upgradeOptions = getUpgradeOptions();
// Remove old cards
for (var i = 0; i < upgradeCards.length; i++) {
upgradeCards[i].destroy();
}
upgradeCards = [];
// Layout cards in 3 rows for better visibility
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var cardSpacingY = 380;
var cardSpacingX = 0; // All cards centered horizontally
for (var i = 0; i < 3; i++) {
var card = new UpgradeCard();
card.x = centerX + cardSpacingX;
card.y = centerY + (i - 1) * cardSpacingY;
card.setText(upgradeOptions[i].label);
card.setHighlighted(false);
// Color special upgrades yellow
if (upgradeOptions[i].special) {
// Find the card background and set its tint to yellow
for (var j = 0; j < card.children.length; j++) {
if (card.children[j] && card.children[j].tint !== undefined) {
card.children[j].tint = 0xffe066; // yellow
}
}
// Also set text color to dark for contrast
if (card.text && card.text.style) {
card.text.style.fill = "#664400";
card.text.dirty = true;
}
}
upgradeGroup.addChild(card);
upgradeCards.push(card);
}
}
// Select an upgrade
function selectUpgrade(idx) {
if (!upgradePending) return;
var upg = upgradeOptions[idx];
applyUpgrade(upg);
LK.getSound('upgrade').play();
// Hide upgrade UI
for (var i = 0; i < upgradeCards.length; i++) {
upgradeCards[i].destroy();
}
upgradeCards = [];
upgradeGroup.visible = false;
upgradePending = false;
// Next wave
wave++;
enemiesPerWave = Math.min(20, 8 + Math.floor(wave * 1.5));
startWave();
}
// Generate upgrade options
function getUpgradeOptions() {
var pool = [{
label: "+1 Max HP",
apply: function apply() {
ship.maxHp += 1;
ship.hp = ship.maxHp;
}
}, {
label: "+2 Max HP",
special: true,
apply: function apply() {
ship.maxHp += 2;
ship.hp = ship.maxHp;
}
}, {
label: "+1 Bullet per shot",
apply: function apply() {
ship.bulletCount = Math.min(5, ship.bulletCount + 1);
}
}, {
label: "+2 Bullets per shot",
special: true,
apply: function apply() {
ship.bulletCount = Math.min(5, ship.bulletCount + 2);
}
}, {
label: "Faster Fire Rate",
apply: function apply() {
ship.fireRate = Math.max(8, ship.fireRate - 4);
}
}, {
label: "Much Faster Fire Rate",
special: true,
apply: function apply() {
ship.fireRate = Math.max(8, ship.fireRate - 8);
}
}, {
label: "Wider Spread",
apply: function apply() {
ship.bulletSpread = Math.min(0.5, ship.bulletSpread + 0.08);
}
}, {
label: "Much Wider Spread",
special: true,
apply: function apply() {
ship.bulletSpread = Math.min(0.5, ship.bulletSpread + 0.16);
}
}, {
label: "Double Shot",
apply: function apply() {
ship.doubleShot = true;
}
}, {
label: "Triple Shot",
apply: function apply() {
ship.tripleShot = true;
}
}, {
label: "Laser (Piercing Beam)",
apply: function apply() {
ship.laser = true;
if (typeof ship.laserSize === "undefined") {
ship.laserSize = 30;
ship.laserUpgradeCount = 1;
} else {
ship.laserUpgradeCount++;
// Each upgrade increases width by 60, up to 300
ship.laserSize = Math.min(300, 30 + (ship.laserUpgradeCount - 1) * 60);
}
}
}, {
label: "Laser x2 (Piercing Beam)",
special: true,
apply: function apply() {
ship.laser = true;
if (typeof ship.laserUpgradeCount === "undefined") {
ship.laserUpgradeCount = 2;
ship.laserSize = Math.min(300, 30 + (ship.laserUpgradeCount - 1) * 60);
} else {
ship.laserUpgradeCount += 2;
ship.laserSize = Math.min(300, 30 + (ship.laserUpgradeCount - 1) * 60);
}
}
}, {
label: "Bomb (Area Explosion)",
apply: function apply() {
ship.bomb = true;
if (typeof ship.bombCount === "undefined") {
ship.bombCount = 1;
} else {
ship.bombCount++;
}
}
}, {
label: "Bomb x2 (Area Explosion)",
special: true,
apply: function apply() {
ship.bomb = true;
if (typeof ship.bombCount === "undefined") {
ship.bombCount = 2;
} else {
ship.bombCount += 2;
}
}
}, {
label: "Big Bullets",
apply: function apply() {
if (typeof ship.bulletSize === "undefined") {
ship.bulletSize = 1.5; // First upgrade: 50% bigger
} else {
ship.bulletSize += 0.5; // Each upgrade makes bullets bigger
}
}
}, {
label: "Huge Bullets",
special: true,
apply: function apply() {
if (typeof ship.bulletSize === "undefined") {
ship.bulletSize = 2.0;
} else {
ship.bulletSize += 1.0;
}
}
}, {
label: "Big Bullets",
apply: function apply() {
if (typeof ship.bulletSize === "undefined") {
ship.bulletSize = 1.5; // First upgrade: 50% bigger
} else {
ship.bulletSize += 0.5; // Each upgrade makes bullets bigger
}
}
}, {
label: "Bullet Damage Up",
apply: function apply() {
if (typeof ship.bulletDamage === "undefined") {
ship.bulletDamage = 2;
} else {
ship.bulletDamage += 1;
}
}
}, {
label: "Bullet Damage Up x2",
special: true,
apply: function apply() {
if (typeof ship.bulletDamage === "undefined") {
ship.bulletDamage = 3;
} else {
ship.bulletDamage += 2;
}
}
}];
// Remove upgrades already owned
if (ship.doubleShot) pool = pool.filter(function (u) {
return u.label !== "Double Shot";
});
if (ship.tripleShot) pool = pool.filter(function (u) {
return u.label !== "Triple Shot";
});
// Buzz Saw and Laser upgrades are always available for selection
// Pick 3 random upgrades, but guarantee at least one special (yellow) if possible
var specials = pool.filter(function (u) {
return u.special;
});
var normals = pool.filter(function (u) {
return !u.special;
});
var options = [];
var used = {};
// 30% chance to include a special, or always if none chosen yet and available
if (specials.length > 0 && Math.random() < 0.3) {
var idx = Math.floor(Math.random() * specials.length);
options.push(specials[idx]);
used[specials[idx].label] = true;
}
while (options.length < 3) {
var poolToUse = Math.random() < 0.3 && specials.length > 0 ? specials : normals;
var idx = Math.floor(Math.random() * poolToUse.length);
var upg = poolToUse[idx];
if (!used[upg.label]) {
options.push(upg);
used[upg.label] = true;
}
}
return options;
}
// Apply upgrade
function applyUpgrade(upg) {
upg.apply();
}
// Utility: check if point is in ship
function pointInShip(x, y) {
return x >= ship.x - ship.width / 2 && x <= ship.x + ship.width / 2 && y >= ship.y - ship.height / 2 && y <= ship.y + ship.height / 2;
}
// Utility: get card bounds
function getCardBounds(card) {
return {
x: card.x - 310,
y: card.y - 160,
w: 620,
h: 320
};
}
// Utility: setTimeout using LK
function setTimeout(fn, ms) {
return LK.setTimeout(fn, ms);
}
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// --- Ship Selection Menu ---
// --- Game Title and Instructions ---
var titleTxt = new Text2("GALACTIC DEFENSE", {
size: 120,
fill: "#fff",
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
titleTxt.anchor.set(0.5, 0);
titleTxt.x = 2048 / 2;
titleTxt.y = 80;
game.addChild(titleTxt);
var instrTxt = new Text2("Choose your ship and tap Start!\nDrag your ship to dodge and shoot.\nSurvive waves, collect upgrades.", {
size: 60,
fill: "#fff",
font: "'GillSans',Arial,'Arial Black',Tahoma,sans-serif"
});
instrTxt.anchor.set(0.5, 0);
instrTxt.x = 2048 / 2;
instrTxt.y = 240;
game.addChild(instrTxt);
// Define available ships
var shipTypes = [{
name: "Balanced",
color: 0x66ccff,
hp: 100,
fireRate: 30,
bulletCount: 1,
bulletSpread: 0,
bulletSize: 1,
bulletDamage: 1,
desc: "Balanced all-rounder"
}, {
name: "Tank",
color: 0x44bb44,
hp: 160,
fireRate: 44,
bulletCount: 1,
bulletSpread: 0,
bulletSize: 1.2,
bulletDamage: 2,
desc: "High HP, slow fire, strong bullets"
}, {
name: "Rapid",
color: 0xffaa00,
hp: 70,
fireRate: 14,
bulletCount: 1,
bulletSpread: 0,
bulletSize: 0.8,
bulletDamage: 1,
desc: "Low HP, very fast fire"
}, {
name: "Spread",
color: 0xdd33cc,
hp: 90,
fireRate: 32,
bulletCount: 3,
bulletSpread: 0.22,
bulletSize: 0.9,
bulletDamage: 1,
desc: "Triple shot, moderate stats"
}];
var selectedShipType = 0;
// Ship selection group
var shipSelectGroup = new Container();
game.addChild(shipSelectGroup);
// Create ship cards
var shipCards = [];
var cardSpacing = 500;
var centerX = 2048 / 2;
var centerY = 2732 / 2 - 200;
for (var i = 0; i < shipTypes.length; i++) {
var card = new Container();
var cardGfx = card.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5
});
cardGfx.width = 540;
cardGfx.height = 670;
cardGfx.tint = shipTypes[i].color;
// Ship name
var nameTxt = new Text2(shipTypes[i].name, {
size: 90,
fill: "#fff",
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
nameTxt.anchor.set(0.5, 0);
nameTxt.y = -270;
card.addChild(nameTxt);
// Ship preview
var preview = card.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
preview.width = 180;
preview.height = 180;
preview.tint = shipTypes[i].color;
preview.y = -100;
// Ship stats
var statsTxt = new Text2("HP: " + shipTypes[i].hp + "\nFire: " + (60 / shipTypes[i].fireRate).toFixed(1) + "/s" + "\nBullets: " + shipTypes[i].bulletCount + "\nDamage: " + shipTypes[i].bulletDamage, {
size: 48,
fill: "#fff",
font: "'GillSans',Arial,'Arial Black',Tahoma,sans-serif"
});
statsTxt.anchor.set(0.5, 0);
statsTxt.y = 100;
card.addChild(statsTxt);
// Description
var descTxt = new Text2(shipTypes[i].desc, {
size: 52,
fill: "#fff",
font: "'GillSans',Arial,'Arial Black',Tahoma,sans-serif"
});
descTxt.anchor.set(0.5, 0);
descTxt.y = 300;
card.addChild(descTxt);
// Arrange in rows of 2
var row = Math.floor(i / 2);
var col = i % 2;
var rowSpacing = 800;
var colSpacing = 700;
// Center the two columns
var totalCols = 2;
var xOffset = (col - (totalCols - 1) / 2) * colSpacing;
var yOffset = row * rowSpacing;
card.x = centerX + xOffset;
card.y = centerY + yOffset;
card.cardIndex = i;
shipSelectGroup.addChild(card);
shipCards.push(card);
}
// Glow effect for selected card
var selectGlow = new Container();
var glowGfx = selectGlow.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
glowGfx.width = 320;
glowGfx.height = 320;
glowGfx.tint = 0xffff66;
glowGfx.alpha = 0.45;
selectGlow.visible = true;
shipSelectGroup.addChild(selectGlow);
// Animate the glow (pulsing)
function pulseGlow() {
tween(glowGfx, {
alpha: 0.15
}, {
duration: 700,
easing: tween.linear,
onFinish: function onFinish() {
tween(glowGfx, {
alpha: 0.45
}, {
duration: 700,
easing: tween.linear,
onFinish: pulseGlow
});
}
});
}
pulseGlow();
function updateShipHighlight() {
selectGlow.x = shipCards[selectedShipType].x;
selectGlow.y = shipCards[selectedShipType].y - 100; // Center behind ship preview
}
updateShipHighlight();
// Ship card touch selection
shipSelectGroup.down = function (x, y, obj) {
for (var i = 0; i < shipCards.length; i++) {
var card = shipCards[i];
var bx = card.x - 210,
by = card.y - 260,
bw = 420,
bh = 520;
if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) {
selectedShipType = i;
updateShipHighlight();
break;
}
}
};
// Ship card swipe (left/right)
shipSelectGroup.move = function (x, y, obj) {
// Optional: could implement swipe, but for now tap to select
};
// --- Start Button ---
// --- Polished Start Button ---
var startButton = new Container();
// Shadow effect (soft drop shadow)
var shadowGfx = startButton.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5
});
shadowGfx.width = 420;
shadowGfx.height = 220;
shadowGfx.tint = 0x222222;
shadowGfx.alpha = 0.28;
shadowGfx.y = 16;
// Main button with rounded look (simulate with slightly larger highlight behind)
var buttonHighlight = startButton.attachAsset('upgradeHighlight', {
anchorX: 0.5,
anchorY: 0.5
});
buttonHighlight.width = 440;
buttonHighlight.height = 240;
buttonHighlight.tint = 0x66ff99;
buttonHighlight.alpha = 0.18;
// Main button
var buttonGfx = startButton.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5
});
buttonGfx.width = 400;
buttonGfx.height = 200;
buttonGfx.tint = 0x00e676; // Modern green
// Animated pulse for highlight
function pulseButtonHighlight() {
tween(buttonHighlight, {
alpha: 0.32
}, {
duration: 700,
easing: tween.linear,
onFinish: function onFinish() {
tween(buttonHighlight, {
alpha: 0.18
}, {
duration: 700,
easing: tween.linear,
onFinish: pulseButtonHighlight
});
}
});
}
pulseButtonHighlight();
// Button text
var buttonText = new Text2('Start', {
size: 100,
fill: "#fff",
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma,sans-serif"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 0;
startButton.addChild(buttonText);
// Subtle shine effect (simulated with a semi-transparent white ellipse)
var shine = startButton.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
shine.width = 320;
shine.height = 60;
shine.tint = 0xffffff;
shine.alpha = 0.13;
shine.y = -40;
// Position the start button higher above the ship cards
startButton.x = 2048 / 2;
startButton.y = 2732 / 2 - 600;
game.addChild(startButton);
// Animated press feedback
startButton.down = function (x, y, obj) {
// Animate press: shrink and darken
tween(buttonGfx, {
scaleX: 0.96,
scaleY: 0.96,
tint: 0x00b050
}, {
duration: 80,
easing: tween.linear
});
tween(buttonHighlight, {
scaleX: 0.98,
scaleY: 0.98,
alpha: 0.32
}, {
duration: 80,
easing: tween.linear
});
tween(shadowGfx, {
alpha: 0.38
}, {
duration: 80,
easing: tween.linear
});
// After short delay, restore and trigger start
LK.setTimeout(function () {
tween(buttonGfx, {
scaleX: 1,
scaleY: 1,
tint: 0x00e676
}, {
duration: 120,
easing: tween.linear
});
tween(buttonHighlight, {
scaleX: 1,
scaleY: 1,
alpha: 0.18
}, {
duration: 120,
easing: tween.linear
});
tween(shadowGfx, {
alpha: 0.28
}, {
duration: 120,
easing: tween.linear
});
// Hide menu UI
startButton.visible = false;
shipSelectGroup.visible = false;
if (typeof titleTxt !== "undefined") titleTxt.visible = false;
if (typeof instrTxt !== "undefined") instrTxt.visible = false;
// Show game UI
scoreTxt.visible = true;
hpTxt.visible = true;
waveTxt.visible = true;
// Initialize game state
wave = 1;
enemiesToSpawn = 0;
enemiesDefeated = 0;
enemiesPerWave = 8;
waveInProgress = false;
upgradePending = false;
upgradeOptions = [];
upgradeCards = [];
dragging = false;
dragOffsetX = 0;
dragOffsetY = 0;
score = 0;
scoreTxt.setText('Score: 0');
// Set up ship with selected type
var t = shipTypes[selectedShipType];
// Remove old ship if present
if (ship && typeof ship.destroy === "function") ship.destroy();
ship = new Ship();
game.addChild(ship);
ship.x = 2048 / 2;
ship.y = 2732 - 180;
ship.hp = t.hp;
ship.maxHp = t.hp;
ship.fireRate = t.fireRate;
ship.bulletCount = t.bulletCount;
ship.bulletSpread = t.bulletSpread;
ship.bulletSize = t.bulletSize;
ship.bulletDamage = t.bulletDamage;
// Tint ship body and wings
for (var i = 0; i < ship.children.length; i++) {
if (ship.children[i] && ship.children[i].tint !== undefined) {
ship.children[i].tint = t.color;
}
}
hpTxt.setText('HP: ' + ship.hp);
waveTxt.setText('Wave: ' + wave);
// Start first wave
startWave();
}, 120);
};
// Add event listener for the start button
startButton.down = function (x, y, obj) {
// Hide menu UI
startButton.visible = false;
shipSelectGroup.visible = false;
if (typeof titleTxt !== "undefined") titleTxt.visible = false;
if (typeof instrTxt !== "undefined") instrTxt.visible = false;
// Show game UI
scoreTxt.visible = true;
hpTxt.visible = true;
waveTxt.visible = true;
// Initialize game state
wave = 1;
enemiesToSpawn = 0;
enemiesDefeated = 0;
enemiesPerWave = 8;
waveInProgress = false;
upgradePending = false;
upgradeOptions = [];
upgradeCards = [];
dragging = false;
dragOffsetX = 0;
dragOffsetY = 0;
score = 0;
scoreTxt.setText('Score: 0');
// Set up ship with selected type
var t = shipTypes[selectedShipType];
// Remove old ship if present
if (ship && typeof ship.destroy === "function") ship.destroy();
ship = new Ship();
game.addChild(ship);
ship.x = 2048 / 2;
ship.y = 2732 - 180;
ship.hp = t.hp;
ship.maxHp = t.hp;
ship.fireRate = t.fireRate;
ship.bulletCount = t.bulletCount;
ship.bulletSpread = t.bulletSpread;
ship.bulletSize = t.bulletSize;
ship.bulletDamage = t.bulletDamage;
// Tint ship body and wings
for (var i = 0; i < ship.children.length; i++) {
if (ship.children[i] && ship.children[i].tint !== undefined) {
ship.children[i].tint = t.color;
}
}
hpTxt.setText('HP: ' + ship.hp);
waveTxt.setText('Wave: ' + wave);
// Start first wave
startWave();
};