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