/****
* Classes
****/
var Bullet = Container.expand(function (damage) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.damage = damage || 10;
self.lifetime = 60; // Bullets disappear after 1 second
self.update = function () {
// Move bullet
self.x += self.vx;
self.y += self.vy;
// Decrease lifetime
self.lifetime--;
return self.lifetime <= 0;
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 15;
self.scale.set(0.2);
self.update = function () {
self.lifetime--;
self.scale.set(1 - self.lifetime / 20);
self.alpha = self.lifetime / 15;
return self.lifetime <= 0;
};
return self;
});
var ManualTarget = Container.expand(function () {
var self = Container.call(this);
var targetGraphics = self.attachAsset('manualTarget', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 15;
self.alpha = 0.5;
self.update = function () {
self.lifetime--;
self.alpha = self.lifetime / 15;
self.rotation += 0.1;
return self.lifetime <= 0;
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
// Tower base (doesn't rotate)
var base = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5
});
// Tower turret (rotates to aim)
var turret = new Container();
var turretGraphics = turret.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(turret);
// Tower stats
self.health = 100;
self.maxHealth = 100;
self.damage = 10;
self.fireRate = 60; // frames between shots
self.fireTimer = 0;
self.range = 800;
self.upgradeLevel = {
damage: 1,
fireRate: 1,
range: 1,
health: 1
};
// Health bar
var healthBarContainer = new Container();
var healthBarBg = healthBarContainer.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200
});
var healthBarFill = new Container();
var healthBarGraphics = healthBarFill.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
width: 200
});
healthBarContainer.addChild(healthBarFill);
healthBarContainer.y = -150;
self.addChild(healthBarContainer);
// Methods
self.update = function () {
// Decrease fire timer
if (self.fireTimer > 0) {
self.fireTimer--;
}
// Update health bar
healthBarFill.scale.x = self.health / self.maxHealth;
// Find target if none currently
var target = null;
var closestDist = self.range;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
target = zombie;
}
}
// Aim at target
if (target) {
var angle = Math.atan2(target.y - self.y, target.x - self.x);
turret.rotation = angle;
// Fire if ready
if (self.fireTimer <= 0) {
var bullet = new Bullet(self.damage);
bullet.x = self.x;
bullet.y = self.y;
bullet.rotation = angle;
bullet.vx = Math.cos(angle) * 15;
bullet.vy = Math.sin(angle) * 15;
game.addChild(bullet);
bullets.push(bullet);
self.fireTimer = self.fireRate;
LK.getSound('shoot').play();
}
}
};
self.manualShoot = function (x, y) {
var angle = Math.atan2(y - self.y, x - self.x);
turret.rotation = angle;
var bullet = new Bullet(self.damage * 2); // Manual shots do double damage
bullet.x = self.x;
bullet.y = self.y;
bullet.rotation = angle;
bullet.vx = Math.cos(angle) * 15;
bullet.vy = Math.sin(angle) * 15;
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
self.takeDamage = function (amount) {
self.health -= amount;
// Flash the tower when hit
LK.effects.flashObject(self, 0xff0000, 300);
LK.getSound('towerHit').play();
if (self.health <= 0) {
return true; // Tower destroyed
}
return false;
};
self.upgradeDamage = function () {
self.upgradeLevel.damage++;
self.damage += 5;
LK.getSound('upgrade').play();
};
self.upgradeFireRate = function () {
self.upgradeLevel.fireRate++;
self.fireRate = Math.max(10, self.fireRate - 10);
LK.getSound('upgrade').play();
};
self.upgradeRange = function () {
self.upgradeLevel.range++;
self.range += 100;
LK.getSound('upgrade').play();
};
self.upgradeHealth = function () {
self.upgradeLevel.health++;
var oldMaxHealth = self.maxHealth;
self.maxHealth += 50;
self.health += self.maxHealth - oldMaxHealth;
LK.getSound('upgrade').play();
};
return self;
});
var UpgradePanel = Container.expand(function (tower) {
var self = Container.call(this);
// Panel background
var panelBg = self.attachAsset('upgradePanel', {
anchorX: 0,
anchorY: 0
});
// Gold display
var goldContainer = new Container();
var goldIcon = goldContainer.attachAsset('goldIcon', {
anchorX: 0.5,
anchorY: 0.5
});
var goldText = new Text2("0", {
size: 40,
fill: 0xffcc00
});
goldText.x = 60;
goldText.anchor.set(0, 0.5);
goldContainer.addChild(goldText);
goldContainer.x = 100;
goldContainer.y = 100;
self.addChild(goldContainer);
// Tower stats display
var statsContainer = new Container();
var damageText = new Text2("Damage: " + tower.damage, {
size: 30,
fill: 0xffffff
});
damageText.y = 0;
statsContainer.addChild(damageText);
var fireRateText = new Text2("Fire Rate: " + (60 / tower.fireRate).toFixed(1) + "/s", {
size: 30,
fill: 0xffffff
});
fireRateText.y = 40;
statsContainer.addChild(fireRateText);
var rangeText = new Text2("Range: " + tower.range, {
size: 30,
fill: 0xffffff
});
rangeText.y = 80;
statsContainer.addChild(rangeText);
var healthText = new Text2("Health: " + tower.health + "/" + tower.maxHealth, {
size: 30,
fill: 0xffffff
});
healthText.y = 120;
statsContainer.addChild(healthText);
statsContainer.x = 100;
statsContainer.y = 200;
self.addChild(statsContainer);
// Upgrade buttons
var upgradeButtons = [];
var upgradeCosts = {
damage: function damage() {
return tower.upgradeLevel.damage * 50;
},
fireRate: function fireRate() {
return tower.upgradeLevel.fireRate * 75;
},
range: function range() {
return tower.upgradeLevel.range * 40;
},
health: function health() {
return tower.upgradeLevel.health * 60;
}
};
function createUpgradeButton(label, y, upgradeFunc, getCostFunc) {
var button = new Container();
var buttonBg = button.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2(label, {
size: 30,
fill: 0xffffff
});
buttonText.anchor.set(0, 0.5);
buttonText.x = -buttonBg.width / 2 + 20;
button.addChild(buttonText);
var costText = new Text2("Cost: " + getCostFunc() + " Gold", {
size: 25,
fill: 0xffcc00
});
costText.anchor.set(1, 0.5);
costText.x = buttonBg.width / 2 - 20;
button.addChild(costText);
button.x = 250;
button.y = y;
button.upgrade = upgradeFunc;
button.getCost = getCostFunc;
button.updateCost = function () {
costText.setText("Cost: " + getCostFunc() + " Gold");
};
self.addChild(button);
upgradeButtons.push(button);
return button;
}
var damageButton = createUpgradeButton("Upgrade Damage", 400, function () {
tower.upgradeDamage();
}, upgradeCosts.damage);
var fireRateButton = createUpgradeButton("Upgrade Fire Rate", 520, function () {
tower.upgradeFireRate();
}, upgradeCosts.fireRate);
var rangeButton = createUpgradeButton("Upgrade Range", 640, function () {
tower.upgradeRange();
}, upgradeCosts.range);
var healthButton = createUpgradeButton("Upgrade Health", 760, function () {
tower.upgradeHealth();
}, upgradeCosts.health);
self.checkUpgradeClick = function (x, y, gold) {
var localX = x - self.x;
var localY = y - self.y;
for (var i = 0; i < upgradeButtons.length; i++) {
var button = upgradeButtons[i];
var buttonX = button.x;
var buttonY = button.y;
var buttonWidth = 450;
var buttonHeight = 100;
if (localX >= buttonX - buttonWidth / 2 && localX <= buttonX + buttonWidth / 2 && localY >= buttonY - buttonHeight / 2 && localY <= buttonY + buttonHeight / 2) {
var cost = button.getCost();
if (gold >= cost) {
button.upgrade();
self.updateStats();
return cost;
} else {
// Not enough gold
button.alpha = 0.5;
LK.setTimeout(function () {
button.alpha = 1;
}, 300);
return 0;
}
}
}
return 0;
};
self.updateGold = function (gold) {
goldText.setText(gold.toString());
};
self.updateStats = function () {
damageText.setText("Damage: " + tower.damage);
fireRateText.setText("Fire Rate: " + (60 / tower.fireRate).toFixed(1) + "/s");
rangeText.setText("Range: " + tower.range);
healthText.setText("Health: " + tower.health + "/" + tower.maxHealth);
// Update costs
for (var i = 0; i < upgradeButtons.length; i++) {
upgradeButtons[i].updateCost();
}
};
self.update = function () {
self.updateStats();
};
return self;
});
var Zombie = Container.expand(function (waveNumber) {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Stats scale with wave number
var waveMultiplier = 1 + waveNumber * 0.2;
self.health = 20 * waveMultiplier;
self.maxHealth = self.health;
self.speed = 2 + Math.random() * 1;
self.damage = 5 * waveMultiplier;
self.value = 10 + Math.floor(waveNumber * 5);
// Chance to be a special zombie
if (Math.random() < 0.2) {
// Tank zombie - slower but more health
self.health *= 3;
self.maxHealth = self.health;
self.speed *= 0.5;
self.damage *= 2;
self.value *= 3;
zombieGraphics.tint = 0xff0000;
zombieGraphics.width = 80;
zombieGraphics.height = 80;
} else if (Math.random() < 0.3) {
// Fast zombie - faster but less health
self.health *= 0.7;
self.maxHealth = self.health;
self.speed *= 2;
self.value *= 2;
zombieGraphics.tint = 0xffff00;
}
// Health bar
var healthBarContainer = new Container();
var healthBarBg = healthBarContainer.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBarFill = new Container();
var healthBarGraphics = healthBarFill.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarContainer.addChild(healthBarFill);
healthBarContainer.y = -50;
self.addChild(healthBarContainer);
self.update = function () {
// Move towards tower
var dx = CENTER_X - self.x;
var dy = CENTER_Y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 50) {
// Attack tower
if (tower.takeDamage(self.damage)) {
return "gameover";
}
// Destroy self
self.destroy();
zombies.splice(zombies.indexOf(self), 1);
} else {
// Move towards tower
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Update health bar
healthBarFill.scale.x = self.health / self.maxHealth;
// Zombie looks where it's going
self.rotation = Math.atan2(dy, dx);
return "ok";
};
self.takeDamage = function (amount) {
self.health -= amount;
return self.health <= 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
/****
* Game Variables
****/
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var CENTER_X = GAME_WIDTH / 2;
var CENTER_Y = GAME_HEIGHT / 2;
// Game state
var isGameActive = false;
var waitingForNextWave = false;
// Game objects
var tower;
var upgradePanel;
var waveIndicator;
var waveCompleteText;
var bonusText;
var waveTextInterval;
// Collections
var zombies = [];
var bullets = [];
var explosions = [];
var targets = [];
// Game stats
var wave = 0;
var zombiesInWave = 0;
var waveInterval = 60; // Frames between zombie spawns
var zombiesKilled = 0;
var gold = 100;
var manualCooldown = 0;
/****
* Game Functions
****/
function startGame() {
// Create background
var background = new Container();
var backgroundGraphics = background.attachAsset('background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = CENTER_X;
background.y = CENTER_Y;
game.addChild(background);
// Create tower
tower = new Tower();
tower.x = CENTER_X;
tower.y = CENTER_Y;
game.addChild(tower);
// Create upgrade panel
upgradePanel = new UpgradePanel(tower);
upgradePanel.x = GAME_WIDTH - 500;
upgradePanel.y = 0;
game.addChild(upgradePanel);
// Create wave indicator
waveIndicator = new Container();
var waveIndicatorBg = waveIndicator.attachAsset('waveIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
var waveText = new Text2("WAVE 1", {
size: 40,
fill: 0xffffff
});
waveText.anchor.set(0.5);
waveIndicator.addChild(waveText);
waveIndicator.x = GAME_WIDTH - 250;
waveIndicator.y = GAME_HEIGHT - 100;
game.addChild(waveIndicator);
// Start the game
isGameActive = true;
// Start first wave
startWave();
// Start background music
LK.getSound('background').play();
// Update gold display
upgradePanel.updateGold(gold);
}
function startWave() {
// Clean up any previous wave text elements
if (waveCompleteText) {
waveCompleteText.destroy();
waveCompleteText = null;
}
if (bonusText) {
bonusText.destroy();
bonusText = null;
}
if (waveTextInterval) {
LK.clearInterval(waveTextInterval);
waveTextInterval = null;
}
waitingForNextWave = false;
waveIndicator.children[1].setText("WAVE " + wave);
zombiesInWave = 10 + wave * 5;
waveInterval = Math.max(20, 60 - wave * 5); // Spawn zombies faster in later waves
// Show wave start message
var waveStartText = new Text2("WAVE " + wave + " STARTING!", {
size: 80,
fill: 0xff9900
});
waveStartText.x = CENTER_X;
waveStartText.y = CENTER_Y;
waveStartText.anchor.set(0.5);
game.addChild(waveStartText);
LK.getSound('waveStart').play();
// Fade out text
var fadeInterval = LK.setInterval(function () {
waveStartText.alpha -= 0.05;
if (waveStartText.alpha <= 0) {
waveStartText.destroy();
LK.clearInterval(fadeInterval);
}
}, 30);
}
function spawnZombie() {
if (zombiesInWave <= 0) {
return;
}
var zombie = new Zombie(wave);
// Spawn at random edge position
var angle = Math.random() * Math.PI * 2;
var distance = 1200; // Spawn outside visible area
zombie.x = CENTER_X + Math.cos(angle) * distance;
zombie.y = CENTER_Y + Math.sin(angle) * distance;
game.addChild(zombie);
zombies.push(zombie);
zombiesInWave--;
}
function gameOver() {
isGameActive = false;
// Play game over sound
LK.getSound('gameOver').play();
// Display message
var gameOverText = new Text2("GAME OVER!", {
size: 120,
fill: 0xff0000
});
gameOverText.x = CENTER_X;
gameOverText.y = CENTER_Y;
gameOverText.anchor.set(0.5);
game.addChild(gameOverText);
var scoreText = new Text2("Waves Survived: " + wave + "\nZombies Killed: " + zombiesKilled, {
size: 60,
fill: 0xffffff
});
scoreText.x = CENTER_X;
scoreText.y = CENTER_Y + 150;
scoreText.anchor.set(0.5);
game.addChild(scoreText);
// Set final score (waves survived * zombies killed)
LK.setScore(wave * zombiesKilled);
// Show game over screen after delay
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
}
/****
* Event Handlers
****/
game.on('down', function (x, y, obj) {
if (!isGameActive) {
return;
}
var event = obj;
var pos = game.toLocal(event.global);
// Check if clicking on upgrade panel
if (pos.x > GAME_WIDTH - 500) {
var upgradeCost = upgradePanel.checkUpgradeClick(pos.x, pos.y, gold);
if (upgradeCost > 0) {
gold -= upgradeCost;
upgradePanel.updateGold(gold);
}
} else {
// Manual targeting - can only fire once per second
if (manualCooldown <= 0) {
// Create target indicator
var target = new ManualTarget();
target.x = pos.x;
target.y = pos.y;
game.addChild(target);
targets.push(target);
// Fire at clicked location
tower.manualShoot(pos.x, pos.y);
manualCooldown = 60; // 1 second cooldown
}
}
});
/****
* Game Loop
****/
LK.on('tick', function () {
if (!isGameActive) {
return;
}
// Update tower
tower.update();
// Update upgrade panel
upgradePanel.update();
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].update()) {
bullets[i].destroy();
bullets.splice(i, 1);
continue;
}
// Check for zombie hits
for (var j = zombies.length - 1; j >= 0; j--) {
if (bullets[i] && bullets[i].intersects(zombies[j])) {
if (zombies[j].takeDamage(bullets[i].damage)) {
// Zombie killed
var explosion = new Explosion();
explosion.x = zombies[j].x;
explosion.y = zombies[j].y;
game.addChild(explosion);
explosions.push(explosion);
// Add gold
gold += zombies[j].value;
upgradePanel.updateGold(gold);
// Show gold pop-up
var goldPop = new Text2("+" + zombies[j].value, {
size: 30,
fill: 0xffcc00
});
goldPop.x = zombies[j].x;
goldPop.y = zombies[j].y - 50;
goldPop.anchor.set(0.5);
game.addChild(goldPop);
// Fade out gold text
var fadeInterval = LK.setInterval(function () {
goldPop.alpha -= 0.05;
goldPop.y -= 1;
if (goldPop.alpha <= 0) {
goldPop.destroy();
LK.clearInterval(fadeInterval);
}
}, 30);
LK.getSound('zombieDeath').play();
zombies[j].destroy();
zombies.splice(j, 1);
zombiesKilled++;
}
// Remove bullet
bullets[i].destroy();
bullets.splice(i, 1);
break;
}
}
}
// Update zombies
for (var k = zombies.length - 1; k >= 0; k--) {
var result = zombies[k].update();
if (result === "gameover") {
gameOver();
return;
}
}
// Update explosions
for (var l = explosions.length - 1; l >= 0; l--) {
if (explosions[l].update()) {
explosions[l].destroy();
explosions.splice(l, 1);
}
}
// Update targets
for (var m = targets.length - 1; m >= 0; m--) {
if (targets[m].update()) {
targets[m].destroy();
targets.splice(m, 1);
}
}
// Spawn zombies
if (LK.ticks % waveInterval === 0 && zombiesInWave > 0) {
spawnZombie();
}
// Check if wave is complete
if (zombiesInWave <= 0 && zombies.length === 0 && !waitingForNextWave) {
// Set flag to prevent multiple wave completions
waitingForNextWave = true;
// Start next wave after delay
wave++;
LK.setTimeout(function () {
startWave();
}, 3000);
// Grant bonus gold between waves
var bonus = 50 + (wave - 1) * 10;
gold += bonus;
upgradePanel.updateGold(gold);
// Show wave complete message
waveCompleteText = new Text2("WAVE " + (wave - 1) + " COMPLETE!", {
size: 80,
fill: 0x33ff33
});
waveCompleteText.x = CENTER_X;
waveCompleteText.y = CENTER_Y;
waveCompleteText.anchor.set(0.5);
game.addChild(waveCompleteText);
bonusText = new Text2("BONUS: +" + bonus + " GOLD", {
size: 60,
fill: 0xffcc00
});
bonusText.x = CENTER_X;
bonusText.y = CENTER_Y + 100;
bonusText.anchor.set(0.5);
game.addChild(bonusText);
// Fade out text
waveTextInterval = LK.setInterval(function () {
waveCompleteText.alpha -= 0.02;
bonusText.alpha -= 0.02;
if (waveCompleteText.alpha <= 0) {
waveCompleteText.destroy();
bonusText.destroy();
LK.clearInterval(waveTextInterval);
waveCompleteText = null;
bonusText = null;
waveTextInterval = null;
}
}, 30);
}
// Update manual cooldown
if (manualCooldown > 0) {
manualCooldown--;
}
});
// Start the game
startGame(); /****
* Classes
****/
var Bullet = Container.expand(function (damage) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.damage = damage || 10;
self.lifetime = 60; // Bullets disappear after 1 second
self.update = function () {
// Move bullet
self.x += self.vx;
self.y += self.vy;
// Decrease lifetime
self.lifetime--;
return self.lifetime <= 0;
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 15;
self.scale.set(0.2);
self.update = function () {
self.lifetime--;
self.scale.set(1 - self.lifetime / 20);
self.alpha = self.lifetime / 15;
return self.lifetime <= 0;
};
return self;
});
var ManualTarget = Container.expand(function () {
var self = Container.call(this);
var targetGraphics = self.attachAsset('manualTarget', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 15;
self.alpha = 0.5;
self.update = function () {
self.lifetime--;
self.alpha = self.lifetime / 15;
self.rotation += 0.1;
return self.lifetime <= 0;
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
// Tower base (doesn't rotate)
var base = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5
});
// Tower turret (rotates to aim)
var turret = new Container();
var turretGraphics = turret.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(turret);
// Tower stats
self.health = 100;
self.maxHealth = 100;
self.damage = 10;
self.fireRate = 60; // frames between shots
self.fireTimer = 0;
self.range = 800;
self.upgradeLevel = {
damage: 1,
fireRate: 1,
range: 1,
health: 1
};
// Health bar
var healthBarContainer = new Container();
var healthBarBg = healthBarContainer.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200
});
var healthBarFill = new Container();
var healthBarGraphics = healthBarFill.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
width: 200
});
healthBarContainer.addChild(healthBarFill);
healthBarContainer.y = -150;
self.addChild(healthBarContainer);
// Methods
self.update = function () {
// Decrease fire timer
if (self.fireTimer > 0) {
self.fireTimer--;
}
// Update health bar
healthBarFill.scale.x = self.health / self.maxHealth;
// Find target if none currently
var target = null;
var closestDist = self.range;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
target = zombie;
}
}
// Aim at target
if (target) {
var angle = Math.atan2(target.y - self.y, target.x - self.x);
turret.rotation = angle;
// Fire if ready
if (self.fireTimer <= 0) {
var bullet = new Bullet(self.damage);
bullet.x = self.x;
bullet.y = self.y;
bullet.rotation = angle;
bullet.vx = Math.cos(angle) * 15;
bullet.vy = Math.sin(angle) * 15;
game.addChild(bullet);
bullets.push(bullet);
self.fireTimer = self.fireRate;
LK.getSound('shoot').play();
}
}
};
self.manualShoot = function (x, y) {
var angle = Math.atan2(y - self.y, x - self.x);
turret.rotation = angle;
var bullet = new Bullet(self.damage * 2); // Manual shots do double damage
bullet.x = self.x;
bullet.y = self.y;
bullet.rotation = angle;
bullet.vx = Math.cos(angle) * 15;
bullet.vy = Math.sin(angle) * 15;
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
self.takeDamage = function (amount) {
self.health -= amount;
// Flash the tower when hit
LK.effects.flashObject(self, 0xff0000, 300);
LK.getSound('towerHit').play();
if (self.health <= 0) {
return true; // Tower destroyed
}
return false;
};
self.upgradeDamage = function () {
self.upgradeLevel.damage++;
self.damage += 5;
LK.getSound('upgrade').play();
};
self.upgradeFireRate = function () {
self.upgradeLevel.fireRate++;
self.fireRate = Math.max(10, self.fireRate - 10);
LK.getSound('upgrade').play();
};
self.upgradeRange = function () {
self.upgradeLevel.range++;
self.range += 100;
LK.getSound('upgrade').play();
};
self.upgradeHealth = function () {
self.upgradeLevel.health++;
var oldMaxHealth = self.maxHealth;
self.maxHealth += 50;
self.health += self.maxHealth - oldMaxHealth;
LK.getSound('upgrade').play();
};
return self;
});
var UpgradePanel = Container.expand(function (tower) {
var self = Container.call(this);
// Panel background
var panelBg = self.attachAsset('upgradePanel', {
anchorX: 0,
anchorY: 0
});
// Gold display
var goldContainer = new Container();
var goldIcon = goldContainer.attachAsset('goldIcon', {
anchorX: 0.5,
anchorY: 0.5
});
var goldText = new Text2("0", {
size: 40,
fill: 0xffcc00
});
goldText.x = 60;
goldText.anchor.set(0, 0.5);
goldContainer.addChild(goldText);
goldContainer.x = 100;
goldContainer.y = 100;
self.addChild(goldContainer);
// Tower stats display
var statsContainer = new Container();
var damageText = new Text2("Damage: " + tower.damage, {
size: 30,
fill: 0xffffff
});
damageText.y = 0;
statsContainer.addChild(damageText);
var fireRateText = new Text2("Fire Rate: " + (60 / tower.fireRate).toFixed(1) + "/s", {
size: 30,
fill: 0xffffff
});
fireRateText.y = 40;
statsContainer.addChild(fireRateText);
var rangeText = new Text2("Range: " + tower.range, {
size: 30,
fill: 0xffffff
});
rangeText.y = 80;
statsContainer.addChild(rangeText);
var healthText = new Text2("Health: " + tower.health + "/" + tower.maxHealth, {
size: 30,
fill: 0xffffff
});
healthText.y = 120;
statsContainer.addChild(healthText);
statsContainer.x = 100;
statsContainer.y = 200;
self.addChild(statsContainer);
// Upgrade buttons
var upgradeButtons = [];
var upgradeCosts = {
damage: function damage() {
return tower.upgradeLevel.damage * 50;
},
fireRate: function fireRate() {
return tower.upgradeLevel.fireRate * 75;
},
range: function range() {
return tower.upgradeLevel.range * 40;
},
health: function health() {
return tower.upgradeLevel.health * 60;
}
};
function createUpgradeButton(label, y, upgradeFunc, getCostFunc) {
var button = new Container();
var buttonBg = button.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2(label, {
size: 30,
fill: 0xffffff
});
buttonText.anchor.set(0, 0.5);
buttonText.x = -buttonBg.width / 2 + 20;
button.addChild(buttonText);
var costText = new Text2("Cost: " + getCostFunc() + " Gold", {
size: 25,
fill: 0xffcc00
});
costText.anchor.set(1, 0.5);
costText.x = buttonBg.width / 2 - 20;
button.addChild(costText);
button.x = 250;
button.y = y;
button.upgrade = upgradeFunc;
button.getCost = getCostFunc;
button.updateCost = function () {
costText.setText("Cost: " + getCostFunc() + " Gold");
};
self.addChild(button);
upgradeButtons.push(button);
return button;
}
var damageButton = createUpgradeButton("Upgrade Damage", 400, function () {
tower.upgradeDamage();
}, upgradeCosts.damage);
var fireRateButton = createUpgradeButton("Upgrade Fire Rate", 520, function () {
tower.upgradeFireRate();
}, upgradeCosts.fireRate);
var rangeButton = createUpgradeButton("Upgrade Range", 640, function () {
tower.upgradeRange();
}, upgradeCosts.range);
var healthButton = createUpgradeButton("Upgrade Health", 760, function () {
tower.upgradeHealth();
}, upgradeCosts.health);
self.checkUpgradeClick = function (x, y, gold) {
var localX = x - self.x;
var localY = y - self.y;
for (var i = 0; i < upgradeButtons.length; i++) {
var button = upgradeButtons[i];
var buttonX = button.x;
var buttonY = button.y;
var buttonWidth = 450;
var buttonHeight = 100;
if (localX >= buttonX - buttonWidth / 2 && localX <= buttonX + buttonWidth / 2 && localY >= buttonY - buttonHeight / 2 && localY <= buttonY + buttonHeight / 2) {
var cost = button.getCost();
if (gold >= cost) {
button.upgrade();
self.updateStats();
return cost;
} else {
// Not enough gold
button.alpha = 0.5;
LK.setTimeout(function () {
button.alpha = 1;
}, 300);
return 0;
}
}
}
return 0;
};
self.updateGold = function (gold) {
goldText.setText(gold.toString());
};
self.updateStats = function () {
damageText.setText("Damage: " + tower.damage);
fireRateText.setText("Fire Rate: " + (60 / tower.fireRate).toFixed(1) + "/s");
rangeText.setText("Range: " + tower.range);
healthText.setText("Health: " + tower.health + "/" + tower.maxHealth);
// Update costs
for (var i = 0; i < upgradeButtons.length; i++) {
upgradeButtons[i].updateCost();
}
};
self.update = function () {
self.updateStats();
};
return self;
});
var Zombie = Container.expand(function (waveNumber) {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Stats scale with wave number
var waveMultiplier = 1 + waveNumber * 0.2;
self.health = 20 * waveMultiplier;
self.maxHealth = self.health;
self.speed = 2 + Math.random() * 1;
self.damage = 5 * waveMultiplier;
self.value = 10 + Math.floor(waveNumber * 5);
// Chance to be a special zombie
if (Math.random() < 0.2) {
// Tank zombie - slower but more health
self.health *= 3;
self.maxHealth = self.health;
self.speed *= 0.5;
self.damage *= 2;
self.value *= 3;
zombieGraphics.tint = 0xff0000;
zombieGraphics.width = 80;
zombieGraphics.height = 80;
} else if (Math.random() < 0.3) {
// Fast zombie - faster but less health
self.health *= 0.7;
self.maxHealth = self.health;
self.speed *= 2;
self.value *= 2;
zombieGraphics.tint = 0xffff00;
}
// Health bar
var healthBarContainer = new Container();
var healthBarBg = healthBarContainer.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBarFill = new Container();
var healthBarGraphics = healthBarFill.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarContainer.addChild(healthBarFill);
healthBarContainer.y = -50;
self.addChild(healthBarContainer);
self.update = function () {
// Move towards tower
var dx = CENTER_X - self.x;
var dy = CENTER_Y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 50) {
// Attack tower
if (tower.takeDamage(self.damage)) {
return "gameover";
}
// Destroy self
self.destroy();
zombies.splice(zombies.indexOf(self), 1);
} else {
// Move towards tower
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Update health bar
healthBarFill.scale.x = self.health / self.maxHealth;
// Zombie looks where it's going
self.rotation = Math.atan2(dy, dx);
return "ok";
};
self.takeDamage = function (amount) {
self.health -= amount;
return self.health <= 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
/****
* Game Variables
****/
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var CENTER_X = GAME_WIDTH / 2;
var CENTER_Y = GAME_HEIGHT / 2;
// Game state
var isGameActive = false;
var waitingForNextWave = false;
// Game objects
var tower;
var upgradePanel;
var waveIndicator;
var waveCompleteText;
var bonusText;
var waveTextInterval;
// Collections
var zombies = [];
var bullets = [];
var explosions = [];
var targets = [];
// Game stats
var wave = 0;
var zombiesInWave = 0;
var waveInterval = 60; // Frames between zombie spawns
var zombiesKilled = 0;
var gold = 100;
var manualCooldown = 0;
/****
* Game Functions
****/
function startGame() {
// Create background
var background = new Container();
var backgroundGraphics = background.attachAsset('background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = CENTER_X;
background.y = CENTER_Y;
game.addChild(background);
// Create tower
tower = new Tower();
tower.x = CENTER_X;
tower.y = CENTER_Y;
game.addChild(tower);
// Create upgrade panel
upgradePanel = new UpgradePanel(tower);
upgradePanel.x = GAME_WIDTH - 500;
upgradePanel.y = 0;
game.addChild(upgradePanel);
// Create wave indicator
waveIndicator = new Container();
var waveIndicatorBg = waveIndicator.attachAsset('waveIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
var waveText = new Text2("WAVE 1", {
size: 40,
fill: 0xffffff
});
waveText.anchor.set(0.5);
waveIndicator.addChild(waveText);
waveIndicator.x = GAME_WIDTH - 250;
waveIndicator.y = GAME_HEIGHT - 100;
game.addChild(waveIndicator);
// Start the game
isGameActive = true;
// Start first wave
startWave();
// Start background music
LK.getSound('background').play();
// Update gold display
upgradePanel.updateGold(gold);
}
function startWave() {
// Clean up any previous wave text elements
if (waveCompleteText) {
waveCompleteText.destroy();
waveCompleteText = null;
}
if (bonusText) {
bonusText.destroy();
bonusText = null;
}
if (waveTextInterval) {
LK.clearInterval(waveTextInterval);
waveTextInterval = null;
}
waitingForNextWave = false;
waveIndicator.children[1].setText("WAVE " + wave);
zombiesInWave = 10 + wave * 5;
waveInterval = Math.max(20, 60 - wave * 5); // Spawn zombies faster in later waves
// Show wave start message
var waveStartText = new Text2("WAVE " + wave + " STARTING!", {
size: 80,
fill: 0xff9900
});
waveStartText.x = CENTER_X;
waveStartText.y = CENTER_Y;
waveStartText.anchor.set(0.5);
game.addChild(waveStartText);
LK.getSound('waveStart').play();
// Fade out text
var fadeInterval = LK.setInterval(function () {
waveStartText.alpha -= 0.05;
if (waveStartText.alpha <= 0) {
waveStartText.destroy();
LK.clearInterval(fadeInterval);
}
}, 30);
}
function spawnZombie() {
if (zombiesInWave <= 0) {
return;
}
var zombie = new Zombie(wave);
// Spawn at random edge position
var angle = Math.random() * Math.PI * 2;
var distance = 1200; // Spawn outside visible area
zombie.x = CENTER_X + Math.cos(angle) * distance;
zombie.y = CENTER_Y + Math.sin(angle) * distance;
game.addChild(zombie);
zombies.push(zombie);
zombiesInWave--;
}
function gameOver() {
isGameActive = false;
// Play game over sound
LK.getSound('gameOver').play();
// Display message
var gameOverText = new Text2("GAME OVER!", {
size: 120,
fill: 0xff0000
});
gameOverText.x = CENTER_X;
gameOverText.y = CENTER_Y;
gameOverText.anchor.set(0.5);
game.addChild(gameOverText);
var scoreText = new Text2("Waves Survived: " + wave + "\nZombies Killed: " + zombiesKilled, {
size: 60,
fill: 0xffffff
});
scoreText.x = CENTER_X;
scoreText.y = CENTER_Y + 150;
scoreText.anchor.set(0.5);
game.addChild(scoreText);
// Set final score (waves survived * zombies killed)
LK.setScore(wave * zombiesKilled);
// Show game over screen after delay
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
}
/****
* Event Handlers
****/
game.on('down', function (x, y, obj) {
if (!isGameActive) {
return;
}
var event = obj;
var pos = game.toLocal(event.global);
// Check if clicking on upgrade panel
if (pos.x > GAME_WIDTH - 500) {
var upgradeCost = upgradePanel.checkUpgradeClick(pos.x, pos.y, gold);
if (upgradeCost > 0) {
gold -= upgradeCost;
upgradePanel.updateGold(gold);
}
} else {
// Manual targeting - can only fire once per second
if (manualCooldown <= 0) {
// Create target indicator
var target = new ManualTarget();
target.x = pos.x;
target.y = pos.y;
game.addChild(target);
targets.push(target);
// Fire at clicked location
tower.manualShoot(pos.x, pos.y);
manualCooldown = 60; // 1 second cooldown
}
}
});
/****
* Game Loop
****/
LK.on('tick', function () {
if (!isGameActive) {
return;
}
// Update tower
tower.update();
// Update upgrade panel
upgradePanel.update();
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].update()) {
bullets[i].destroy();
bullets.splice(i, 1);
continue;
}
// Check for zombie hits
for (var j = zombies.length - 1; j >= 0; j--) {
if (bullets[i] && bullets[i].intersects(zombies[j])) {
if (zombies[j].takeDamage(bullets[i].damage)) {
// Zombie killed
var explosion = new Explosion();
explosion.x = zombies[j].x;
explosion.y = zombies[j].y;
game.addChild(explosion);
explosions.push(explosion);
// Add gold
gold += zombies[j].value;
upgradePanel.updateGold(gold);
// Show gold pop-up
var goldPop = new Text2("+" + zombies[j].value, {
size: 30,
fill: 0xffcc00
});
goldPop.x = zombies[j].x;
goldPop.y = zombies[j].y - 50;
goldPop.anchor.set(0.5);
game.addChild(goldPop);
// Fade out gold text
var fadeInterval = LK.setInterval(function () {
goldPop.alpha -= 0.05;
goldPop.y -= 1;
if (goldPop.alpha <= 0) {
goldPop.destroy();
LK.clearInterval(fadeInterval);
}
}, 30);
LK.getSound('zombieDeath').play();
zombies[j].destroy();
zombies.splice(j, 1);
zombiesKilled++;
}
// Remove bullet
bullets[i].destroy();
bullets.splice(i, 1);
break;
}
}
}
// Update zombies
for (var k = zombies.length - 1; k >= 0; k--) {
var result = zombies[k].update();
if (result === "gameover") {
gameOver();
return;
}
}
// Update explosions
for (var l = explosions.length - 1; l >= 0; l--) {
if (explosions[l].update()) {
explosions[l].destroy();
explosions.splice(l, 1);
}
}
// Update targets
for (var m = targets.length - 1; m >= 0; m--) {
if (targets[m].update()) {
targets[m].destroy();
targets.splice(m, 1);
}
}
// Spawn zombies
if (LK.ticks % waveInterval === 0 && zombiesInWave > 0) {
spawnZombie();
}
// Check if wave is complete
if (zombiesInWave <= 0 && zombies.length === 0 && !waitingForNextWave) {
// Set flag to prevent multiple wave completions
waitingForNextWave = true;
// Start next wave after delay
wave++;
LK.setTimeout(function () {
startWave();
}, 3000);
// Grant bonus gold between waves
var bonus = 50 + (wave - 1) * 10;
gold += bonus;
upgradePanel.updateGold(gold);
// Show wave complete message
waveCompleteText = new Text2("WAVE " + (wave - 1) + " COMPLETE!", {
size: 80,
fill: 0x33ff33
});
waveCompleteText.x = CENTER_X;
waveCompleteText.y = CENTER_Y;
waveCompleteText.anchor.set(0.5);
game.addChild(waveCompleteText);
bonusText = new Text2("BONUS: +" + bonus + " GOLD", {
size: 60,
fill: 0xffcc00
});
bonusText.x = CENTER_X;
bonusText.y = CENTER_Y + 100;
bonusText.anchor.set(0.5);
game.addChild(bonusText);
// Fade out text
waveTextInterval = LK.setInterval(function () {
waveCompleteText.alpha -= 0.02;
bonusText.alpha -= 0.02;
if (waveCompleteText.alpha <= 0) {
waveCompleteText.destroy();
bonusText.destroy();
LK.clearInterval(waveTextInterval);
waveCompleteText = null;
bonusText = null;
waveTextInterval = null;
}
}, 30);
}
// Update manual cooldown
if (manualCooldown > 0) {
manualCooldown--;
}
});
// Start the game
startGame();