User prompt
towers yazısı ve start wave yazısı birbirine değmesin
User prompt
geri al
User prompt
biraz daha
User prompt
towers yazısını biraz yukarı al
User prompt
towers ve start wave butonlarını 2 kat büyük
User prompt
kulenin üstündeki sağlık çubuğunu büyüt
User prompt
harita yolunu gri karelerle değiştir
User prompt
kuleler yazsını geri getir
User prompt
towers yazısını kaldır
User prompt
biraz daha bekleyerek ateş etsin mesela 1.5 saniyede bir
User prompt
okçu kulesi düşmanları 5 vuruşta öldürebilsin
User prompt
gold ve score yazılarını üst üste koyma yan yana koy
Code edit (1 edits merged)
Please save this source code
User prompt
Medieval Tower Defense
Initial prompt
etkileyici bir orta çağ arka planının üstünde bir kule ve ona giden zikzaklı bir yol olsun ve burdan düşmanlar gelsin biz ise tıkladığımız yere silahlar koyarak onların kaleye gitmesini engelleyelim
/**** * 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();