User prompt
Every ten wave have one boss enemy who takes multiple hits to defeat and is bigger and has a special gun.
User prompt
Give the player 100 hp
User prompt
Make the enemies move down into about the middle and then stop moving.
User prompt
Make the enemies stay in place
Code edit (1 edits merged)
Please save this source code
User prompt
Shipwave: Endless Upgrades
Initial prompt
Create a game where you are a ship at the bottom of the screen with basic guns and an infinite amount of waves of enemies come at you and after killing a certain amount of enemies you can get upgrades to give you stronger and faster and better guns.
/**** * 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();
};