/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
lastWave: 0
});
/****
* Classes
****/
var Castle = Container.expand(function () {
var self = Container.call(this);
var castleGraphic = self.attachAsset('castle', {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.health = 10;
self.maxHealth = 10;
// Health bar
self.healthBar = new Container();
self.healthBar.y = -castleGraphic.height / 2 - 20;
self.healthBackground = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 15,
tint: 0x444444
});
self.healthFill = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 15,
tint: 0x00FF00
});
self.healthBar.addChild(self.healthBackground);
self.healthBar.addChild(self.healthFill);
self.healthBar.x = -100;
self.addChild(self.healthBar);
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
if (self.health <= 0) {
LK.showGameOver();
} else {
LK.getSound('castle-hit').play();
// Flash the castle red when damaged
LK.effects.flashObject(castleGraphic, 0xFF0000, 500);
}
};
self.updateHealthBar = function () {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthFill.width = 200 * healthPercent;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.health = 100;
self.maxHealth = 100;
self.speed = 2;
self.damage = 1;
self.currentPathIndex = 0;
self.reward = 10;
self.type = 'normal';
// Health bar
self.healthBar = new Container();
self.healthBar.y = -enemyGraphic.height / 2 - 10;
self.healthBackground = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 100,
height: 10,
tint: 0x444444
});
self.healthFill = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 100,
height: 10,
tint: 0x00FF00
});
self.healthBar.addChild(self.healthBackground);
self.healthBar.addChild(self.healthFill);
self.healthBar.x = -35;
self.addChild(self.healthBar);
self.update = function () {
if (self.currentPathIndex >= pathTiles.length) {
// Reached the castle
self.reachedCastle();
return;
}
var targetTile = pathTiles[self.currentPathIndex];
var dx = targetTile.x - self.x;
var dy = targetTile.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
// Reached this tile, move to next tile
self.currentPathIndex++;
} else {
// Move towards the current tile
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
// Rotate enemy to face movement direction
self.rotation = Math.atan2(moveY, moveX);
}
};
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
LK.getSound('enemy-hit').play();
if (self.health <= 0) {
// Enemy died
LK.getSound('enemy-death').play();
gold += self.reward;
goldText.setText("Gold: " + gold);
self.destroy();
enemies.splice(enemies.indexOf(self), 1);
// Update score
LK.setScore(LK.getScore() + 10);
scoreText.setText("Score: " + LK.getScore());
} else {
// Flash the enemy red when hit
LK.effects.flashObject(enemyGraphic, 0xFF0000, 200);
}
};
self.updateHealthBar = function () {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthFill.width = 70 * healthPercent;
};
self.reachedCastle = function () {
castle.takeDamage(self.damage);
self.destroy();
enemies.splice(enemies.indexOf(self), 1);
};
return self;
});
var PathTile = Container.expand(function () {
var self = Container.call(this);
var pathGraphic = self.attachAsset('gray-square', {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.tileIndex = 0;
self.nextTile = null;
self.canPlaceTower = true;
return self;
});
var Projectile = Container.expand(function (type, source, target) {
var self = Container.call(this);
// Set properties based on type
var assetId;
var speed;
switch (type) {
case 'arrow':
assetId = 'arrow';
speed = 10;
break;
case 'cannonball':
assetId = 'cannonball';
speed = 7;
break;
case 'magic':
assetId = 'magic';
speed = 8;
break;
default:
assetId = 'arrow';
speed = 10;
}
var projectileGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.source = source;
self.target = target;
self.damage = source.damage;
self.speed = speed;
self.type = type;
self.hasSplashDamage = false;
self.splashRadius = 0;
self.hasSlowEffect = false;
self.slowAmount = 0;
self.slowDuration = 0;
// Set initial position to tower
self.x = source.x;
self.y = source.y;
self.update = function () {
if (!self.target || !self.target.parent) {
// Target is dead, remove projectile
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
return;
}
// Move towards target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Rotate projectile to face target
self.rotation = Math.atan2(dy, dx);
if (distance < 20) {
// Hit target
self.hit();
} else {
// Move towards target
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.hit = function () {
if (self.hasSplashDamage) {
// Deal splash damage to nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.target.x;
var dy = enemy.y - self.target.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.splashRadius) {
// Calculate damage falloff based on distance
var damageMultiplier = 1 - distance / self.splashRadius;
var damage = Math.floor(self.damage * damageMultiplier);
enemy.takeDamage(damage);
}
}
} else {
// Deal direct damage to target
self.target.takeDamage(self.damage);
// Apply slow effect if applicable
if (self.hasSlowEffect && self.target.speed) {
var originalSpeed = self.target.speed;
self.target.speed *= 1 - self.slowAmount;
// Reset speed after duration
LK.setTimeout(function () {
if (self.target && self.target.parent) {
self.target.speed = originalSpeed;
}
}, self.slowDuration * (1000 / 60)); // Convert frames to milliseconds
// Visual indication of slow effect
tween(self.target, {
alpha: 0.7
}, {
duration: 200,
onFinish: function onFinish() {
if (self.target && self.target.parent) {
tween(self.target, {
alpha: 1
}, {
duration: 200
});
}
}
});
}
}
// Remove projectile
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
// Common tower properties
self.range = 250;
self.damage = 25;
self.attackSpeed = 1; // Attacks per second
self.attackCooldown = 0;
self.cost = 100;
self.level = 1;
self.upgradeCost = 50;
self.type = 'basic';
// Range indicator (initially invisible)
self.rangeIndicator = LK.getAsset('tower-range', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
visible: false
});
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.addChild(self.rangeIndicator);
self.findTarget = function () {
var nearestEnemy = null;
var nearestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestEnemy = enemy;
nearestDistance = distance;
}
}
return nearestEnemy;
};
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else {
var target = self.findTarget();
if (target) {
self.attack(target);
self.attackCooldown = Math.floor(60 / self.attackSpeed); // 60 is FPS
}
}
};
self.attack = function (target) {
// To be overridden by specific tower types
};
self.showRange = function () {
self.rangeIndicator.visible = true;
};
self.hideRange = function () {
self.rangeIndicator.visible = false;
};
self.down = function (x, y, obj) {
// Show range and tower info when clicked
self.showRange();
// Set as selected tower
if (selectedTower && selectedTower !== self) {
selectedTower.hideRange();
}
selectedTower = self;
// Show upgrade button if applicable
if (gold >= self.upgradeCost) {
upgradeButton.visible = true;
upgradeText.setText("Upgrade: " + self.upgradeCost + "g");
} else {
upgradeButton.visible = false;
}
// Show tower stats
var towerInfo = "Level: " + self.level + "\n";
towerInfo += "Damage: " + self.damage + "\n";
towerInfo += "Range: " + self.range + "\n";
towerInfo += "Speed: " + self.attackSpeed.toFixed(1);
towerInfoText.setText(towerInfo);
towerInfoText.visible = true;
// Position the upgrade UI near the tower
var gamePosition = game.toLocal({
x: self.x,
y: self.y
});
upgradeUI.x = Math.min(Math.max(gamePosition.x, 300), 2048 - 300);
upgradeUI.y = Math.min(Math.max(gamePosition.y + 150, 200), 2732 - 200);
upgradeUI.visible = true;
};
self.upgrade = function () {
if (gold >= self.upgradeCost) {
gold -= self.upgradeCost;
goldText.setText("Gold: " + gold);
// Increase tower stats
self.level++;
self.damage = Math.floor(self.damage * 1.3);
self.range = Math.floor(self.range * 1.1);
self.attackSpeed *= 1.1;
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
// Update upgrade cost
self.upgradeCost = Math.floor(self.upgradeCost * 1.5);
// Update UI
upgradeText.setText("Upgrade: " + self.upgradeCost + "g");
// Update tower info
var towerInfo = "Level: " + self.level + "\n";
towerInfo += "Damage: " + self.damage + "\n";
towerInfo += "Range: " + self.range + "\n";
towerInfo += "Speed: " + self.attackSpeed.toFixed(1);
towerInfoText.setText(towerInfo);
// Show upgrade button only if player has enough gold
if (gold < self.upgradeCost) {
upgradeButton.visible = false;
}
LK.getSound('build').play();
}
};
return self;
});
var WizardTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphic = self.attachAsset('wizard-tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Wizard tower specific properties
self.range = 250;
self.damage = 15;
self.attackSpeed = 1.2;
self.cost = 200;
self.slowEffect = 0.5; // Slow enemies to 50% speed
self.slowDuration = 120; // 2 seconds (60 frames per second)
self.type = 'wizard';
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.attack = function (target) {
// Create a magic projectile that slows enemies
var magic = new Projectile('magic', self, target);
magic.hasSlowEffect = true;
magic.slowAmount = self.slowEffect;
magic.slowDuration = self.slowDuration;
projectiles.push(magic);
game.addChild(magic);
};
return self;
});
var CannonTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphic = self.attachAsset('cannon-tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Cannon tower specific properties
self.range = 200;
self.damage = 50;
self.attackSpeed = 0.8;
self.cost = 150;
self.splashRadius = 100;
self.type = 'cannon';
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.attack = function (target) {
// Create a cannonball with splash damage
var cannonball = new Projectile('cannonball', self, target);
cannonball.hasSplashDamage = true;
cannonball.splashRadius = self.splashRadius;
projectiles.push(cannonball);
game.addChild(cannonball);
};
return self;
});
var ArcherTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphic = self.attachAsset('archer-tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Archer tower specific properties
self.range = 300;
self.damage = 20; // Adjusted damage to ensure 5 hits kill an enemy with 100 health
self.attackSpeed = 0.67; // Adjusted to fire every 1.5 seconds (1/1.5)
self.cost = 100;
self.type = 'archer';
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.attack = function (target) {
// Create an arrow
var arrow = new Projectile('arrow', self, target);
projectiles.push(arrow);
game.addChild(arrow);
};
return self;
});
var TowerButton = Container.expand(function (towerType) {
var self = Container.call(this);
// Button background
var buttonGraphic = self.attachAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5
});
// Tower preview
var towerPreview;
var cost;
if (towerType === 'archer') {
towerPreview = self.attachAsset('archer-tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cost = 100;
} else if (towerType === 'cannon') {
towerPreview = self.attachAsset('cannon-tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cost = 150;
} else if (towerType === 'wizard') {
towerPreview = self.attachAsset('wizard-tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cost = 200;
}
// Cost text
var costText = new Text2(cost + "g", {
size: 30,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0.5);
costText.y = 50;
self.addChild(costText);
// Properties
self.towerType = towerType;
self.cost = cost;
self.down = function (x, y, obj) {
// Initiate tower placement
if (gold >= self.cost) {
// Create ghost tower for placement preview
placingTowerType = self.towerType;
createTowerPlacementPreview();
closeMenu();
} else {
// Not enough gold - flash the gold text
tween(goldText, {
alpha: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(goldText, {
alpha: 1
}, {
duration: 200
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x548147 // Green background for grass
});
/****
* Game Code
****/
// Game state variables
var gold = 200;
var wave = 1;
var waveSize = 5;
var waveActive = false;
var enemySpawnInterval = 1000; // ms between enemy spawns
var enemySpawnTimer = 0;
var enemiesSpawned = 0;
var enemiesToSpawn = 0;
var selectedTower = null;
var placingTowerType = null;
var towerPlacementPreview = null;
var pathTiles = [];
var enemies = [];
var towers = [];
var projectiles = [];
var castle;
// UI Elements
var scoreText;
var goldText;
var waveText;
var nextWaveButton;
var towerMenu;
var upgradeUI;
var upgradeButton;
var upgradeText;
var towerInfoText;
// Initialize UI
function initUI() {
// Score text
scoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -50;
scoreText.y = 50;
// Gold text
goldText = new Text2("Gold: " + gold, {
size: 50,
fill: 0xFFD700
});
goldText.anchor.set(1, 0);
LK.gui.topRight.addChild(goldText);
goldText.x = -250;
goldText.y = 50;
// Wave text
waveText = new Text2("Wave: " + wave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
waveText.y = 50;
// Next wave button
nextWaveButton = new Container();
var nextWaveButtonBg = LK.getAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 160,
tint: 0x5588FF
});
nextWaveButton.addChild(nextWaveButtonBg);
var nextWaveButtonText = new Text2("Start Wave", {
size: 40,
fill: 0xFFFFFF
});
nextWaveButtonText.anchor.set(0.5, 0.5);
nextWaveButton.addChild(nextWaveButtonText);
LK.gui.bottomRight.addChild(nextWaveButton);
nextWaveButton.x = -150;
nextWaveButton.y = -100;
nextWaveButton.interactive = true;
nextWaveButton.down = function () {
if (!waveActive) {
startWave();
}
};
// Tower menu button
var towerMenuButton = new Container();
var towerMenuButtonBg = LK.getAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 160,
tint: 0x55AA55
});
towerMenuButton.addChild(towerMenuButtonBg);
var towerMenuButtonText = new Text2("Towers", {
size: 40,
fill: 0xFFFFFF
});
towerMenuButtonText.anchor.set(0.5, 0.5);
towerMenuButtonText.y = -20; // Move text further upwards to prevent overlap
towerMenuButton.addChild(towerMenuButtonText);
LK.gui.bottomRight.addChild(towerMenuButton);
towerMenuButton.x = -150;
towerMenuButton.y = -200;
towerMenuButton.interactive = true;
towerMenuButton.down = function () {
if (towerMenu.visible) {
closeMenu();
} else {
openMenu();
}
};
// Tower selection menu
towerMenu = new Container();
var towerMenuBg = LK.getAsset('tower-menu', {
anchorX: 0.5,
anchorY: 0.5
});
towerMenu.addChild(towerMenuBg);
// Add tower buttons
var archerButton = new TowerButton('archer');
archerButton.x = -150;
towerMenu.addChild(archerButton);
var cannonButton = new TowerButton('cannon');
cannonButton.x = 0;
towerMenu.addChild(cannonButton);
var wizardButton = new TowerButton('wizard');
wizardButton.x = 150;
towerMenu.addChild(wizardButton);
// Add title
var menuTitle = new Text2("Select Tower", {
size: 40,
fill: 0x000000
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.y = -80;
towerMenu.addChild(menuTitle);
LK.gui.center.addChild(towerMenu);
towerMenu.visible = false;
// Tower upgrade UI
upgradeUI = new Container();
var upgradeUIBg = LK.getAsset('tower-menu', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 200
});
upgradeUI.addChild(upgradeUIBg);
towerInfoText = new Text2("", {
size: 30,
fill: 0x000000
});
towerInfoText.anchor.set(0.5, 0);
towerInfoText.y = -80;
upgradeUI.addChild(towerInfoText);
upgradeButton = LK.getAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 60,
tint: 0x55AA55
});
upgradeButton.y = 60;
upgradeUI.addChild(upgradeButton);
upgradeText = new Text2("Upgrade: 50g", {
size: 30,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeText.y = 60;
upgradeUI.addChild(upgradeText);
// Close button for upgrade UI
var closeButton = new Text2("X", {
size: 40,
fill: 0xFF0000
});
closeButton.anchor.set(0.5, 0.5);
closeButton.x = upgradeUIBg.width / 2 - 20;
closeButton.y = -upgradeUIBg.height / 2 + 20;
closeButton.interactive = true;
closeButton.down = function () {
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
upgradeUI.visible = false;
};
upgradeUI.addChild(closeButton);
upgradeButton.interactive = true;
upgradeButton.down = function () {
if (selectedTower) {
selectedTower.upgrade();
}
};
game.addChild(upgradeUI);
upgradeUI.visible = false;
}
function openMenu() {
// Show tower menu
towerMenu.visible = true;
// Hide upgrade UI
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
upgradeUI.visible = false;
}
function closeMenu() {
towerMenu.visible = false;
}
// Create map path
function createMap() {
// Create a zigzag path from start to the castle
var path = [];
var startX = 150;
var startY = 150;
var tileSize = 120;
var mapWidth = Math.floor(2048 / tileSize) - 1;
var mapHeight = Math.floor(2732 / tileSize) - 5;
// Initialize gray square shape for path tiles
// Start point
path.push({
x: startX,
y: startY
});
// Generate zigzag path
var currentX = startX;
var currentY = startY;
var direction = 1; // 1 = right, -1 = left
for (var row = 0; row < 10; row++) {
// Move across
for (var i = 0; i < mapWidth - 1; i++) {
currentX += tileSize * direction;
path.push({
x: currentX,
y: currentY
});
}
// Move down
currentY += tileSize;
path.push({
x: currentX,
y: currentY
});
// Reverse direction
direction *= -1;
}
// Create path tiles
for (var i = 0; i < path.length; i++) {
var tile = new PathTile();
tile.x = path[i].x;
tile.y = path[i].y;
tile.tileIndex = i;
// Connect to next tile
if (i < path.length - 1) {
tile.nextTile = path[i + 1];
}
pathTiles.push(tile);
game.addChild(tile);
}
// Create castle at the end of the path
castle = new Castle();
castle.x = path[path.length - 1].x;
castle.y = path[path.length - 1].y + 150;
game.addChild(castle);
}
// Start a new wave
function startWave() {
waveActive = true;
enemiesToSpawn = waveSize;
enemiesSpawned = 0;
waveText.setText("Wave: " + wave + " - " + enemiesSpawned + "/" + enemiesToSpawn);
// Play wave start sound
LK.getSound('wave-start').play();
}
// Spawn a new enemy
function spawnEnemy() {
var enemy = new Enemy();
// Set enemy position at the start of the path
enemy.x = pathTiles[0].x;
enemy.y = pathTiles[0].y;
// Adjust enemy stats based on wave
enemy.health = 100 + (wave - 1) * 20;
enemy.maxHealth = enemy.health;
enemy.speed = 2 + wave * 0.1;
// Special enemy types for later waves
if (wave > 3 && Math.random() < 0.3) {
// Fast enemy
enemy.health = Math.floor(enemy.health * 0.7);
enemy.maxHealth = enemy.health;
enemy.speed = enemy.speed * 1.5;
enemy.reward = 15;
enemy.type = 'fast';
// Visually distinguish fast enemies
enemy.children[0].tint = 0x00FFFF;
}
if (wave > 5 && Math.random() < 0.2) {
// Tank enemy
enemy.health = Math.floor(enemy.health * 2);
enemy.maxHealth = enemy.health;
enemy.speed = enemy.speed * 0.7;
enemy.reward = 20;
enemy.type = 'tank';
// Visually distinguish tank enemies
enemy.children[0].tint = 0x8800FF;
}
// Update health bar
enemy.updateHealthBar();
// Add to game
enemies.push(enemy);
game.addChild(enemy);
// Update counter
enemiesSpawned++;
waveText.setText("Wave: " + wave + " - " + enemiesSpawned + "/" + enemiesToSpawn);
}
// Create tower placement preview
function createTowerPlacementPreview() {
// Remove existing preview if any
if (towerPlacementPreview) {
towerPlacementPreview.destroy();
towerPlacementPreview = null;
}
// Create appropriate tower preview based on type
if (placingTowerType === 'archer') {
towerPlacementPreview = new ArcherTower();
} else if (placingTowerType === 'cannon') {
towerPlacementPreview = new CannonTower();
} else if (placingTowerType === 'wizard') {
towerPlacementPreview = new WizardTower();
}
// Make preview semi-transparent
towerPlacementPreview.alpha = 0.6;
// Show range
towerPlacementPreview.showRange();
// Add to game
game.addChild(towerPlacementPreview);
}
// Check if position is valid for tower placement
function isValidTowerPosition(x, y) {
// Don't place towers on the path
for (var i = 0; i < pathTiles.length; i++) {
var tile = pathTiles[i];
var dx = tile.x - x;
var dy = tile.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
return false;
}
}
// Don't place towers on the castle
var dx = castle.x - x;
var dy = castle.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
return false;
}
// Don't place towers on other towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = tower.x - x;
var dy = tower.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
return false;
}
}
// Check if in bounds
if (x < 50 || x > 2048 - 50 || y < 50 || y > 2732 - 50) {
return false;
}
return true;
}
// Place tower at position
function placeTower(x, y) {
var tower;
if (placingTowerType === 'archer') {
tower = new ArcherTower();
gold -= tower.cost;
} else if (placingTowerType === 'cannon') {
tower = new CannonTower();
gold -= tower.cost;
} else if (placingTowerType === 'wizard') {
tower = new WizardTower();
gold -= tower.cost;
}
tower.x = x;
tower.y = y;
// Add to game
towers.push(tower);
game.addChild(tower);
// Play build sound
LK.getSound('build').play();
// Update gold text
goldText.setText("Gold: " + gold);
// Clear placement mode
placingTowerType = null;
if (towerPlacementPreview) {
towerPlacementPreview.destroy();
towerPlacementPreview = null;
}
}
// Game down event (mouse/touch down)
game.down = function (x, y, obj) {
// If in tower placement mode
if (placingTowerType && towerPlacementPreview) {
if (isValidTowerPosition(x, y)) {
placeTower(x, y);
}
} else {
// If upgrade UI is visible and clicked outside, hide it
if (upgradeUI.visible) {
var upgradeUIBounds = upgradeUI.getBounds();
if (x < upgradeUI.x - upgradeUIBounds.width / 2 || x > upgradeUI.x + upgradeUIBounds.width / 2 || y < upgradeUI.y - upgradeUIBounds.height / 2 || y > upgradeUI.y + upgradeUIBounds.height / 2) {
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
upgradeUI.visible = false;
}
}
// If tower menu is visible and clicked outside, hide it
if (towerMenu.visible) {
var menuBounds = towerMenu.getBounds();
if (x < towerMenu.x - menuBounds.width / 2 || x > towerMenu.x + menuBounds.width / 2 || y < towerMenu.y - menuBounds.height / 2 || y > towerMenu.y + menuBounds.height / 2) {
closeMenu();
}
}
}
};
// Game move event
game.move = function (x, y, obj) {
// If in tower placement mode, update preview position
if (placingTowerType && towerPlacementPreview) {
towerPlacementPreview.x = x;
towerPlacementPreview.y = y;
// Change color based on placement validity
if (isValidTowerPosition(x, y)) {
towerPlacementPreview.alpha = 0.6;
towerPlacementPreview.rangeIndicator.tint = 0xFFFFFF;
} else {
towerPlacementPreview.alpha = 0.4;
towerPlacementPreview.rangeIndicator.tint = 0xFF0000;
}
}
};
// Game up event
game.up = function (x, y, obj) {
// We handle most interactions in the down event
};
// Main game update
game.update = function () {
// Spawn enemies during active wave
if (waveActive) {
enemySpawnTimer++;
if (enemySpawnTimer >= 60 && enemiesSpawned < enemiesToSpawn) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Check if wave is complete
if (enemiesSpawned >= enemiesToSpawn && enemies.length === 0) {
// Wave completed
waveActive = false;
wave++;
waveSize = Math.floor(waveSize * 1.3);
waveText.setText("Wave: " + wave);
// Award gold for completing wave
var waveReward = 50 + wave * 10;
gold += waveReward;
goldText.setText("Gold: " + gold);
// Display wave completion message
var waveCompleteText = new Text2("Wave Complete!\n+" + waveReward + " gold", {
size: 70,
fill: 0xFFFFFF
});
waveCompleteText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(waveCompleteText);
// Remove message after delay
LK.setTimeout(function () {
LK.gui.center.removeChild(waveCompleteText);
}, 2000);
// Update high score if needed
if (wave > storage.lastWave) {
storage.lastWave = wave;
}
}
}
// Update all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update all towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all projectiles
for (var i = 0; i < projectiles.length; i++) {
projectiles[i].update();
}
};
// Initialize game
function initGame() {
// Reset variables
gold = 200;
wave = 1;
waveSize = 5;
waveActive = false;
enemySpawnTimer = 0;
enemiesSpawned = 0;
enemiesToSpawn = 0;
selectedTower = null;
placingTowerType = null;
// Clear arrays
while (enemies.length > 0) {
enemies[0].destroy();
enemies.splice(0, 1);
}
while (towers.length > 0) {
towers[0].destroy();
towers.splice(0, 1);
}
while (projectiles.length > 0) {
projectiles[0].destroy();
projectiles.splice(0, 1);
}
while (pathTiles.length > 0) {
pathTiles[0].destroy();
pathTiles.splice(0, 1);
}
if (castle) {
castle.destroy();
castle = null;
}
if (towerPlacementPreview) {
towerPlacementPreview.destroy();
towerPlacementPreview = null;
}
// Reset score
LK.setScore(0);
// Set up UI
initUI();
// Create map
createMap();
// Start background music
LK.playMusic('background-music');
}
// Initialize game on startup
initGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
lastWave: 0
});
/****
* Classes
****/
var Castle = Container.expand(function () {
var self = Container.call(this);
var castleGraphic = self.attachAsset('castle', {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.health = 10;
self.maxHealth = 10;
// Health bar
self.healthBar = new Container();
self.healthBar.y = -castleGraphic.height / 2 - 20;
self.healthBackground = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 15,
tint: 0x444444
});
self.healthFill = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 15,
tint: 0x00FF00
});
self.healthBar.addChild(self.healthBackground);
self.healthBar.addChild(self.healthFill);
self.healthBar.x = -100;
self.addChild(self.healthBar);
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
if (self.health <= 0) {
LK.showGameOver();
} else {
LK.getSound('castle-hit').play();
// Flash the castle red when damaged
LK.effects.flashObject(castleGraphic, 0xFF0000, 500);
}
};
self.updateHealthBar = function () {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthFill.width = 200 * healthPercent;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.health = 100;
self.maxHealth = 100;
self.speed = 2;
self.damage = 1;
self.currentPathIndex = 0;
self.reward = 10;
self.type = 'normal';
// Health bar
self.healthBar = new Container();
self.healthBar.y = -enemyGraphic.height / 2 - 10;
self.healthBackground = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 100,
height: 10,
tint: 0x444444
});
self.healthFill = LK.getAsset('map-path', {
anchorX: 0,
anchorY: 0.5,
width: 100,
height: 10,
tint: 0x00FF00
});
self.healthBar.addChild(self.healthBackground);
self.healthBar.addChild(self.healthFill);
self.healthBar.x = -35;
self.addChild(self.healthBar);
self.update = function () {
if (self.currentPathIndex >= pathTiles.length) {
// Reached the castle
self.reachedCastle();
return;
}
var targetTile = pathTiles[self.currentPathIndex];
var dx = targetTile.x - self.x;
var dy = targetTile.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
// Reached this tile, move to next tile
self.currentPathIndex++;
} else {
// Move towards the current tile
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
// Rotate enemy to face movement direction
self.rotation = Math.atan2(moveY, moveX);
}
};
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
LK.getSound('enemy-hit').play();
if (self.health <= 0) {
// Enemy died
LK.getSound('enemy-death').play();
gold += self.reward;
goldText.setText("Gold: " + gold);
self.destroy();
enemies.splice(enemies.indexOf(self), 1);
// Update score
LK.setScore(LK.getScore() + 10);
scoreText.setText("Score: " + LK.getScore());
} else {
// Flash the enemy red when hit
LK.effects.flashObject(enemyGraphic, 0xFF0000, 200);
}
};
self.updateHealthBar = function () {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthFill.width = 70 * healthPercent;
};
self.reachedCastle = function () {
castle.takeDamage(self.damage);
self.destroy();
enemies.splice(enemies.indexOf(self), 1);
};
return self;
});
var PathTile = Container.expand(function () {
var self = Container.call(this);
var pathGraphic = self.attachAsset('gray-square', {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.tileIndex = 0;
self.nextTile = null;
self.canPlaceTower = true;
return self;
});
var Projectile = Container.expand(function (type, source, target) {
var self = Container.call(this);
// Set properties based on type
var assetId;
var speed;
switch (type) {
case 'arrow':
assetId = 'arrow';
speed = 10;
break;
case 'cannonball':
assetId = 'cannonball';
speed = 7;
break;
case 'magic':
assetId = 'magic';
speed = 8;
break;
default:
assetId = 'arrow';
speed = 10;
}
var projectileGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Properties
self.source = source;
self.target = target;
self.damage = source.damage;
self.speed = speed;
self.type = type;
self.hasSplashDamage = false;
self.splashRadius = 0;
self.hasSlowEffect = false;
self.slowAmount = 0;
self.slowDuration = 0;
// Set initial position to tower
self.x = source.x;
self.y = source.y;
self.update = function () {
if (!self.target || !self.target.parent) {
// Target is dead, remove projectile
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
return;
}
// Move towards target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Rotate projectile to face target
self.rotation = Math.atan2(dy, dx);
if (distance < 20) {
// Hit target
self.hit();
} else {
// Move towards target
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.hit = function () {
if (self.hasSplashDamage) {
// Deal splash damage to nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.target.x;
var dy = enemy.y - self.target.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.splashRadius) {
// Calculate damage falloff based on distance
var damageMultiplier = 1 - distance / self.splashRadius;
var damage = Math.floor(self.damage * damageMultiplier);
enemy.takeDamage(damage);
}
}
} else {
// Deal direct damage to target
self.target.takeDamage(self.damage);
// Apply slow effect if applicable
if (self.hasSlowEffect && self.target.speed) {
var originalSpeed = self.target.speed;
self.target.speed *= 1 - self.slowAmount;
// Reset speed after duration
LK.setTimeout(function () {
if (self.target && self.target.parent) {
self.target.speed = originalSpeed;
}
}, self.slowDuration * (1000 / 60)); // Convert frames to milliseconds
// Visual indication of slow effect
tween(self.target, {
alpha: 0.7
}, {
duration: 200,
onFinish: function onFinish() {
if (self.target && self.target.parent) {
tween(self.target, {
alpha: 1
}, {
duration: 200
});
}
}
});
}
}
// Remove projectile
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
// Common tower properties
self.range = 250;
self.damage = 25;
self.attackSpeed = 1; // Attacks per second
self.attackCooldown = 0;
self.cost = 100;
self.level = 1;
self.upgradeCost = 50;
self.type = 'basic';
// Range indicator (initially invisible)
self.rangeIndicator = LK.getAsset('tower-range', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
visible: false
});
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.addChild(self.rangeIndicator);
self.findTarget = function () {
var nearestEnemy = null;
var nearestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestEnemy = enemy;
nearestDistance = distance;
}
}
return nearestEnemy;
};
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else {
var target = self.findTarget();
if (target) {
self.attack(target);
self.attackCooldown = Math.floor(60 / self.attackSpeed); // 60 is FPS
}
}
};
self.attack = function (target) {
// To be overridden by specific tower types
};
self.showRange = function () {
self.rangeIndicator.visible = true;
};
self.hideRange = function () {
self.rangeIndicator.visible = false;
};
self.down = function (x, y, obj) {
// Show range and tower info when clicked
self.showRange();
// Set as selected tower
if (selectedTower && selectedTower !== self) {
selectedTower.hideRange();
}
selectedTower = self;
// Show upgrade button if applicable
if (gold >= self.upgradeCost) {
upgradeButton.visible = true;
upgradeText.setText("Upgrade: " + self.upgradeCost + "g");
} else {
upgradeButton.visible = false;
}
// Show tower stats
var towerInfo = "Level: " + self.level + "\n";
towerInfo += "Damage: " + self.damage + "\n";
towerInfo += "Range: " + self.range + "\n";
towerInfo += "Speed: " + self.attackSpeed.toFixed(1);
towerInfoText.setText(towerInfo);
towerInfoText.visible = true;
// Position the upgrade UI near the tower
var gamePosition = game.toLocal({
x: self.x,
y: self.y
});
upgradeUI.x = Math.min(Math.max(gamePosition.x, 300), 2048 - 300);
upgradeUI.y = Math.min(Math.max(gamePosition.y + 150, 200), 2732 - 200);
upgradeUI.visible = true;
};
self.upgrade = function () {
if (gold >= self.upgradeCost) {
gold -= self.upgradeCost;
goldText.setText("Gold: " + gold);
// Increase tower stats
self.level++;
self.damage = Math.floor(self.damage * 1.3);
self.range = Math.floor(self.range * 1.1);
self.attackSpeed *= 1.1;
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
// Update upgrade cost
self.upgradeCost = Math.floor(self.upgradeCost * 1.5);
// Update UI
upgradeText.setText("Upgrade: " + self.upgradeCost + "g");
// Update tower info
var towerInfo = "Level: " + self.level + "\n";
towerInfo += "Damage: " + self.damage + "\n";
towerInfo += "Range: " + self.range + "\n";
towerInfo += "Speed: " + self.attackSpeed.toFixed(1);
towerInfoText.setText(towerInfo);
// Show upgrade button only if player has enough gold
if (gold < self.upgradeCost) {
upgradeButton.visible = false;
}
LK.getSound('build').play();
}
};
return self;
});
var WizardTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphic = self.attachAsset('wizard-tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Wizard tower specific properties
self.range = 250;
self.damage = 15;
self.attackSpeed = 1.2;
self.cost = 200;
self.slowEffect = 0.5; // Slow enemies to 50% speed
self.slowDuration = 120; // 2 seconds (60 frames per second)
self.type = 'wizard';
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.attack = function (target) {
// Create a magic projectile that slows enemies
var magic = new Projectile('magic', self, target);
magic.hasSlowEffect = true;
magic.slowAmount = self.slowEffect;
magic.slowDuration = self.slowDuration;
projectiles.push(magic);
game.addChild(magic);
};
return self;
});
var CannonTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphic = self.attachAsset('cannon-tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Cannon tower specific properties
self.range = 200;
self.damage = 50;
self.attackSpeed = 0.8;
self.cost = 150;
self.splashRadius = 100;
self.type = 'cannon';
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.attack = function (target) {
// Create a cannonball with splash damage
var cannonball = new Projectile('cannonball', self, target);
cannonball.hasSplashDamage = true;
cannonball.splashRadius = self.splashRadius;
projectiles.push(cannonball);
game.addChild(cannonball);
};
return self;
});
var ArcherTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphic = self.attachAsset('archer-tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Archer tower specific properties
self.range = 300;
self.damage = 20; // Adjusted damage to ensure 5 hits kill an enemy with 100 health
self.attackSpeed = 0.67; // Adjusted to fire every 1.5 seconds (1/1.5)
self.cost = 100;
self.type = 'archer';
// Update range indicator
self.rangeIndicator.width = self.range * 2;
self.rangeIndicator.height = self.range * 2;
self.attack = function (target) {
// Create an arrow
var arrow = new Projectile('arrow', self, target);
projectiles.push(arrow);
game.addChild(arrow);
};
return self;
});
var TowerButton = Container.expand(function (towerType) {
var self = Container.call(this);
// Button background
var buttonGraphic = self.attachAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5
});
// Tower preview
var towerPreview;
var cost;
if (towerType === 'archer') {
towerPreview = self.attachAsset('archer-tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cost = 100;
} else if (towerType === 'cannon') {
towerPreview = self.attachAsset('cannon-tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cost = 150;
} else if (towerType === 'wizard') {
towerPreview = self.attachAsset('wizard-tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cost = 200;
}
// Cost text
var costText = new Text2(cost + "g", {
size: 30,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0.5);
costText.y = 50;
self.addChild(costText);
// Properties
self.towerType = towerType;
self.cost = cost;
self.down = function (x, y, obj) {
// Initiate tower placement
if (gold >= self.cost) {
// Create ghost tower for placement preview
placingTowerType = self.towerType;
createTowerPlacementPreview();
closeMenu();
} else {
// Not enough gold - flash the gold text
tween(goldText, {
alpha: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(goldText, {
alpha: 1
}, {
duration: 200
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x548147 // Green background for grass
});
/****
* Game Code
****/
// Game state variables
var gold = 200;
var wave = 1;
var waveSize = 5;
var waveActive = false;
var enemySpawnInterval = 1000; // ms between enemy spawns
var enemySpawnTimer = 0;
var enemiesSpawned = 0;
var enemiesToSpawn = 0;
var selectedTower = null;
var placingTowerType = null;
var towerPlacementPreview = null;
var pathTiles = [];
var enemies = [];
var towers = [];
var projectiles = [];
var castle;
// UI Elements
var scoreText;
var goldText;
var waveText;
var nextWaveButton;
var towerMenu;
var upgradeUI;
var upgradeButton;
var upgradeText;
var towerInfoText;
// Initialize UI
function initUI() {
// Score text
scoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -50;
scoreText.y = 50;
// Gold text
goldText = new Text2("Gold: " + gold, {
size: 50,
fill: 0xFFD700
});
goldText.anchor.set(1, 0);
LK.gui.topRight.addChild(goldText);
goldText.x = -250;
goldText.y = 50;
// Wave text
waveText = new Text2("Wave: " + wave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
waveText.y = 50;
// Next wave button
nextWaveButton = new Container();
var nextWaveButtonBg = LK.getAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 160,
tint: 0x5588FF
});
nextWaveButton.addChild(nextWaveButtonBg);
var nextWaveButtonText = new Text2("Start Wave", {
size: 40,
fill: 0xFFFFFF
});
nextWaveButtonText.anchor.set(0.5, 0.5);
nextWaveButton.addChild(nextWaveButtonText);
LK.gui.bottomRight.addChild(nextWaveButton);
nextWaveButton.x = -150;
nextWaveButton.y = -100;
nextWaveButton.interactive = true;
nextWaveButton.down = function () {
if (!waveActive) {
startWave();
}
};
// Tower menu button
var towerMenuButton = new Container();
var towerMenuButtonBg = LK.getAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 160,
tint: 0x55AA55
});
towerMenuButton.addChild(towerMenuButtonBg);
var towerMenuButtonText = new Text2("Towers", {
size: 40,
fill: 0xFFFFFF
});
towerMenuButtonText.anchor.set(0.5, 0.5);
towerMenuButtonText.y = -20; // Move text further upwards to prevent overlap
towerMenuButton.addChild(towerMenuButtonText);
LK.gui.bottomRight.addChild(towerMenuButton);
towerMenuButton.x = -150;
towerMenuButton.y = -200;
towerMenuButton.interactive = true;
towerMenuButton.down = function () {
if (towerMenu.visible) {
closeMenu();
} else {
openMenu();
}
};
// Tower selection menu
towerMenu = new Container();
var towerMenuBg = LK.getAsset('tower-menu', {
anchorX: 0.5,
anchorY: 0.5
});
towerMenu.addChild(towerMenuBg);
// Add tower buttons
var archerButton = new TowerButton('archer');
archerButton.x = -150;
towerMenu.addChild(archerButton);
var cannonButton = new TowerButton('cannon');
cannonButton.x = 0;
towerMenu.addChild(cannonButton);
var wizardButton = new TowerButton('wizard');
wizardButton.x = 150;
towerMenu.addChild(wizardButton);
// Add title
var menuTitle = new Text2("Select Tower", {
size: 40,
fill: 0x000000
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.y = -80;
towerMenu.addChild(menuTitle);
LK.gui.center.addChild(towerMenu);
towerMenu.visible = false;
// Tower upgrade UI
upgradeUI = new Container();
var upgradeUIBg = LK.getAsset('tower-menu', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 200
});
upgradeUI.addChild(upgradeUIBg);
towerInfoText = new Text2("", {
size: 30,
fill: 0x000000
});
towerInfoText.anchor.set(0.5, 0);
towerInfoText.y = -80;
upgradeUI.addChild(towerInfoText);
upgradeButton = LK.getAsset('tower-button', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 60,
tint: 0x55AA55
});
upgradeButton.y = 60;
upgradeUI.addChild(upgradeButton);
upgradeText = new Text2("Upgrade: 50g", {
size: 30,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeText.y = 60;
upgradeUI.addChild(upgradeText);
// Close button for upgrade UI
var closeButton = new Text2("X", {
size: 40,
fill: 0xFF0000
});
closeButton.anchor.set(0.5, 0.5);
closeButton.x = upgradeUIBg.width / 2 - 20;
closeButton.y = -upgradeUIBg.height / 2 + 20;
closeButton.interactive = true;
closeButton.down = function () {
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
upgradeUI.visible = false;
};
upgradeUI.addChild(closeButton);
upgradeButton.interactive = true;
upgradeButton.down = function () {
if (selectedTower) {
selectedTower.upgrade();
}
};
game.addChild(upgradeUI);
upgradeUI.visible = false;
}
function openMenu() {
// Show tower menu
towerMenu.visible = true;
// Hide upgrade UI
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
upgradeUI.visible = false;
}
function closeMenu() {
towerMenu.visible = false;
}
// Create map path
function createMap() {
// Create a zigzag path from start to the castle
var path = [];
var startX = 150;
var startY = 150;
var tileSize = 120;
var mapWidth = Math.floor(2048 / tileSize) - 1;
var mapHeight = Math.floor(2732 / tileSize) - 5;
// Initialize gray square shape for path tiles
// Start point
path.push({
x: startX,
y: startY
});
// Generate zigzag path
var currentX = startX;
var currentY = startY;
var direction = 1; // 1 = right, -1 = left
for (var row = 0; row < 10; row++) {
// Move across
for (var i = 0; i < mapWidth - 1; i++) {
currentX += tileSize * direction;
path.push({
x: currentX,
y: currentY
});
}
// Move down
currentY += tileSize;
path.push({
x: currentX,
y: currentY
});
// Reverse direction
direction *= -1;
}
// Create path tiles
for (var i = 0; i < path.length; i++) {
var tile = new PathTile();
tile.x = path[i].x;
tile.y = path[i].y;
tile.tileIndex = i;
// Connect to next tile
if (i < path.length - 1) {
tile.nextTile = path[i + 1];
}
pathTiles.push(tile);
game.addChild(tile);
}
// Create castle at the end of the path
castle = new Castle();
castle.x = path[path.length - 1].x;
castle.y = path[path.length - 1].y + 150;
game.addChild(castle);
}
// Start a new wave
function startWave() {
waveActive = true;
enemiesToSpawn = waveSize;
enemiesSpawned = 0;
waveText.setText("Wave: " + wave + " - " + enemiesSpawned + "/" + enemiesToSpawn);
// Play wave start sound
LK.getSound('wave-start').play();
}
// Spawn a new enemy
function spawnEnemy() {
var enemy = new Enemy();
// Set enemy position at the start of the path
enemy.x = pathTiles[0].x;
enemy.y = pathTiles[0].y;
// Adjust enemy stats based on wave
enemy.health = 100 + (wave - 1) * 20;
enemy.maxHealth = enemy.health;
enemy.speed = 2 + wave * 0.1;
// Special enemy types for later waves
if (wave > 3 && Math.random() < 0.3) {
// Fast enemy
enemy.health = Math.floor(enemy.health * 0.7);
enemy.maxHealth = enemy.health;
enemy.speed = enemy.speed * 1.5;
enemy.reward = 15;
enemy.type = 'fast';
// Visually distinguish fast enemies
enemy.children[0].tint = 0x00FFFF;
}
if (wave > 5 && Math.random() < 0.2) {
// Tank enemy
enemy.health = Math.floor(enemy.health * 2);
enemy.maxHealth = enemy.health;
enemy.speed = enemy.speed * 0.7;
enemy.reward = 20;
enemy.type = 'tank';
// Visually distinguish tank enemies
enemy.children[0].tint = 0x8800FF;
}
// Update health bar
enemy.updateHealthBar();
// Add to game
enemies.push(enemy);
game.addChild(enemy);
// Update counter
enemiesSpawned++;
waveText.setText("Wave: " + wave + " - " + enemiesSpawned + "/" + enemiesToSpawn);
}
// Create tower placement preview
function createTowerPlacementPreview() {
// Remove existing preview if any
if (towerPlacementPreview) {
towerPlacementPreview.destroy();
towerPlacementPreview = null;
}
// Create appropriate tower preview based on type
if (placingTowerType === 'archer') {
towerPlacementPreview = new ArcherTower();
} else if (placingTowerType === 'cannon') {
towerPlacementPreview = new CannonTower();
} else if (placingTowerType === 'wizard') {
towerPlacementPreview = new WizardTower();
}
// Make preview semi-transparent
towerPlacementPreview.alpha = 0.6;
// Show range
towerPlacementPreview.showRange();
// Add to game
game.addChild(towerPlacementPreview);
}
// Check if position is valid for tower placement
function isValidTowerPosition(x, y) {
// Don't place towers on the path
for (var i = 0; i < pathTiles.length; i++) {
var tile = pathTiles[i];
var dx = tile.x - x;
var dy = tile.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
return false;
}
}
// Don't place towers on the castle
var dx = castle.x - x;
var dy = castle.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
return false;
}
// Don't place towers on other towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = tower.x - x;
var dy = tower.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
return false;
}
}
// Check if in bounds
if (x < 50 || x > 2048 - 50 || y < 50 || y > 2732 - 50) {
return false;
}
return true;
}
// Place tower at position
function placeTower(x, y) {
var tower;
if (placingTowerType === 'archer') {
tower = new ArcherTower();
gold -= tower.cost;
} else if (placingTowerType === 'cannon') {
tower = new CannonTower();
gold -= tower.cost;
} else if (placingTowerType === 'wizard') {
tower = new WizardTower();
gold -= tower.cost;
}
tower.x = x;
tower.y = y;
// Add to game
towers.push(tower);
game.addChild(tower);
// Play build sound
LK.getSound('build').play();
// Update gold text
goldText.setText("Gold: " + gold);
// Clear placement mode
placingTowerType = null;
if (towerPlacementPreview) {
towerPlacementPreview.destroy();
towerPlacementPreview = null;
}
}
// Game down event (mouse/touch down)
game.down = function (x, y, obj) {
// If in tower placement mode
if (placingTowerType && towerPlacementPreview) {
if (isValidTowerPosition(x, y)) {
placeTower(x, y);
}
} else {
// If upgrade UI is visible and clicked outside, hide it
if (upgradeUI.visible) {
var upgradeUIBounds = upgradeUI.getBounds();
if (x < upgradeUI.x - upgradeUIBounds.width / 2 || x > upgradeUI.x + upgradeUIBounds.width / 2 || y < upgradeUI.y - upgradeUIBounds.height / 2 || y > upgradeUI.y + upgradeUIBounds.height / 2) {
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
upgradeUI.visible = false;
}
}
// If tower menu is visible and clicked outside, hide it
if (towerMenu.visible) {
var menuBounds = towerMenu.getBounds();
if (x < towerMenu.x - menuBounds.width / 2 || x > towerMenu.x + menuBounds.width / 2 || y < towerMenu.y - menuBounds.height / 2 || y > towerMenu.y + menuBounds.height / 2) {
closeMenu();
}
}
}
};
// Game move event
game.move = function (x, y, obj) {
// If in tower placement mode, update preview position
if (placingTowerType && towerPlacementPreview) {
towerPlacementPreview.x = x;
towerPlacementPreview.y = y;
// Change color based on placement validity
if (isValidTowerPosition(x, y)) {
towerPlacementPreview.alpha = 0.6;
towerPlacementPreview.rangeIndicator.tint = 0xFFFFFF;
} else {
towerPlacementPreview.alpha = 0.4;
towerPlacementPreview.rangeIndicator.tint = 0xFF0000;
}
}
};
// Game up event
game.up = function (x, y, obj) {
// We handle most interactions in the down event
};
// Main game update
game.update = function () {
// Spawn enemies during active wave
if (waveActive) {
enemySpawnTimer++;
if (enemySpawnTimer >= 60 && enemiesSpawned < enemiesToSpawn) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Check if wave is complete
if (enemiesSpawned >= enemiesToSpawn && enemies.length === 0) {
// Wave completed
waveActive = false;
wave++;
waveSize = Math.floor(waveSize * 1.3);
waveText.setText("Wave: " + wave);
// Award gold for completing wave
var waveReward = 50 + wave * 10;
gold += waveReward;
goldText.setText("Gold: " + gold);
// Display wave completion message
var waveCompleteText = new Text2("Wave Complete!\n+" + waveReward + " gold", {
size: 70,
fill: 0xFFFFFF
});
waveCompleteText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(waveCompleteText);
// Remove message after delay
LK.setTimeout(function () {
LK.gui.center.removeChild(waveCompleteText);
}, 2000);
// Update high score if needed
if (wave > storage.lastWave) {
storage.lastWave = wave;
}
}
}
// Update all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update all towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all projectiles
for (var i = 0; i < projectiles.length; i++) {
projectiles[i].update();
}
};
// Initialize game
function initGame() {
// Reset variables
gold = 200;
wave = 1;
waveSize = 5;
waveActive = false;
enemySpawnTimer = 0;
enemiesSpawned = 0;
enemiesToSpawn = 0;
selectedTower = null;
placingTowerType = null;
// Clear arrays
while (enemies.length > 0) {
enemies[0].destroy();
enemies.splice(0, 1);
}
while (towers.length > 0) {
towers[0].destroy();
towers.splice(0, 1);
}
while (projectiles.length > 0) {
projectiles[0].destroy();
projectiles.splice(0, 1);
}
while (pathTiles.length > 0) {
pathTiles[0].destroy();
pathTiles.splice(0, 1);
}
if (castle) {
castle.destroy();
castle = null;
}
if (towerPlacementPreview) {
towerPlacementPreview.destroy();
towerPlacementPreview = null;
}
// Reset score
LK.setScore(0);
// Set up UI
initUI();
// Create map
createMap();
// Start background music
LK.playMusic('background-music');
}
// Initialize game on startup
initGame();