/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Enemy class var Enemy = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Reduce initial power and scaling of enemies (so a tower can kill in two hits at the beginning) self.maxHp = 4 + waveNumber * 2; self.hp = self.maxHp; self.speed = 3 + 0.35 * waveNumber; self.pathIndex = 0; self.progress = 0; // 0-1 between path points // For hit flash self.flash = function () { tween(sprite, { tint: 0xFFFFFF }, { duration: 60, onFinish: function onFinish() { tween(sprite, { tint: 0xB22222 }, { duration: 120 }); } }); }; // Called every tick self.update = function () { // Move along path if (self.pathIndex < pathPoints.length - 1) { var p0 = pathPoints[self.pathIndex]; var p1 = pathPoints[self.pathIndex + 1]; var dx = p1.x - p0.x; var dy = p1.y - p0.y; var dist = Math.sqrt(dx * dx + dy * dy); var step = self.speed; var move = step / dist; self.progress += move; if (self.progress >= 1) { self.pathIndex += 1; self.progress = 0; if (self.pathIndex >= pathPoints.length - 1) { // Reached end self.x = p1.x; self.y = p1.y; self.reachedEnd = true; return; } } var px = p0.x + dx * self.progress; var py = p0.y + dy * self.progress; self.x = px; self.y = py; } }; return self; }); // Projectile class var Projectile = Container.expand(function (x, y, angle, damage) { var self = Container.call(this); var sprite = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.x = x; self.y = y; self.angle = angle; self.speed = 18; self.damage = damage; self.radius = 15; // Called every tick self.update = function () { self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; }; return self; }); // Tower class var Tower = Container.expand(function () { var self = Container.call(this); // Attach base var base = self.attachAsset('towerBase', { anchorX: 0.5, anchorY: 0.5 }); // Attach cannon var cannon = self.attachAsset('towerCannon', { anchorX: 0.5, anchorY: 0.8, y: -20 }); // Range indicator (hidden by default) var rangeCircle = self.attachAsset('towerRange', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); // Tower stats self.range = 280; // Increased from 200 self.fireRate = 40; // Faster fire rate (was 60) self.cooldown = 0; self.level = 1; self.damage = 2; // Increased from 1 self.upgradeCost = 50; // For upgrade UI self.showRange = function (show) { rangeCircle.alpha = show ? 0.2 : 0; }; // Upgrade tower self.upgrade = function () { if (gold >= 50) { gold -= 50; self.level += 1; self.range += 40; self.damage += 1; self.fireRate = Math.max(30, self.fireRate - 10); self.upgradeCost += 50; updateGoldText(); // Animate upgrade tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); } }; // Find nearest enemy in range self.findTarget = function () { var minDist = self.range; var target = null; for (var i = 0; i < enemies.length; ++i) { var e = enemies[i]; var dx = e.x - self.x; var dy = e.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = e; } } return target; }; // Called every tick self.update = function () { if (self.cooldown > 0) { self.cooldown -= 1; } var target = self.findTarget(); if (target) { // Rotate cannon towards target var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx); cannon.rotation = angle - Math.PI / 2; // Fire if ready if (self.cooldown <= 0) { self.cooldown = self.fireRate; var proj = new Projectile(self.x, self.y, angle, self.damage); projectiles.push(proj); game.addChild(proj); // LK.getSound('shoot').play(); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2e2b1b }); /**** * Game Code ****/ // LK.init.sound('enemyDie', {volume:0.5}); // LK.init.sound('shoot', {volume:0.5}); // Sounds (not used in MVP, but ready for future) // Path // Projectiles // Enemies // For debug/upgrade, alpha set in code // Towers // Path definition (array of points) var pathPoints = [{ x: 200, y: 400 }, { x: 200, y: 1800 }, { x: 1000, y: 1800 }, { x: 1000, y: 600 }, { x: 1800, y: 600 }, { x: 1800, y: 2300 }]; // Gold, wave, etc. var gold = 100; var waveNumber = 1; var enemies = []; var towers = []; var projectiles = []; var lives = 10; var enemiesToSpawn = 0; var spawnTick = 0; var waveInProgress = false; // GUI: Gold display var goldText = new Text2('Gold: 100', { size: 90, fill: 0xFFD700 }); goldText.anchor.set(1, 0); // Top right LK.gui.topRight.addChild(goldText); function updateGoldText() { goldText.setText('Gold: ' + gold); } // GUI: Wave display var waveText = new Text2('Wave: 1', { size: 70, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); function updateWaveText() { waveText.setText('Wave: ' + waveNumber); } // GUI: Lives display var livesText = new Text2('Lives: 10', { size: 70, fill: 0xFF4444 }); livesText.anchor.set(0, 0); LK.gui.top.addChild(livesText); function updateLivesText() { livesText.setText('Lives: ' + lives); } // Draw path tiles for (var i = 0; i < pathPoints.length - 1; ++i) { var p0 = pathPoints[i]; var p1 = pathPoints[i + 1]; var dx = p1.x - p0.x; var dy = p1.y - p0.y; var dist = Math.sqrt(dx * dx + dy * dy); var steps = Math.floor(dist / 100); for (var s = 0; s <= steps; ++s) { var px = p0.x + dx * (s / steps); var py = p0.y + dy * (s / steps); var tile = LK.getAsset('pathTile', { anchorX: 0.5, anchorY: 0.5, x: px, y: py }); game.addChild(tile); } } // Tower placement var placingTower = false; var towerGhost = null; var towerCost = 50; // Placeable area: not on path, not too close to other towers, not in top left 100x100 function canPlaceTower(x, y) { // Not in top left 100x100 if (x < 100 && y < 100) return false; // Not too close to path for (var i = 0; i < pathPoints.length - 1; ++i) { var p0 = pathPoints[i]; var p1 = pathPoints[i + 1]; var dx = p1.x - p0.x; var dy = p1.y - p0.y; var dist = Math.sqrt(dx * dx + dy * dy); var steps = Math.floor(dist / 40); for (var s = 0; s <= steps; ++s) { var px = p0.x + dx * (s / steps); var py = p0.y + dy * (s / steps); var d = Math.sqrt((x - px) * (x - px) + (y - py) * (y - py)); if (d < 100) return false; } } // Not too close to other towers for (var t = 0; t < towers.length; ++t) { var tw = towers[t]; var d = Math.sqrt((x - tw.x) * (x - tw.x) + (y - tw.y) * (y - tw.y)); if (d < 180) return false; } return true; } // Tower upgrade UI var upgradeBtn = new Text2('Upgrade\n(50)', { size: 70, fill: 0x44BB44 }); upgradeBtn.anchor.set(0.5, 1); // Not added to GUI yet; will be added near selected tower var selectedTower = null; // Handle upgrade button upgradeBtn.down = function (x, y, obj) { if (selectedTower) { selectedTower.upgrade(); // Update upgrade button cost upgradeBtn.setText('Upgrade\n(' + selectedTower.upgradeCost + ')'); } }; // Handle game touch events game.down = function (x, y, obj) { // If placing tower if (placingTower) { if (canPlaceTower(x, y) && gold >= towerCost) { var t = new Tower(); t.x = x; t.y = y; towers.push(t); game.addChild(t); gold -= towerCost; updateGoldText(); placingTower = false; if (towerGhost) { towerGhost.destroy(); towerGhost = null; } } return; } // Allow tower placement by clicking anywhere on the roadside (not on path, not in top left 100x100) if (!placingTower && gold >= towerCost) { if (canPlaceTower(x, y)) { var t = new Tower(); t.x = x; t.y = y; towers.push(t); game.addChild(t); gold -= towerCost; updateGoldText(); if (towerGhost) { towerGhost.destroy(); towerGhost = null; } placingTower = false; return; } } // If clicking on a tower, select it or upgrade on double click var found = null; for (var i = 0; i < towers.length; ++i) { var tw = towers[i]; var dx = x - tw.x; var dy = y - tw.y; if (Math.abs(dx) < 60 && Math.abs(dy) < 60) { found = tw; break; } } if (found) { // Double click detection for upgrade if (!found._lastClickTime) found._lastClickTime = 0; var now = Date.now(); if (now - found._lastClickTime < 400) { // Double click detected, upgrade tower found.upgrade(); // Update upgrade button cost if visible if (upgradeBtn.parent && selectedTower === found) { upgradeBtn.setText('Upgrade\n(' + found.upgradeCost + ')'); } found._lastClickTime = 0; return; } else { found._lastClickTime = now; } if (selectedTower && selectedTower !== found) { selectedTower.showRange(false); } selectedTower = found; selectedTower.showRange(true); // Show upgrade button near tower (above) upgradeBtn.setText('Upgrade\n(' + selectedTower.upgradeCost + ')'); LK.gui.bottom.addChild(upgradeBtn); return; } // Deselect tower if clicking elsewhere if (selectedTower) { selectedTower.showRange(false); selectedTower = null; if (upgradeBtn.parent) upgradeBtn.parent.removeChild(upgradeBtn); } }; // Move handler for tower ghost game.move = function (x, y, obj) { if (placingTower && towerGhost) { towerGhost.x = x; towerGhost.y = y; if (canPlaceTower(x, y) && gold >= towerCost) { towerGhost.alpha = 0.5; } else { towerGhost.alpha = 0.2; } } }; // Remove ghost on up game.up = function (x, y, obj) { if (placingTower && towerGhost) { if (towerGhost.parent) { towerGhost.parent.removeChild(towerGhost); } towerGhost = null; placingTower = false; } }; // Start wave button (bottom center) var startBtn = new Text2('Start Wave', { size: 90, fill: 0x44AAFF }); startBtn.anchor.set(0.5, 1); LK.gui.bottom.addChild(startBtn); startBtn.down = function (x, y, obj) { if (!waveInProgress) { startWave(); } }; // Start a new wave function startWave() { waveInProgress = true; enemiesToSpawn = 10 + (waveNumber - 1) * 2; spawnTick = 0; updateWaveText(); } // Main update loop game.update = function () { // Spawn enemies if (waveInProgress && enemiesToSpawn > 0) { if (LK.ticks % 30 === 0) { var e = new Enemy(); var p = pathPoints[0]; e.x = p.x; e.y = p.y; enemies.push(e); game.addChild(e); enemiesToSpawn -= 1; } } // Update enemies for (var i = enemies.length - 1; i >= 0; --i) { var e = enemies[i]; e.update(); // Check if reached end if (e.reachedEnd) { lives -= 1; updateLivesText(); e.destroy(); enemies.splice(i, 1); if (lives <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } continue; } } // Update towers for (var i = 0; i < towers.length; ++i) { towers[i].update(); } // Update projectiles for (var i = projectiles.length - 1; i >= 0; --i) { var p = projectiles[i]; p.update(); // Remove if out of bounds if (p.x < 0 || p.x > 2048 || p.y < 0 || p.y > 2732) { p.destroy(); projectiles.splice(i, 1); continue; } // Check collision with enemies var hit = false; for (var j = enemies.length - 1; j >= 0; --j) { var e = enemies[j]; var dx = p.x - e.x; var dy = p.y - e.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 50) { e.hp -= p.damage; e.flash(); if (e.hp <= 0) { // Enemy dies gold += 10; updateGoldText(); // LK.getSound('enemyDie').play(); e.destroy(); enemies.splice(j, 1); } p.destroy(); projectiles.splice(i, 1); hit = true; break; } } if (hit) continue; } // Check if wave is over if (waveInProgress && enemiesToSpawn === 0 && enemies.length === 0) { waveInProgress = false; waveNumber += 1; updateWaveText(); // Win condition: survive 10 waves if (waveNumber > 10) { LK.effects.flashScreen(0x44ff44, 1000); LK.showYouWin(); return; } // Automatically start next wave after a short delay (e.g. 2 seconds) LK.setTimeout(function () { if (!waveInProgress && lives > 0 && waveNumber <= 10) { startWave(); } }, 2000); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Reduce initial power and scaling of enemies (so a tower can kill in two hits at the beginning)
self.maxHp = 4 + waveNumber * 2;
self.hp = self.maxHp;
self.speed = 3 + 0.35 * waveNumber;
self.pathIndex = 0;
self.progress = 0; // 0-1 between path points
// For hit flash
self.flash = function () {
tween(sprite, {
tint: 0xFFFFFF
}, {
duration: 60,
onFinish: function onFinish() {
tween(sprite, {
tint: 0xB22222
}, {
duration: 120
});
}
});
};
// Called every tick
self.update = function () {
// Move along path
if (self.pathIndex < pathPoints.length - 1) {
var p0 = pathPoints[self.pathIndex];
var p1 = pathPoints[self.pathIndex + 1];
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var step = self.speed;
var move = step / dist;
self.progress += move;
if (self.progress >= 1) {
self.pathIndex += 1;
self.progress = 0;
if (self.pathIndex >= pathPoints.length - 1) {
// Reached end
self.x = p1.x;
self.y = p1.y;
self.reachedEnd = true;
return;
}
}
var px = p0.x + dx * self.progress;
var py = p0.y + dy * self.progress;
self.x = px;
self.y = py;
}
};
return self;
});
// Projectile class
var Projectile = Container.expand(function (x, y, angle, damage) {
var self = Container.call(this);
var sprite = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.angle = angle;
self.speed = 18;
self.damage = damage;
self.radius = 15;
// Called every tick
self.update = function () {
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
};
return self;
});
// Tower class
var Tower = Container.expand(function () {
var self = Container.call(this);
// Attach base
var base = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5
});
// Attach cannon
var cannon = self.attachAsset('towerCannon', {
anchorX: 0.5,
anchorY: 0.8,
y: -20
});
// Range indicator (hidden by default)
var rangeCircle = self.attachAsset('towerRange', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Tower stats
self.range = 280; // Increased from 200
self.fireRate = 40; // Faster fire rate (was 60)
self.cooldown = 0;
self.level = 1;
self.damage = 2; // Increased from 1
self.upgradeCost = 50;
// For upgrade UI
self.showRange = function (show) {
rangeCircle.alpha = show ? 0.2 : 0;
};
// Upgrade tower
self.upgrade = function () {
if (gold >= 50) {
gold -= 50;
self.level += 1;
self.range += 40;
self.damage += 1;
self.fireRate = Math.max(30, self.fireRate - 10);
self.upgradeCost += 50;
updateGoldText();
// Animate upgrade
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
}
};
// Find nearest enemy in range
self.findTarget = function () {
var minDist = self.range;
var target = null;
for (var i = 0; i < enemies.length; ++i) {
var e = enemies[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = e;
}
}
return target;
};
// Called every tick
self.update = function () {
if (self.cooldown > 0) {
self.cooldown -= 1;
}
var target = self.findTarget();
if (target) {
// Rotate cannon towards target
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx);
cannon.rotation = angle - Math.PI / 2;
// Fire if ready
if (self.cooldown <= 0) {
self.cooldown = self.fireRate;
var proj = new Projectile(self.x, self.y, angle, self.damage);
projectiles.push(proj);
game.addChild(proj);
// LK.getSound('shoot').play();
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2e2b1b
});
/****
* Game Code
****/
// LK.init.sound('enemyDie', {volume:0.5});
// LK.init.sound('shoot', {volume:0.5});
// Sounds (not used in MVP, but ready for future)
// Path
// Projectiles
// Enemies
// For debug/upgrade, alpha set in code
// Towers
// Path definition (array of points)
var pathPoints = [{
x: 200,
y: 400
}, {
x: 200,
y: 1800
}, {
x: 1000,
y: 1800
}, {
x: 1000,
y: 600
}, {
x: 1800,
y: 600
}, {
x: 1800,
y: 2300
}];
// Gold, wave, etc.
var gold = 100;
var waveNumber = 1;
var enemies = [];
var towers = [];
var projectiles = [];
var lives = 10;
var enemiesToSpawn = 0;
var spawnTick = 0;
var waveInProgress = false;
// GUI: Gold display
var goldText = new Text2('Gold: 100', {
size: 90,
fill: 0xFFD700
});
goldText.anchor.set(1, 0); // Top right
LK.gui.topRight.addChild(goldText);
function updateGoldText() {
goldText.setText('Gold: ' + gold);
}
// GUI: Wave display
var waveText = new Text2('Wave: 1', {
size: 70,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
function updateWaveText() {
waveText.setText('Wave: ' + waveNumber);
}
// GUI: Lives display
var livesText = new Text2('Lives: 10', {
size: 70,
fill: 0xFF4444
});
livesText.anchor.set(0, 0);
LK.gui.top.addChild(livesText);
function updateLivesText() {
livesText.setText('Lives: ' + lives);
}
// Draw path tiles
for (var i = 0; i < pathPoints.length - 1; ++i) {
var p0 = pathPoints[i];
var p1 = pathPoints[i + 1];
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var steps = Math.floor(dist / 100);
for (var s = 0; s <= steps; ++s) {
var px = p0.x + dx * (s / steps);
var py = p0.y + dy * (s / steps);
var tile = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
game.addChild(tile);
}
}
// Tower placement
var placingTower = false;
var towerGhost = null;
var towerCost = 50;
// Placeable area: not on path, not too close to other towers, not in top left 100x100
function canPlaceTower(x, y) {
// Not in top left 100x100
if (x < 100 && y < 100) return false;
// Not too close to path
for (var i = 0; i < pathPoints.length - 1; ++i) {
var p0 = pathPoints[i];
var p1 = pathPoints[i + 1];
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var steps = Math.floor(dist / 40);
for (var s = 0; s <= steps; ++s) {
var px = p0.x + dx * (s / steps);
var py = p0.y + dy * (s / steps);
var d = Math.sqrt((x - px) * (x - px) + (y - py) * (y - py));
if (d < 100) return false;
}
}
// Not too close to other towers
for (var t = 0; t < towers.length; ++t) {
var tw = towers[t];
var d = Math.sqrt((x - tw.x) * (x - tw.x) + (y - tw.y) * (y - tw.y));
if (d < 180) return false;
}
return true;
}
// Tower upgrade UI
var upgradeBtn = new Text2('Upgrade\n(50)', {
size: 70,
fill: 0x44BB44
});
upgradeBtn.anchor.set(0.5, 1);
// Not added to GUI yet; will be added near selected tower
var selectedTower = null;
// Handle upgrade button
upgradeBtn.down = function (x, y, obj) {
if (selectedTower) {
selectedTower.upgrade();
// Update upgrade button cost
upgradeBtn.setText('Upgrade\n(' + selectedTower.upgradeCost + ')');
}
};
// Handle game touch events
game.down = function (x, y, obj) {
// If placing tower
if (placingTower) {
if (canPlaceTower(x, y) && gold >= towerCost) {
var t = new Tower();
t.x = x;
t.y = y;
towers.push(t);
game.addChild(t);
gold -= towerCost;
updateGoldText();
placingTower = false;
if (towerGhost) {
towerGhost.destroy();
towerGhost = null;
}
}
return;
}
// Allow tower placement by clicking anywhere on the roadside (not on path, not in top left 100x100)
if (!placingTower && gold >= towerCost) {
if (canPlaceTower(x, y)) {
var t = new Tower();
t.x = x;
t.y = y;
towers.push(t);
game.addChild(t);
gold -= towerCost;
updateGoldText();
if (towerGhost) {
towerGhost.destroy();
towerGhost = null;
}
placingTower = false;
return;
}
}
// If clicking on a tower, select it or upgrade on double click
var found = null;
for (var i = 0; i < towers.length; ++i) {
var tw = towers[i];
var dx = x - tw.x;
var dy = y - tw.y;
if (Math.abs(dx) < 60 && Math.abs(dy) < 60) {
found = tw;
break;
}
}
if (found) {
// Double click detection for upgrade
if (!found._lastClickTime) found._lastClickTime = 0;
var now = Date.now();
if (now - found._lastClickTime < 400) {
// Double click detected, upgrade tower
found.upgrade();
// Update upgrade button cost if visible
if (upgradeBtn.parent && selectedTower === found) {
upgradeBtn.setText('Upgrade\n(' + found.upgradeCost + ')');
}
found._lastClickTime = 0;
return;
} else {
found._lastClickTime = now;
}
if (selectedTower && selectedTower !== found) {
selectedTower.showRange(false);
}
selectedTower = found;
selectedTower.showRange(true);
// Show upgrade button near tower (above)
upgradeBtn.setText('Upgrade\n(' + selectedTower.upgradeCost + ')');
LK.gui.bottom.addChild(upgradeBtn);
return;
}
// Deselect tower if clicking elsewhere
if (selectedTower) {
selectedTower.showRange(false);
selectedTower = null;
if (upgradeBtn.parent) upgradeBtn.parent.removeChild(upgradeBtn);
}
};
// Move handler for tower ghost
game.move = function (x, y, obj) {
if (placingTower && towerGhost) {
towerGhost.x = x;
towerGhost.y = y;
if (canPlaceTower(x, y) && gold >= towerCost) {
towerGhost.alpha = 0.5;
} else {
towerGhost.alpha = 0.2;
}
}
};
// Remove ghost on up
game.up = function (x, y, obj) {
if (placingTower && towerGhost) {
if (towerGhost.parent) {
towerGhost.parent.removeChild(towerGhost);
}
towerGhost = null;
placingTower = false;
}
};
// Start wave button (bottom center)
var startBtn = new Text2('Start Wave', {
size: 90,
fill: 0x44AAFF
});
startBtn.anchor.set(0.5, 1);
LK.gui.bottom.addChild(startBtn);
startBtn.down = function (x, y, obj) {
if (!waveInProgress) {
startWave();
}
};
// Start a new wave
function startWave() {
waveInProgress = true;
enemiesToSpawn = 10 + (waveNumber - 1) * 2;
spawnTick = 0;
updateWaveText();
}
// Main update loop
game.update = function () {
// Spawn enemies
if (waveInProgress && enemiesToSpawn > 0) {
if (LK.ticks % 30 === 0) {
var e = new Enemy();
var p = pathPoints[0];
e.x = p.x;
e.y = p.y;
enemies.push(e);
game.addChild(e);
enemiesToSpawn -= 1;
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// Check if reached end
if (e.reachedEnd) {
lives -= 1;
updateLivesText();
e.destroy();
enemies.splice(i, 1);
if (lives <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
continue;
}
}
// Update towers
for (var i = 0; i < towers.length; ++i) {
towers[i].update();
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; --i) {
var p = projectiles[i];
p.update();
// Remove if out of bounds
if (p.x < 0 || p.x > 2048 || p.y < 0 || p.y > 2732) {
p.destroy();
projectiles.splice(i, 1);
continue;
}
// Check collision with enemies
var hit = false;
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
var dx = p.x - e.x;
var dy = p.y - e.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 50) {
e.hp -= p.damage;
e.flash();
if (e.hp <= 0) {
// Enemy dies
gold += 10;
updateGoldText();
// LK.getSound('enemyDie').play();
e.destroy();
enemies.splice(j, 1);
}
p.destroy();
projectiles.splice(i, 1);
hit = true;
break;
}
}
if (hit) continue;
}
// Check if wave is over
if (waveInProgress && enemiesToSpawn === 0 && enemies.length === 0) {
waveInProgress = false;
waveNumber += 1;
updateWaveText();
// Win condition: survive 10 waves
if (waveNumber > 10) {
LK.effects.flashScreen(0x44ff44, 1000);
LK.showYouWin();
return;
}
// Automatically start next wave after a short delay (e.g. 2 seconds)
LK.setTimeout(function () {
if (!waveInProgress && lives > 0 && waveNumber <= 10) {
startWave();
}
}, 2000);
}
};