/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basicEnemy';
self.enemyGraphic = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
switch (self.type) {
case 'basicEnemy':
self.maxHealth = 100;
self.speed = 1.5;
self.goldValue = 10;
break;
case 'fastEnemy':
self.maxHealth = 75;
self.speed = 2.5;
self.goldValue = 15;
break;
case 'tankEnemy':
self.maxHealth = 250;
self.speed = 0.8;
self.goldValue = 25;
break;
case 'bossEnemy':
self.maxHealth = 500;
self.speed = 1.0;
self.goldValue = 100;
break;
default:
self.maxHealth = 100;
self.speed = 1.5;
self.goldValue = 10;
}
self.health = self.maxHealth;
self.currentPathIndex = 0;
self.reachedEnd = false;
// Health bar
self.healthBarBackground = new Container();
self.healthBarBackground.width = self.enemyGraphic.width * 1.2;
self.healthBarBackground.height = 10;
self.healthBarBackground.x = -self.healthBarBackground.width / 2;
self.healthBarBackground.y = -self.enemyGraphic.height / 2 - 15;
self.addChild(self.healthBarBackground);
self.healthBar = new Container();
self.healthBar.width = self.healthBarBackground.width;
self.healthBar.height = self.healthBarBackground.height;
self.healthBar.x = self.healthBarBackground.x;
self.healthBar.y = self.healthBarBackground.y;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
var healthPercentage = self.health / self.maxHealth;
self.healthBar.width = self.healthBarBackground.width * healthPercentage;
// Change color based on health percentage
if (healthPercentage > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercentage > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
};
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
// Briefly flash the enemy when taking damage
tween(self.enemyGraphic, {
alpha: 0.5
}, {
duration: 100,
onFinish: function onFinish() {
tween(self.enemyGraphic, {
alpha: 1
}, {
duration: 100
});
}
});
if (self.health <= 0) {
return true; // Enemy is dead
}
return false; // Enemy is still alive
};
self.moveAlongPath = function (path) {
if (self.currentPathIndex >= path.length) {
self.reachedEnd = true;
return true;
}
var targetX = path[self.currentPathIndex].x;
var targetY = path[self.currentPathIndex].y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.currentPathIndex++;
return false;
}
var normalizedX = dx / distance;
var normalizedY = dy / distance;
self.x += normalizedX * self.speed;
self.y += normalizedY * self.speed;
return false;
};
self.updateHealthBar();
return self;
});
var MapTile = Container.expand(function (tileType) {
var self = Container.call(this);
self.tileType = tileType || 'grass';
self.canPlaceTower = self.tileType === 'grass';
var tileGraphic = self.attachAsset(self.tileType, {
anchorX: 0.5,
anchorY: 0.5
});
self.setHighlight = function (highlight) {
if (highlight && self.canPlaceTower) {
tileGraphic.alpha = 0.8;
} else {
tileGraphic.alpha = 1.0;
}
};
return self;
});
var Projectile = Container.expand(function (targetEnemy, damage, speed, type) {
var self = Container.call(this);
self.targetEnemy = targetEnemy;
self.damage = damage || 25;
self.speed = speed || 10;
self.projectileType = type || 'projectile';
var projectileGraphic = self.attachAsset(self.projectileType, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 15) {
// Hit enemy
if (self.targetEnemy.takeDamage(self.damage)) {
// Enemy is dead
gameState.gold += self.targetEnemy.goldValue;
gameState.score += self.targetEnemy.goldValue;
updateGoldText();
updateScoreText();
LK.getSound('enemyDeath').play();
var enemyIndex = gameState.enemies.indexOf(self.targetEnemy);
if (enemyIndex !== -1) {
self.targetEnemy.destroy();
gameState.enemies.splice(enemyIndex, 1);
}
}
self.destroy();
return;
}
// Move toward enemy
var normalizedX = dx / distance;
var normalizedY = dy / distance;
self.x += normalizedX * self.speed;
self.y += normalizedY * self.speed;
// Rotate projectile to face direction of travel
self.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
self.towerType = type || 'baseTower';
self.baseTower = self.attachAsset('baseTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.towerGraphic = self.attachAsset(self.towerType, {
anchorX: 0.5,
anchorY: 0.5,
y: -10
});
// Tower properties based on type
switch (self.towerType) {
case 'cannonTower':
self.damage = 40;
self.range = 200;
self.attackSpeed = 1.5; // Attacks per second
self.cost = 100;
self.projectileSpeed = 8;
self.targetingStrategy = 'first'; // Target first enemy in path
break;
case 'archerTower':
self.damage = 20;
self.range = 300;
self.attackSpeed = 3; // Attacks per second
self.cost = 150;
self.projectileSpeed = 15;
self.targetingStrategy = 'first';
break;
case 'magicTower':
self.damage = 30;
self.range = 250;
self.attackSpeed = 2; // Attacks per second
self.cost = 200;
self.projectileSpeed = 10;
self.targetingStrategy = 'strongest'; // Target enemy with most health
break;
default:
self.damage = 25;
self.range = 200;
self.attackSpeed = 1; // Attacks per second
self.cost = 50;
self.projectileSpeed = 8;
self.targetingStrategy = 'first';
}
self.level = 1;
self.attackCooldown = 0;
self.rangeIndicator = null;
self.selected = false;
// Show range indicator when tower is selected
self.showRange = function (show) {
if (show && !self.rangeIndicator) {
self.rangeIndicator = self.attachAsset('rangeIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: self.range / 300,
// Scale to match tower's range
scaleY: self.range / 300
});
self.rangeIndicator.zIndex = -1;
} else if (!show && self.rangeIndicator) {
self.removeChild(self.rangeIndicator);
self.rangeIndicator = null;
}
};
self.upgrade = function () {
if (self.level >= 3) return false; // Max level reached
var upgradeCost = self.cost * self.level;
if (gameState.gold < upgradeCost) return false; // Not enough gold
gameState.gold -= upgradeCost;
self.level++;
// Upgrade stats
self.damage *= 1.5;
self.range *= 1.2;
self.attackSpeed *= 1.2;
// Update visuals for upgraded tower
self.towerGraphic.scale.set(1 + (self.level - 1) * 0.2);
// Update range indicator if visible
if (self.rangeIndicator) {
self.removeChild(self.rangeIndicator);
self.showRange(true);
}
updateGoldText();
return true;
};
self.findTarget = function (enemies) {
var target = null;
var maxPriority = -1;
var minDistance = Infinity;
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 <= self.range) {
var priority = 0;
switch (self.targetingStrategy) {
case 'first':
priority = enemy.currentPathIndex + (1 - distance / self.range);
break;
case 'strongest':
priority = enemy.health;
break;
case 'closest':
priority = self.range - distance;
break;
}
if (priority > maxPriority || priority === maxPriority && distance < minDistance) {
maxPriority = priority;
minDistance = distance;
target = enemy;
}
}
}
return target;
};
self.attackEnemy = function (enemy) {
if (!enemy) return;
// Create projectile
var projectile = new Projectile(enemy, self.damage, self.projectileSpeed);
projectile.x = self.x;
projectile.y = self.y;
game.addChild(projectile);
gameState.projectiles.push(projectile);
LK.getSound('projectileShot').play();
// Reset cooldown
self.attackCooldown = 60 / self.attackSpeed;
// Tower attack animation
tween(self.towerGraphic, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(self.towerGraphic, {
scaleX: 1 + (self.level - 1) * 0.2,
scaleY: 1 + (self.level - 1) * 0.2
}, {
duration: 100
});
}
});
};
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else {
var target = self.findTarget(gameState.enemies);
if (target) {
self.attackEnemy(target);
}
}
};
self.down = function (x, y, obj) {
// Select this tower, deselect others
selectTower(self);
};
return self;
});
var TowerButton = Container.expand(function (towerType, cost) {
var self = Container.call(this);
self.towerType = towerType;
self.cost = cost;
var button = self.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var towerPreview = self.attachAsset(towerType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var costText = new Text2(cost.toString(), {
size: 24,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0.5);
costText.y = 30;
self.addChild(costText);
self.setEnabled = function (enabled) {
button.alpha = enabled ? 1.0 : 0.5;
towerPreview.alpha = enabled ? 1.0 : 0.5;
self.enabled = enabled;
};
self.down = function (x, y, obj) {
if (!self.enabled) return;
// Set the selected tower type for placement
gameState.selectedTowerType = self.towerType;
gameState.placingTower = true;
// Deselect any selected tower
if (gameState.selectedTower) {
gameState.selectedTower.showRange(false);
gameState.selectedTower = null;
}
// Show tower placeholders
for (var i = 0; i < gameState.map.length; i++) {
for (var j = 0; j < gameState.map[i].length; j++) {
var tile = gameState.map[i][j];
if (tile.canPlaceTower && !tile.tower) {
tile.setHighlight(true);
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2B5329 // Green background for the game
});
/****
* Game Code
****/
// Game state variables
// Tower assets
// Enemy assets
// Map and UI assets
// Sounds
// Music
var gameState = {
gold: 200,
lives: 10,
score: 0,
wave: 0,
enemyCount: 0,
waveInProgress: false,
selectedTower: null,
selectedTowerType: null,
placingTower: false,
map: [],
towers: [],
enemies: [],
projectiles: [],
path: [],
tileSize: 100,
mapWidth: 10,
mapHeight: 10
};
// Create UI elements
var scoreText = new Text2('Score: 0', {
size: 36,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var goldText = new Text2('Gold: ' + gameState.gold, {
size: 36,
fill: 0xFFD700
});
goldText.anchor.set(0, 0);
goldText.x = 150;
LK.gui.topLeft.addChild(goldText);
var livesText = new Text2('Lives: ' + gameState.lives, {
size: 36,
fill: 0xFF0000
});
livesText.anchor.set(1, 0);
livesText.x = -150;
LK.gui.topRight.addChild(livesText);
var waveText = new Text2('Wave: ' + gameState.wave, {
size: 36,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 50;
LK.gui.top.addChild(waveText);
var nextWaveButton = new Container();
var nextWaveButtonGraphic = nextWaveButton.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var nextWaveButtonText = new Text2('Next Wave', {
size: 30,
fill: 0xFFFFFF
});
nextWaveButtonText.anchor.set(0.5, 0.5);
nextWaveButton.addChild(nextWaveButtonText);
nextWaveButton.x = 0;
nextWaveButton.y = -100;
LK.gui.bottom.addChild(nextWaveButton);
// Create tower buttons UI
var towerButtonsContainer = new Container();
towerButtonsContainer.y = -200;
LK.gui.bottom.addChild(towerButtonsContainer);
var towerButtons = [{
type: 'cannonTower',
cost: 100
}, {
type: 'archerTower',
cost: 150
}, {
type: 'magicTower',
cost: 200
}];
var buttonSpacing = 220;
for (var i = 0; i < towerButtons.length; i++) {
var button = new TowerButton(towerButtons[i].type, towerButtons[i].cost);
button.x = (i - (towerButtons.length - 1) / 2) * buttonSpacing;
towerButtonsContainer.addChild(button);
}
// Tower upgrade button and sell button
var upgradeButton = new Container();
var upgradeButtonGraphic = upgradeButton.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var upgradeButtonText = new Text2('Upgrade', {
size: 30,
fill: 0xFFFFFF
});
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(upgradeButtonText);
upgradeButton.visible = false;
upgradeButton.x = -120;
upgradeButton.y = -300;
LK.gui.bottom.addChild(upgradeButton);
var sellButton = new Container();
var sellButtonGraphic = sellButton.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var sellButtonText = new Text2('Sell', {
size: 30,
fill: 0xFFFFFF
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
sellButton.visible = false;
sellButton.x = 120;
sellButton.y = -300;
LK.gui.bottom.addChild(sellButton);
// Update functions for UI elements
function updateScoreText() {
scoreText.setText('Score: ' + gameState.score);
}
function updateGoldText() {
goldText.setText('Gold: ' + gameState.gold);
// Update tower buttons enabled/disabled state based on gold
for (var i = 0; i < towerButtonsContainer.children.length; i++) {
var button = towerButtonsContainer.children[i];
button.setEnabled(gameState.gold >= button.cost);
}
}
function updateLivesText() {
livesText.setText('Lives: ' + gameState.lives);
}
function updateWaveText() {
waveText.setText('Wave: ' + gameState.wave);
}
// Generate the game map with path
function generateMap() {
// Define map layout
var layout = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], [0, 1, 0, 1, 1, 1, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1, 1, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 2]];
var startX = 0;
var startY = 0;
var endX = 0;
var endY = 0;
// X offset to center map on screen
var offsetX = (2048 - gameState.mapWidth * gameState.tileSize) / 2;
var offsetY = (2732 - gameState.mapHeight * gameState.tileSize) / 2 - 150; // Offset to account for UI
// Create map tiles based on layout
for (var i = 0; i < gameState.mapHeight; i++) {
gameState.map[i] = [];
for (var j = 0; j < gameState.mapWidth; j++) {
var tileType = layout[i][j] === 1 ? 'path' : 'grass';
// Handle start point (entry point)
if (layout[i][j] === 1 && (i === 0 || j === 0 || i === gameState.mapHeight - 1 || j === gameState.mapWidth - 1)) {
if (gameState.path.length === 0) {
startX = j * gameState.tileSize + offsetX + gameState.tileSize / 2;
startY = i * gameState.tileSize + offsetY + gameState.tileSize / 2;
}
}
// Handle end point (base)
if (layout[i][j] === 2) {
tileType = 'baseStructure';
endX = j * gameState.tileSize + offsetX + gameState.tileSize / 2;
endY = i * gameState.tileSize + offsetY + gameState.tileSize / 2;
}
var tile = new MapTile(tileType);
tile.x = j * gameState.tileSize + offsetX + gameState.tileSize / 2;
tile.y = i * gameState.tileSize + offsetY + gameState.tileSize / 2;
tile.gridX = j;
tile.gridY = i;
tile.tower = null;
gameState.map[i][j] = tile;
game.addChild(tile);
// Add to path if it's a path tile
if (tileType === 'path') {
gameState.path.push({
x: tile.x,
y: tile.y,
tile: tile
});
}
}
}
// Sort path from start to end (using a simplified approach for this straight path)
// This is a simplification - a real game would need a proper path finding algorithm
gameState.path.sort(function (a, b) {
var distA = Math.sqrt(Math.pow(a.x - startX, 2) + Math.pow(a.y - startY, 2));
var distB = Math.sqrt(Math.pow(b.x - startX, 2) + Math.pow(b.y - startY, 2));
return distA - distB;
});
// The first point should be off-screen for enemy spawn
gameState.path.unshift({
x: startX - gameState.tileSize,
y: startY,
offScreen: true
});
// The last point is the base
gameState.path.push({
x: endX,
y: endY,
isBase: true
});
}
// Start a new wave of enemies
function startWave() {
if (gameState.waveInProgress) return;
gameState.wave++;
updateWaveText();
gameState.waveInProgress = true;
gameState.enemyCount = 5 + gameState.wave * 2; // More enemies per wave
LK.getSound('waveStart').play();
var spawnInterval = LK.setInterval(function () {
spawnEnemy();
gameState.enemyCount--;
if (gameState.enemyCount <= 0) {
LK.clearInterval(spawnInterval);
// Wave is considered over when all enemies are spawned
// The actual check for wave completion is in the update function
}
}, 1500 - gameState.wave * 100); // Spawn faster in later waves
}
// Spawn an enemy
function spawnEnemy() {
var enemyTypes = ['basicEnemy'];
// Add more enemy types as waves progress
if (gameState.wave >= 3) enemyTypes.push('fastEnemy');
if (gameState.wave >= 5) enemyTypes.push('tankEnemy');
if (gameState.wave >= 10 && gameState.wave % 5 === 0) enemyTypes.push('bossEnemy');
var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
// Always spawn a boss on wave 5, 10, 15, etc.
if (gameState.wave % 5 === 0 && gameState.enemyCount === 0) {
randomType = 'bossEnemy';
}
var enemy = new Enemy(randomType);
// Spawn at the start of the path
// Use the first path point (index 0), which is off-screen
var startPoint = gameState.path[0];
enemy.x = startPoint.x;
enemy.y = startPoint.y;
// Set the initial path index to 1 so enemies start moving toward the first visible point
enemy.currentPathIndex = 1;
gameState.enemies.push(enemy);
game.addChild(enemy);
}
// Check if wave is complete
function checkWaveComplete() {
if (gameState.waveInProgress && gameState.enemyCount <= 0 && gameState.enemies.length === 0) {
gameState.waveInProgress = false;
gameState.gold += 50 + gameState.wave * 10; // Bonus gold for completing wave
updateGoldText();
}
}
// Place a tower on the map
function placeTower(gridX, gridY) {
var tile = gameState.map[gridY][gridX];
if (!tile.canPlaceTower || tile.tower) return false;
var towerCost = 0;
switch (gameState.selectedTowerType) {
case 'cannonTower':
towerCost = 100;
break;
case 'archerTower':
towerCost = 150;
break;
case 'magicTower':
towerCost = 200;
break;
default:
towerCost = 100;
}
if (gameState.gold < towerCost) return false;
var tower = new Tower(gameState.selectedTowerType);
tower.x = tile.x;
tower.y = tile.y;
tower.gridX = gridX;
tower.gridY = gridY;
tile.tower = tower;
gameState.towers.push(tower);
game.addChild(tower);
gameState.gold -= towerCost;
updateGoldText();
LK.getSound('buildTower').play();
// Clear tower placement mode
clearTowerPlacement();
return true;
}
// Select a tower
function selectTower(tower) {
// Deselect previously selected tower
if (gameState.selectedTower) {
gameState.selectedTower.showRange(false);
}
// Clear tower placement mode
clearTowerPlacement();
// Select new tower
gameState.selectedTower = tower;
tower.showRange(true);
// Show upgrade and sell buttons
upgradeButton.visible = true;
sellButton.visible = true;
}
// Clear tower placement mode
function clearTowerPlacement() {
gameState.placingTower = false;
gameState.selectedTowerType = null;
// Remove highlights from all tiles
for (var i = 0; i < gameState.map.length; i++) {
for (var j = 0; j < gameState.map[i].length; j++) {
gameState.map[i][j].setHighlight(false);
}
}
}
// Handle game over
function checkGameOver() {
if (gameState.lives <= 0) {
// Update high score
if (gameState.score > storage.highScore) {
storage.highScore = gameState.score;
}
// Game over
LK.getSound('gameOver').play();
LK.showGameOver();
}
}
// Event handlers
nextWaveButton.down = function () {
if (!gameState.waveInProgress) {
startWave();
}
};
upgradeButton.down = function () {
if (gameState.selectedTower) {
if (gameState.selectedTower.upgrade()) {
// Success - tower was upgraded
} else {
// Failed to upgrade (either max level or not enough gold)
}
}
};
sellButton.down = function () {
if (gameState.selectedTower) {
// Refund 70% of tower cost
var refundAmount = Math.floor(gameState.selectedTower.cost * 0.7);
gameState.gold += refundAmount;
updateGoldText();
// Remove tower from map and arrays
var tile = gameState.map[gameState.selectedTower.gridY][gameState.selectedTower.gridX];
tile.tower = null;
var towerIndex = gameState.towers.indexOf(gameState.selectedTower);
if (towerIndex !== -1) {
gameState.towers.splice(towerIndex, 1);
}
gameState.selectedTower.destroy();
gameState.selectedTower = null;
// Hide buttons
upgradeButton.visible = false;
sellButton.visible = false;
}
};
game.down = function (x, y, obj) {
if (gameState.placingTower) {
// Find which tile was clicked
for (var i = 0; i < gameState.map.length; i++) {
for (var j = 0; j < gameState.map[i].length; j++) {
var tile = gameState.map[i][j];
var dx = tile.x - x;
var dy = tile.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < gameState.tileSize / 2) {
if (placeTower(j, i)) {
return; // Tower placed successfully
}
}
}
}
// Clicked outside valid tile, cancel placement
clearTowerPlacement();
} else {
// Check if clicked on a tower
var towerClicked = false;
for (var i = 0; i < gameState.towers.length; i++) {
var tower = gameState.towers[i];
var dx = tower.x - x;
var dy = tower.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < gameState.tileSize / 2) {
selectTower(tower);
towerClicked = true;
break;
}
}
// If clicked outside tower, deselect current tower
if (!towerClicked && gameState.selectedTower) {
gameState.selectedTower.showRange(false);
gameState.selectedTower = null;
upgradeButton.visible = false;
sellButton.visible = false;
}
}
};
// Main update function
game.update = function () {
// Update all towers
for (var i = 0; i < gameState.towers.length; i++) {
gameState.towers[i].update();
}
// Update all projectiles
for (var i = gameState.projectiles.length - 1; i >= 0; i--) {
gameState.projectiles[i].update();
// Check if projectile needs to be removed
if (!gameState.projectiles[i].parent) {
gameState.projectiles.splice(i, 1);
}
}
// Update all enemies
for (var i = gameState.enemies.length - 1; i >= 0; i--) {
var enemy = gameState.enemies[i];
var reachedEnd = enemy.moveAlongPath(gameState.path);
if (reachedEnd) {
// Enemy reached the base, player loses a life
gameState.lives--;
updateLivesText();
LK.getSound('playerDamage').play();
// Remove enemy
enemy.destroy();
gameState.enemies.splice(i, 1);
// Flash screen
LK.effects.flashScreen(0xFF0000, 500);
// Check for game over
checkGameOver();
}
}
// Check if wave is complete
checkWaveComplete();
// Update UI button states
nextWaveButton.alpha = gameState.waveInProgress ? 0.5 : 1.0;
};
// Initialize game
function initGame() {
// Reset game state
gameState.gold = 200;
gameState.lives = 10;
gameState.score = 0;
gameState.wave = 0;
gameState.waveInProgress = false;
gameState.selectedTower = null;
gameState.selectedTowerType = null;
gameState.placingTower = false;
gameState.map = [];
gameState.towers = [];
gameState.enemies = [];
gameState.projectiles = [];
gameState.path = [];
// Update UI
updateScoreText();
updateGoldText();
updateLivesText();
updateWaveText();
// Generate map
generateMap();
// Hide upgrade and sell buttons
upgradeButton.visible = false;
sellButton.visible = false;
// Play background music
LK.playMusic('bgMusic');
}
// Start the game
initGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basicEnemy';
self.enemyGraphic = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
switch (self.type) {
case 'basicEnemy':
self.maxHealth = 100;
self.speed = 1.5;
self.goldValue = 10;
break;
case 'fastEnemy':
self.maxHealth = 75;
self.speed = 2.5;
self.goldValue = 15;
break;
case 'tankEnemy':
self.maxHealth = 250;
self.speed = 0.8;
self.goldValue = 25;
break;
case 'bossEnemy':
self.maxHealth = 500;
self.speed = 1.0;
self.goldValue = 100;
break;
default:
self.maxHealth = 100;
self.speed = 1.5;
self.goldValue = 10;
}
self.health = self.maxHealth;
self.currentPathIndex = 0;
self.reachedEnd = false;
// Health bar
self.healthBarBackground = new Container();
self.healthBarBackground.width = self.enemyGraphic.width * 1.2;
self.healthBarBackground.height = 10;
self.healthBarBackground.x = -self.healthBarBackground.width / 2;
self.healthBarBackground.y = -self.enemyGraphic.height / 2 - 15;
self.addChild(self.healthBarBackground);
self.healthBar = new Container();
self.healthBar.width = self.healthBarBackground.width;
self.healthBar.height = self.healthBarBackground.height;
self.healthBar.x = self.healthBarBackground.x;
self.healthBar.y = self.healthBarBackground.y;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
var healthPercentage = self.health / self.maxHealth;
self.healthBar.width = self.healthBarBackground.width * healthPercentage;
// Change color based on health percentage
if (healthPercentage > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercentage > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
};
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
// Briefly flash the enemy when taking damage
tween(self.enemyGraphic, {
alpha: 0.5
}, {
duration: 100,
onFinish: function onFinish() {
tween(self.enemyGraphic, {
alpha: 1
}, {
duration: 100
});
}
});
if (self.health <= 0) {
return true; // Enemy is dead
}
return false; // Enemy is still alive
};
self.moveAlongPath = function (path) {
if (self.currentPathIndex >= path.length) {
self.reachedEnd = true;
return true;
}
var targetX = path[self.currentPathIndex].x;
var targetY = path[self.currentPathIndex].y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.currentPathIndex++;
return false;
}
var normalizedX = dx / distance;
var normalizedY = dy / distance;
self.x += normalizedX * self.speed;
self.y += normalizedY * self.speed;
return false;
};
self.updateHealthBar();
return self;
});
var MapTile = Container.expand(function (tileType) {
var self = Container.call(this);
self.tileType = tileType || 'grass';
self.canPlaceTower = self.tileType === 'grass';
var tileGraphic = self.attachAsset(self.tileType, {
anchorX: 0.5,
anchorY: 0.5
});
self.setHighlight = function (highlight) {
if (highlight && self.canPlaceTower) {
tileGraphic.alpha = 0.8;
} else {
tileGraphic.alpha = 1.0;
}
};
return self;
});
var Projectile = Container.expand(function (targetEnemy, damage, speed, type) {
var self = Container.call(this);
self.targetEnemy = targetEnemy;
self.damage = damage || 25;
self.speed = speed || 10;
self.projectileType = type || 'projectile';
var projectileGraphic = self.attachAsset(self.projectileType, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 15) {
// Hit enemy
if (self.targetEnemy.takeDamage(self.damage)) {
// Enemy is dead
gameState.gold += self.targetEnemy.goldValue;
gameState.score += self.targetEnemy.goldValue;
updateGoldText();
updateScoreText();
LK.getSound('enemyDeath').play();
var enemyIndex = gameState.enemies.indexOf(self.targetEnemy);
if (enemyIndex !== -1) {
self.targetEnemy.destroy();
gameState.enemies.splice(enemyIndex, 1);
}
}
self.destroy();
return;
}
// Move toward enemy
var normalizedX = dx / distance;
var normalizedY = dy / distance;
self.x += normalizedX * self.speed;
self.y += normalizedY * self.speed;
// Rotate projectile to face direction of travel
self.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
self.towerType = type || 'baseTower';
self.baseTower = self.attachAsset('baseTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.towerGraphic = self.attachAsset(self.towerType, {
anchorX: 0.5,
anchorY: 0.5,
y: -10
});
// Tower properties based on type
switch (self.towerType) {
case 'cannonTower':
self.damage = 40;
self.range = 200;
self.attackSpeed = 1.5; // Attacks per second
self.cost = 100;
self.projectileSpeed = 8;
self.targetingStrategy = 'first'; // Target first enemy in path
break;
case 'archerTower':
self.damage = 20;
self.range = 300;
self.attackSpeed = 3; // Attacks per second
self.cost = 150;
self.projectileSpeed = 15;
self.targetingStrategy = 'first';
break;
case 'magicTower':
self.damage = 30;
self.range = 250;
self.attackSpeed = 2; // Attacks per second
self.cost = 200;
self.projectileSpeed = 10;
self.targetingStrategy = 'strongest'; // Target enemy with most health
break;
default:
self.damage = 25;
self.range = 200;
self.attackSpeed = 1; // Attacks per second
self.cost = 50;
self.projectileSpeed = 8;
self.targetingStrategy = 'first';
}
self.level = 1;
self.attackCooldown = 0;
self.rangeIndicator = null;
self.selected = false;
// Show range indicator when tower is selected
self.showRange = function (show) {
if (show && !self.rangeIndicator) {
self.rangeIndicator = self.attachAsset('rangeIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: self.range / 300,
// Scale to match tower's range
scaleY: self.range / 300
});
self.rangeIndicator.zIndex = -1;
} else if (!show && self.rangeIndicator) {
self.removeChild(self.rangeIndicator);
self.rangeIndicator = null;
}
};
self.upgrade = function () {
if (self.level >= 3) return false; // Max level reached
var upgradeCost = self.cost * self.level;
if (gameState.gold < upgradeCost) return false; // Not enough gold
gameState.gold -= upgradeCost;
self.level++;
// Upgrade stats
self.damage *= 1.5;
self.range *= 1.2;
self.attackSpeed *= 1.2;
// Update visuals for upgraded tower
self.towerGraphic.scale.set(1 + (self.level - 1) * 0.2);
// Update range indicator if visible
if (self.rangeIndicator) {
self.removeChild(self.rangeIndicator);
self.showRange(true);
}
updateGoldText();
return true;
};
self.findTarget = function (enemies) {
var target = null;
var maxPriority = -1;
var minDistance = Infinity;
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 <= self.range) {
var priority = 0;
switch (self.targetingStrategy) {
case 'first':
priority = enemy.currentPathIndex + (1 - distance / self.range);
break;
case 'strongest':
priority = enemy.health;
break;
case 'closest':
priority = self.range - distance;
break;
}
if (priority > maxPriority || priority === maxPriority && distance < minDistance) {
maxPriority = priority;
minDistance = distance;
target = enemy;
}
}
}
return target;
};
self.attackEnemy = function (enemy) {
if (!enemy) return;
// Create projectile
var projectile = new Projectile(enemy, self.damage, self.projectileSpeed);
projectile.x = self.x;
projectile.y = self.y;
game.addChild(projectile);
gameState.projectiles.push(projectile);
LK.getSound('projectileShot').play();
// Reset cooldown
self.attackCooldown = 60 / self.attackSpeed;
// Tower attack animation
tween(self.towerGraphic, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(self.towerGraphic, {
scaleX: 1 + (self.level - 1) * 0.2,
scaleY: 1 + (self.level - 1) * 0.2
}, {
duration: 100
});
}
});
};
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else {
var target = self.findTarget(gameState.enemies);
if (target) {
self.attackEnemy(target);
}
}
};
self.down = function (x, y, obj) {
// Select this tower, deselect others
selectTower(self);
};
return self;
});
var TowerButton = Container.expand(function (towerType, cost) {
var self = Container.call(this);
self.towerType = towerType;
self.cost = cost;
var button = self.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var towerPreview = self.attachAsset(towerType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var costText = new Text2(cost.toString(), {
size: 24,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0.5);
costText.y = 30;
self.addChild(costText);
self.setEnabled = function (enabled) {
button.alpha = enabled ? 1.0 : 0.5;
towerPreview.alpha = enabled ? 1.0 : 0.5;
self.enabled = enabled;
};
self.down = function (x, y, obj) {
if (!self.enabled) return;
// Set the selected tower type for placement
gameState.selectedTowerType = self.towerType;
gameState.placingTower = true;
// Deselect any selected tower
if (gameState.selectedTower) {
gameState.selectedTower.showRange(false);
gameState.selectedTower = null;
}
// Show tower placeholders
for (var i = 0; i < gameState.map.length; i++) {
for (var j = 0; j < gameState.map[i].length; j++) {
var tile = gameState.map[i][j];
if (tile.canPlaceTower && !tile.tower) {
tile.setHighlight(true);
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2B5329 // Green background for the game
});
/****
* Game Code
****/
// Game state variables
// Tower assets
// Enemy assets
// Map and UI assets
// Sounds
// Music
var gameState = {
gold: 200,
lives: 10,
score: 0,
wave: 0,
enemyCount: 0,
waveInProgress: false,
selectedTower: null,
selectedTowerType: null,
placingTower: false,
map: [],
towers: [],
enemies: [],
projectiles: [],
path: [],
tileSize: 100,
mapWidth: 10,
mapHeight: 10
};
// Create UI elements
var scoreText = new Text2('Score: 0', {
size: 36,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var goldText = new Text2('Gold: ' + gameState.gold, {
size: 36,
fill: 0xFFD700
});
goldText.anchor.set(0, 0);
goldText.x = 150;
LK.gui.topLeft.addChild(goldText);
var livesText = new Text2('Lives: ' + gameState.lives, {
size: 36,
fill: 0xFF0000
});
livesText.anchor.set(1, 0);
livesText.x = -150;
LK.gui.topRight.addChild(livesText);
var waveText = new Text2('Wave: ' + gameState.wave, {
size: 36,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 50;
LK.gui.top.addChild(waveText);
var nextWaveButton = new Container();
var nextWaveButtonGraphic = nextWaveButton.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var nextWaveButtonText = new Text2('Next Wave', {
size: 30,
fill: 0xFFFFFF
});
nextWaveButtonText.anchor.set(0.5, 0.5);
nextWaveButton.addChild(nextWaveButtonText);
nextWaveButton.x = 0;
nextWaveButton.y = -100;
LK.gui.bottom.addChild(nextWaveButton);
// Create tower buttons UI
var towerButtonsContainer = new Container();
towerButtonsContainer.y = -200;
LK.gui.bottom.addChild(towerButtonsContainer);
var towerButtons = [{
type: 'cannonTower',
cost: 100
}, {
type: 'archerTower',
cost: 150
}, {
type: 'magicTower',
cost: 200
}];
var buttonSpacing = 220;
for (var i = 0; i < towerButtons.length; i++) {
var button = new TowerButton(towerButtons[i].type, towerButtons[i].cost);
button.x = (i - (towerButtons.length - 1) / 2) * buttonSpacing;
towerButtonsContainer.addChild(button);
}
// Tower upgrade button and sell button
var upgradeButton = new Container();
var upgradeButtonGraphic = upgradeButton.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var upgradeButtonText = new Text2('Upgrade', {
size: 30,
fill: 0xFFFFFF
});
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(upgradeButtonText);
upgradeButton.visible = false;
upgradeButton.x = -120;
upgradeButton.y = -300;
LK.gui.bottom.addChild(upgradeButton);
var sellButton = new Container();
var sellButtonGraphic = sellButton.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5
});
var sellButtonText = new Text2('Sell', {
size: 30,
fill: 0xFFFFFF
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
sellButton.visible = false;
sellButton.x = 120;
sellButton.y = -300;
LK.gui.bottom.addChild(sellButton);
// Update functions for UI elements
function updateScoreText() {
scoreText.setText('Score: ' + gameState.score);
}
function updateGoldText() {
goldText.setText('Gold: ' + gameState.gold);
// Update tower buttons enabled/disabled state based on gold
for (var i = 0; i < towerButtonsContainer.children.length; i++) {
var button = towerButtonsContainer.children[i];
button.setEnabled(gameState.gold >= button.cost);
}
}
function updateLivesText() {
livesText.setText('Lives: ' + gameState.lives);
}
function updateWaveText() {
waveText.setText('Wave: ' + gameState.wave);
}
// Generate the game map with path
function generateMap() {
// Define map layout
var layout = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], [0, 1, 0, 1, 1, 1, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1, 1, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 2]];
var startX = 0;
var startY = 0;
var endX = 0;
var endY = 0;
// X offset to center map on screen
var offsetX = (2048 - gameState.mapWidth * gameState.tileSize) / 2;
var offsetY = (2732 - gameState.mapHeight * gameState.tileSize) / 2 - 150; // Offset to account for UI
// Create map tiles based on layout
for (var i = 0; i < gameState.mapHeight; i++) {
gameState.map[i] = [];
for (var j = 0; j < gameState.mapWidth; j++) {
var tileType = layout[i][j] === 1 ? 'path' : 'grass';
// Handle start point (entry point)
if (layout[i][j] === 1 && (i === 0 || j === 0 || i === gameState.mapHeight - 1 || j === gameState.mapWidth - 1)) {
if (gameState.path.length === 0) {
startX = j * gameState.tileSize + offsetX + gameState.tileSize / 2;
startY = i * gameState.tileSize + offsetY + gameState.tileSize / 2;
}
}
// Handle end point (base)
if (layout[i][j] === 2) {
tileType = 'baseStructure';
endX = j * gameState.tileSize + offsetX + gameState.tileSize / 2;
endY = i * gameState.tileSize + offsetY + gameState.tileSize / 2;
}
var tile = new MapTile(tileType);
tile.x = j * gameState.tileSize + offsetX + gameState.tileSize / 2;
tile.y = i * gameState.tileSize + offsetY + gameState.tileSize / 2;
tile.gridX = j;
tile.gridY = i;
tile.tower = null;
gameState.map[i][j] = tile;
game.addChild(tile);
// Add to path if it's a path tile
if (tileType === 'path') {
gameState.path.push({
x: tile.x,
y: tile.y,
tile: tile
});
}
}
}
// Sort path from start to end (using a simplified approach for this straight path)
// This is a simplification - a real game would need a proper path finding algorithm
gameState.path.sort(function (a, b) {
var distA = Math.sqrt(Math.pow(a.x - startX, 2) + Math.pow(a.y - startY, 2));
var distB = Math.sqrt(Math.pow(b.x - startX, 2) + Math.pow(b.y - startY, 2));
return distA - distB;
});
// The first point should be off-screen for enemy spawn
gameState.path.unshift({
x: startX - gameState.tileSize,
y: startY,
offScreen: true
});
// The last point is the base
gameState.path.push({
x: endX,
y: endY,
isBase: true
});
}
// Start a new wave of enemies
function startWave() {
if (gameState.waveInProgress) return;
gameState.wave++;
updateWaveText();
gameState.waveInProgress = true;
gameState.enemyCount = 5 + gameState.wave * 2; // More enemies per wave
LK.getSound('waveStart').play();
var spawnInterval = LK.setInterval(function () {
spawnEnemy();
gameState.enemyCount--;
if (gameState.enemyCount <= 0) {
LK.clearInterval(spawnInterval);
// Wave is considered over when all enemies are spawned
// The actual check for wave completion is in the update function
}
}, 1500 - gameState.wave * 100); // Spawn faster in later waves
}
// Spawn an enemy
function spawnEnemy() {
var enemyTypes = ['basicEnemy'];
// Add more enemy types as waves progress
if (gameState.wave >= 3) enemyTypes.push('fastEnemy');
if (gameState.wave >= 5) enemyTypes.push('tankEnemy');
if (gameState.wave >= 10 && gameState.wave % 5 === 0) enemyTypes.push('bossEnemy');
var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
// Always spawn a boss on wave 5, 10, 15, etc.
if (gameState.wave % 5 === 0 && gameState.enemyCount === 0) {
randomType = 'bossEnemy';
}
var enemy = new Enemy(randomType);
// Spawn at the start of the path
// Use the first path point (index 0), which is off-screen
var startPoint = gameState.path[0];
enemy.x = startPoint.x;
enemy.y = startPoint.y;
// Set the initial path index to 1 so enemies start moving toward the first visible point
enemy.currentPathIndex = 1;
gameState.enemies.push(enemy);
game.addChild(enemy);
}
// Check if wave is complete
function checkWaveComplete() {
if (gameState.waveInProgress && gameState.enemyCount <= 0 && gameState.enemies.length === 0) {
gameState.waveInProgress = false;
gameState.gold += 50 + gameState.wave * 10; // Bonus gold for completing wave
updateGoldText();
}
}
// Place a tower on the map
function placeTower(gridX, gridY) {
var tile = gameState.map[gridY][gridX];
if (!tile.canPlaceTower || tile.tower) return false;
var towerCost = 0;
switch (gameState.selectedTowerType) {
case 'cannonTower':
towerCost = 100;
break;
case 'archerTower':
towerCost = 150;
break;
case 'magicTower':
towerCost = 200;
break;
default:
towerCost = 100;
}
if (gameState.gold < towerCost) return false;
var tower = new Tower(gameState.selectedTowerType);
tower.x = tile.x;
tower.y = tile.y;
tower.gridX = gridX;
tower.gridY = gridY;
tile.tower = tower;
gameState.towers.push(tower);
game.addChild(tower);
gameState.gold -= towerCost;
updateGoldText();
LK.getSound('buildTower').play();
// Clear tower placement mode
clearTowerPlacement();
return true;
}
// Select a tower
function selectTower(tower) {
// Deselect previously selected tower
if (gameState.selectedTower) {
gameState.selectedTower.showRange(false);
}
// Clear tower placement mode
clearTowerPlacement();
// Select new tower
gameState.selectedTower = tower;
tower.showRange(true);
// Show upgrade and sell buttons
upgradeButton.visible = true;
sellButton.visible = true;
}
// Clear tower placement mode
function clearTowerPlacement() {
gameState.placingTower = false;
gameState.selectedTowerType = null;
// Remove highlights from all tiles
for (var i = 0; i < gameState.map.length; i++) {
for (var j = 0; j < gameState.map[i].length; j++) {
gameState.map[i][j].setHighlight(false);
}
}
}
// Handle game over
function checkGameOver() {
if (gameState.lives <= 0) {
// Update high score
if (gameState.score > storage.highScore) {
storage.highScore = gameState.score;
}
// Game over
LK.getSound('gameOver').play();
LK.showGameOver();
}
}
// Event handlers
nextWaveButton.down = function () {
if (!gameState.waveInProgress) {
startWave();
}
};
upgradeButton.down = function () {
if (gameState.selectedTower) {
if (gameState.selectedTower.upgrade()) {
// Success - tower was upgraded
} else {
// Failed to upgrade (either max level or not enough gold)
}
}
};
sellButton.down = function () {
if (gameState.selectedTower) {
// Refund 70% of tower cost
var refundAmount = Math.floor(gameState.selectedTower.cost * 0.7);
gameState.gold += refundAmount;
updateGoldText();
// Remove tower from map and arrays
var tile = gameState.map[gameState.selectedTower.gridY][gameState.selectedTower.gridX];
tile.tower = null;
var towerIndex = gameState.towers.indexOf(gameState.selectedTower);
if (towerIndex !== -1) {
gameState.towers.splice(towerIndex, 1);
}
gameState.selectedTower.destroy();
gameState.selectedTower = null;
// Hide buttons
upgradeButton.visible = false;
sellButton.visible = false;
}
};
game.down = function (x, y, obj) {
if (gameState.placingTower) {
// Find which tile was clicked
for (var i = 0; i < gameState.map.length; i++) {
for (var j = 0; j < gameState.map[i].length; j++) {
var tile = gameState.map[i][j];
var dx = tile.x - x;
var dy = tile.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < gameState.tileSize / 2) {
if (placeTower(j, i)) {
return; // Tower placed successfully
}
}
}
}
// Clicked outside valid tile, cancel placement
clearTowerPlacement();
} else {
// Check if clicked on a tower
var towerClicked = false;
for (var i = 0; i < gameState.towers.length; i++) {
var tower = gameState.towers[i];
var dx = tower.x - x;
var dy = tower.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < gameState.tileSize / 2) {
selectTower(tower);
towerClicked = true;
break;
}
}
// If clicked outside tower, deselect current tower
if (!towerClicked && gameState.selectedTower) {
gameState.selectedTower.showRange(false);
gameState.selectedTower = null;
upgradeButton.visible = false;
sellButton.visible = false;
}
}
};
// Main update function
game.update = function () {
// Update all towers
for (var i = 0; i < gameState.towers.length; i++) {
gameState.towers[i].update();
}
// Update all projectiles
for (var i = gameState.projectiles.length - 1; i >= 0; i--) {
gameState.projectiles[i].update();
// Check if projectile needs to be removed
if (!gameState.projectiles[i].parent) {
gameState.projectiles.splice(i, 1);
}
}
// Update all enemies
for (var i = gameState.enemies.length - 1; i >= 0; i--) {
var enemy = gameState.enemies[i];
var reachedEnd = enemy.moveAlongPath(gameState.path);
if (reachedEnd) {
// Enemy reached the base, player loses a life
gameState.lives--;
updateLivesText();
LK.getSound('playerDamage').play();
// Remove enemy
enemy.destroy();
gameState.enemies.splice(i, 1);
// Flash screen
LK.effects.flashScreen(0xFF0000, 500);
// Check for game over
checkGameOver();
}
}
// Check if wave is complete
checkWaveComplete();
// Update UI button states
nextWaveButton.alpha = gameState.waveInProgress ? 0.5 : 1.0;
};
// Initialize game
function initGame() {
// Reset game state
gameState.gold = 200;
gameState.lives = 10;
gameState.score = 0;
gameState.wave = 0;
gameState.waveInProgress = false;
gameState.selectedTower = null;
gameState.selectedTowerType = null;
gameState.placingTower = false;
gameState.map = [];
gameState.towers = [];
gameState.enemies = [];
gameState.projectiles = [];
gameState.path = [];
// Update UI
updateScoreText();
updateGoldText();
updateLivesText();
updateWaveText();
// Generate map
generateMap();
// Hide upgrade and sell buttons
upgradeButton.visible = false;
sellButton.visible = false;
// Play background music
LK.playMusic('bgMusic');
}
// Start the game
initGame();
a archer robot. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a robotic heart. In-Game asset. 2d. High contrast. No shadows
a alien with space ship. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a king alien with space ship. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a cannon robot. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a fast alien with space ship. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a magic wizard robot. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a tank alien with space ship. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat