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