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