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