User prompt
Double the health of enemies
User prompt
make double enemys heal
User prompt
When an ice bullet hits an enemy, the enemy should visually turn blue. This indicates that the enemy has been slowed or frozen. Please update the enemy's appearance (e.g., apply a blue tint or replace the texture) immediately upon collision with an ice bullet. The blue effect should last for a limited duration (e.g., 2 seconds) and then revert back to normal. Ensure this only happens when the ice bullet successfully hits an enemy and not during normal movement. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
put arcer,ice and cannon buton down corner
User prompt
when i click with my mouse to that three buttons i will choose other towers
User prompt
when i clicked button i will chose tat tower make it
User prompt
when i clicked button i will chose tat tower make it
User prompt
when i clicket arcer , ice or cannon write i want choose put that tower
User prompt
when i clicked arcer, ice or Cannon i will make spawn it
User prompt
I'm developing a 2D tower defense game using a JavaScript-based engine (Live Kod or similar). I've implemented towers (archer, ice, cannon), enemies (basic, fast, tank), bullets, a path system, GUI buttons for tower selection, a grid-based map, and wave management. The full game logic includes: - Bullet logic with tracking and damage - Enemy types with health, speed, and rewards - Towers that track targets in range and shoot periodically - A grid system for tower placement and path indication - UI for money/lives/waves - Tower selection with GUI buttons (e.g. Buton1, Buton2, Buton3) - Game states like "playing", "gameOver", and "victory" Everything displays and functions visually: - I can place towers on valid grid cells. - Enemies spawn and follow the path properly. - Bullets are defined with speed, type, and target. - The tower upgrade UI appears correctly. However, **I have a critical issue:** Even though the towers are placed and selected using GUI buttons, **they do not shoot** at enemies during gameplay. No bullets are created, and the `shoot()` method seems unused during runtime. I suspect that: 1. I'm **not calling the `update()` methods** of towers or bullets inside my main `game.update` loop. 2. This might be why towers never acquire targets or fire. My `game.update` currently handles enemy spawning and wave control, but seems to miss running per-frame logic for towers and bullets. Please review this architecture and help me: - Identify why towers aren’t shooting bullets - Suggest exactly **where and how** to call `tower.update()` and `bullet.update()` each frame - Confirm if my assumption is correct (i.e., updates not running) - Give me the fixed version of the relevant `game.update` loop All class logic (`Tower`, `Bullet`, `Enemy`, etc.) has proper `.update()` methods implemented. Let me know if anything else might cause this behavior. You can assume the rest of the code is functional. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
put the buttons right corner
User prompt
when i clicked the writes Arcer , İce or cannon i will choose put it ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
use buton1 buton2 buton3 for choose put archer ice or Cannon ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
There are 3 buttons at the bottom, and the purpose of these buttons is to select these 3 tower types. Whichever one I click on, I can place that type of tower. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'archerButton.style.fill = 0x8B4513;' Line Number: 571
User prompt
There are 3 buttons at the bottom, and the purpose of these buttons is to select these 3 tower types. Whichever one I click on, I can place that type of tower.
User prompt
make the entire path of the character pathcell
User prompt
Goal: In a 2D tower defense game written in a JavaScript-like language (possibly using a game engine like LK or similar), the player should be able to place towers on the grid by clicking on a non-path, unoccupied tile. Issue: When the player clicks on a valid, empty tile, the tower does not appear. The game.down function executes, but the tower is not placed visually or logically. There may be an issue with coordinate conversions or grid checks. Code Provided: Below is the full game code, including the game.down function that handles tower placement. It uses helper functions like worldToGrid, gridToWorld, and checks grid occupancy and path status. Request: Analyze the code and explain why towers are not being placed correctly where the player clicks. Check if coordinate conversions (toLocal, worldToGrid) are working as expected. Add debug outputs (like console.log) to help verify where the clicks are being registered and how they translate to grid positions. Provide a working fix if possible.
User prompt
Create a 2D tower defence game using JavaScript with a simple game engine similar to LK.js. Here are the game features and requirements: **Game Overview:** - The player defends a base by placing towers on a grid next to a predefined enemy path. - Enemies spawn in waves and follow this path. - Each enemy killed gives the player money. - If too many enemies reach the base, the game ends. - The background is a flat green field, and the style is flat and minimalistic. **Towers:** - The player can select from 3 types of towers: Archer, Ice, and Cannon. - Each tower has its own damage, range, fire rate, cost, and upgrade cost. - Towers can be upgraded to higher levels, increasing their stats. - Each tower shoots bullets (ice bullets can slow, cannonballs deal splash damage). **Enemies:** - Enemies follow a fixed path defined by grid coordinates. - 3 types of enemies: Basic, Fast, and Tank. - Each type has different health, speed, and reward. - Enemies can be slowed or damaged by splash effects. - A health bar above each enemy shows current health and color changes with HP level. **UI Elements:** - Top-left: Player's money and tower shop (Archer $50, Ice $75, Cannon $100) - Top-right: Remaining lives - Top-center: Current wave number - Center screen: Upgrade info when a tower is selected - The UI is clear and shows tower stats and upgrade cost when applicable. **Assets:** - Use basic shapes for most elements (e.g., ellipse or box). - One image is used for the Archer Tower. - Shapes are initialized via `LK.init.shape()` and image with `LK.init.image()`. **Code Notes:** - Bullets, enemies, and towers are each implemented as extended Container classes. - Each tower finds the nearest target in range and shoots at a fixed fire rate. - The game updates every frame and spawns enemies at 1-second intervals during waves. - Enemies and bullets are cleaned up from the list when destroyed. - When all enemies in a wave are defeated, a new wave starts after 3 seconds. Please write modular, clean, and readable code using JS. Assume `LK`, `Text2`, and `Container` are available. Start by initializing the game, defining assets, and building the main loop logic. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
place the tower where I click with the mouse
Code edit (1 edits merged)
Please save this source code
User prompt
Tower Defense Command
Initial prompt
I want to create a 2D tower defence game. The game has the following features: - A main menu (UI) where the player can start the game. - A fixed 2D map with a predefined path that enemies follow. - The player can place towers near the path by selecting them from a panel that shows each tower’s price. - Each tower is of a different type (e.g. archer, ice, cannon), has a shooting range, and attacks enemies automatically. - Towers can be upgraded to improve damage, range, or speed. - Enemies spawn in waves and follow the path toward a goal point. - Each enemy killed gives the player money. - A UI at the bottom shows available tower types and their prices. - The top UI shows the player’s money, remaining lives, and current wave number. Please provide: 1. Basic path-following system for enemies. 2. Tower placement system with snapping to a grid. 3. Shooting logic and targeting for towers. 4. Upgrade system for towers. 5. Wave manager for spawning enemy waves. 6. UI elements for tower prices, player money, lives, and waves. 7. Main menu UI with a start button. Use a clean and modular architecture (e.g. separate scripts for enemies, towers, game manager, UI, etc.).
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (type, damage, speed, tower) { var self = Container.call(this); self.type = type || 'normal'; self.damage = damage || 10; self.speed = speed || 8; self.tower = tower; self.target = null; self.destroyed = false; var bulletGraphics; if (self.type === 'ice') { bulletGraphics = self.attachAsset('iceBullet', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'cannon') { bulletGraphics = self.attachAsset('cannonBall', { anchorX: 0.5, anchorY: 0.5 }); } else { bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); } self.update = function () { // Don't update if bullet is already destroyed if (self.destroyed) { return; } if (self.target && !self.target.destroyed) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { // Hit target - mark bullet as destroyed first to prevent multiple hits self.destroyed = true; // Apply damage to target self.target.takeDamage(self.damage); // Apply special effects based on bullet type if (self.type === 'ice') { self.target.slow(0.5, 2000); // 50% speed for 2 seconds self.target.applyIceEffect(); // Apply blue visual effect } else if (self.type === 'cannon') { // Splash damage for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var splashDx = enemy.x - self.x; var splashDy = enemy.y - self.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance < 60 && enemy !== self.target) { enemy.takeDamage(self.damage * 0.5); } } } LK.getSound('enemyHit').play(); self.destroy(); return; } var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } else { self.destroyed = true; self.destroy(); } }; return self; }); var Enemy = Container.expand(function (type, path) { var self = Container.call(this); self.type = type || 'basic'; self.path = path; self.pathIndex = 0; self.health = 0; self.maxHealth = 0; self.speed = 0; self.baseSpeed = 0; self.reward = 0; self.slowFactor = 1; self.slowEndTime = 0; self.destroyed = false; self.healAmount = 0; self.hasHealed = false; // Enemy stats based on type var stats = { basic: { health: 60, speed: 1.5, reward: 10 }, fast: { health: 40, speed: 2.5, reward: 15 }, tank: { health: 160, speed: 1, reward: 25 }, boss: { health: 500, speed: 0.8, reward: 100 } }; var enemyStats = stats[self.type]; // Double health after wave 5 var healthMultiplier = currentWave > 5 ? 2 : 1; self.health = enemyStats.health * healthMultiplier; self.maxHealth = enemyStats.health * healthMultiplier; self.speed = enemyStats.speed; self.baseSpeed = enemyStats.speed; self.reward = enemyStats.reward; if (self.type === 'boss') { if (currentWave === 5) { self.healAmount = 3000; } else if (currentWave === 10) { self.healAmount = 10000; } } var enemyGraphics; if (self.type === 'fast') { enemyGraphics = self.attachAsset('fastEnemy', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'tank') { enemyGraphics = self.attachAsset('tankEnemy', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'boss') { if (currentWave === 5) { enemyGraphics = self.attachAsset('Bos1', { anchorX: 0.5, anchorY: 0.5 }); } else if (currentWave === 10) { enemyGraphics = self.attachAsset('Bos2', { anchorX: 0.5, anchorY: 0.5 }); } enemyGraphics.scaleX = 1.5; enemyGraphics.scaleY = 1.5; } else { enemyGraphics = self.attachAsset('basicEnemy', { anchorX: 0.5, anchorY: 0.5 }); } // Health bar self.healthBarBg = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.1 }); self.healthBarBg.tint = 0x000000; self.healthBarBg.y = -30; self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.1 }); self.healthBar.tint = 0x00FF00; self.healthBar.y = -30; self.addChild(self.healthBar); self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { self.die(); } }; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; self.healthBar.scaleX = 0.6 * healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0x00FF00; } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFFFF00; } else { self.healthBar.tint = 0xFF0000; } }; self.slow = function (factor, duration) { self.slowFactor = factor; self.slowEndTime = Date.now() + duration; }; self.applyIceEffect = function () { // Stop any existing ice effect tween tween.stop(enemyGraphics, { tint: true }); // Apply blue tint immediately enemyGraphics.tint = 0x4169E1; // Royal blue color // Tween back to normal color after 2 seconds tween(enemyGraphics, { tint: 0xFFFFFF }, { duration: 2000, easing: tween.easeOut }); }; self.die = function () { self.destroyed = true; // Stop any ongoing tween effects tween.stop(enemyGraphics, { tint: true }); playerMoney += self.reward; LK.getSound('enemyDestroyed').play(); updateUI(); self.destroy(); }; self.reachBase = function () { self.destroyed = true; playerLives--; updateUI(); if (playerLives <= 0) { gameState = 'gameOver'; LK.showGameOver(); } self.destroy(); }; self.update = function () { // Boss healing logic if (self.type === 'boss' && !self.hasHealed && self.health > 0 && self.health / self.maxHealth <= 0.5) { self.health += self.healAmount; if (self.health > self.maxHealth) { self.health = self.maxHealth; } self.hasHealed = true; self.updateHealthBar(); // Visual effect for healing - flash green tween.stop(enemyGraphics, { tint: true }); enemyGraphics.tint = 0x00FF00; tween(enemyGraphics, { tint: 0xFFFFFF }, { duration: 500 }); } // Handle slow effect if (Date.now() > self.slowEndTime) { self.slowFactor = 1; } var currentSpeed = self.baseSpeed * self.slowFactor; if (self.pathIndex < self.path.length - 1) { var target = self.path[self.pathIndex + 1]; var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 5) { self.pathIndex++; if (self.pathIndex >= self.path.length - 1) { self.reachBase(); return; } } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * currentSpeed; self.y += Math.sin(angle) * currentSpeed; } } else { self.reachBase(); } }; return self; }); var MoneyCoin = Container.expand(function (value) { var self = Container.call(this); self.value = value || 25; self.collected = false; var coinGraphics = self.attachAsset('moneyCoin', { anchorX: 0.5, anchorY: 0.5 }); coinGraphics.tint = 0xFFD700; // Gold color // Add value text self.valueText = new Text2('$' + self.value, { size: 20, fill: 0xFFFFFF }); self.valueText.anchor.set(0.5, 0.5); self.valueText.x = 0; self.valueText.y = -25; self.addChild(self.valueText); self.collect = function () { if (self.collected) return; self.collected = true; // Add money to player playerMoney += self.value; // If this coin was from a money tree, decrement its counter if (self.sourceTree) { self.sourceTree.currentMoney--; } // Play collection sound LK.getSound('coinCollect').play(); // Collection animation - fly up and fade out tween(self, { y: self.y - 100, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); // Update UI updateUI(); }; self.down = function (x, y, obj) { self.collect(); }; return self; }); var MoneyTree = Container.expand(function (gridX, gridY) { var self = Container.call(this); self.type = 'moneyTree'; self.gridX = gridX; self.gridY = gridY; self.lastDropTime = 0; self.dropInterval = 5000; // Drop money every 5 seconds self.moneyAmount = 25; // Amount of money to drop self.maxMoney = 2; // Maximum number of money coins that can be accumulated self.currentMoney = 0; // Current number of money coins from this tree var treeGraphics = self.attachAsset('moneyTree', { anchorX: 0.5, anchorY: 0.5 }); // Add glow effect to distinguish money trees treeGraphics.tint = 0xFFD700; // Gold tint self.dropMoney = function () { // Only drop money if under the limit if (self.currentMoney >= self.maxMoney) { return; // Don't drop more money if at maximum } // Create money coin var coin = new MoneyCoin(self.moneyAmount); coin.x = self.x; coin.y = self.y - 30; // Start above the tree coin.sourceTree = self; // Reference to the tree that created this coin // Increment the tree's money count self.currentMoney++; // Add to game moneyCoins.push(coin); game.addChild(coin); // Animate coin dropping with bounce var targetY = self.y + 50; tween(coin, { y: targetY }, { duration: 800, easing: tween.bounceOut }); // Add floating effect after landing LK.setTimeout(function () { tween(coin, { y: targetY - 10 }, { duration: 1000, repeat: -1, yoyo: true, easing: tween.easeInOut }); }, 800); }; self.getTotalCost = function () { return 200; // Cost of money tree is fixed at 200 }; self.sell = function () { var sellValue = Math.floor(self.getTotalCost() * 0.75); playerMoney += sellValue; // Mark grid cell as unoccupied grid[self.gridX][self.gridY].occupied = false; // Remove from moneyTrees array for (var i = moneyTrees.length - 1; i >= 0; i--) { if (moneyTrees[i] === self) { moneyTrees.splice(i, 1); break; } } // Clear selection selectedTower = null; showUpgradeUI = false; // Update UI updateUI(); // Destroy tree self.destroy(); }; self.down = function (x, y, obj) { if (gameState === 'playing') { selectedTower = self; showUpgradeUI = true; } }; self.update = function () { var currentTime = Date.now(); if (currentTime - self.lastDropTime >= self.dropInterval) { self.dropMoney(); self.lastDropTime = currentTime; } }; return self; }); var Tower = Container.expand(function (type, gridX, gridY) { var self = Container.call(this); self.type = type || 'archer'; self.gridX = gridX; self.gridY = gridY; self.level = 1; self.lastShotTime = 0; // Tower stats based on type and level self.getStats = function () { var stats = { archer: { damage: 15 * self.level, range: 240 + self.level * 20, fireRate: 800 - self.level * 100, // ms between shots cost: 50, upgradeCost: self.level * 60 }, ice: { damage: 8 * self.level, range: 135 + self.level * 15, fireRate: 1200 - self.level * 150, cost: 75, upgradeCost: self.level * 90 }, cannon: { damage: 40 * self.level, range: 90 + self.level * 10, fireRate: 1500 - self.level * 200, cost: 100, upgradeCost: self.level * 120 } }; return stats[self.type]; }; var towerGraphics; if (self.type === 'ice') { towerGraphics = self.attachAsset('iceTower', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'cannon') { towerGraphics = self.attachAsset('cannonTower', { anchorX: 0.5, anchorY: 0.5 }); } else { towerGraphics = self.attachAsset('archerTower', { anchorX: 0.5, anchorY: 0.5 }); } // Level indicator self.levelText = new Text2(self.level.toString(), { size: 20, fill: 0xFFFFFF }); self.levelText.anchor.set(0.5, 0.5); self.levelText.x = 0; self.levelText.y = -30; self.addChild(self.levelText); self.findTarget = function () { var stats = self.getStats(); var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= stats.range && distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.shoot = function (target) { var stats = self.getStats(); var bullet = new Bullet(self.type, stats.damage, 8, self); bullet.target = target; bullet.x = self.x; bullet.y = self.y; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.getTotalCost = function () { var stats = self.getStats(); var totalCost = stats.cost; // Add upgrade costs for each level beyond 1 for (var i = 1; i < self.level; i++) { totalCost += i * (stats.cost * 0.6); // Upgrade cost formula } return totalCost; }; self.upgrade = function () { var stats = self.getStats(); if (playerMoney >= stats.upgradeCost) { playerMoney -= stats.upgradeCost; self.level++; self.levelText.setText(self.level.toString()); updateUI(); } }; self.sell = function () { var sellValue = Math.floor(self.getTotalCost() * 0.75); playerMoney += sellValue; // Mark grid cell as unoccupied grid[self.gridX][self.gridY].occupied = false; // Remove from towers array for (var i = towers.length - 1; i >= 0; i--) { if (towers[i] === self) { towers.splice(i, 1); break; } } // Clear selection selectedTower = null; showUpgradeUI = false; // Update UI updateUI(); // Destroy tower self.destroy(); }; self.down = function (x, y, obj) { if (gameState === 'playing') { selectedTower = self; showUpgradeUI = true; } }; self.update = function () { var stats = self.getStats(); var currentTime = Date.now(); if (currentTime - self.lastShotTime >= stats.fireRate) { var target = self.findTarget(); if (target) { self.shoot(target); self.lastShotTime = currentTime; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Game state var gameState = 'playing'; // 'playing', 'gameOver', 'victory' var playerMoney = 200; var playerLives = 20; var currentWave = 1; var maxWaves = 10; var waveInProgress = false; var nextWaveTimer = 0; var enemySpawnTimer = 0; var enemiesInWave = 0; var enemiesSpawned = 0; // Game objects var towers = []; var enemies = []; var bullets = []; var moneyTrees = []; var moneyCoins = []; var selectedTower = null; var showUpgradeUI = false; // Grid system var gridSize = 80; var gridWidth = Math.floor(2048 / gridSize); var gridHeight = Math.floor(2732 / gridSize); var grid = []; // Path definition (from top to bottom with some turns) var pathPoints = [{ x: 0, y: 5 }, { x: 3, y: 5 }, { x: 3, y: 10 }, { x: 8, y: 10 }, { x: 8, y: 15 }, { x: 15, y: 15 }, { x: 15, y: 20 }, { x: 20, y: 20 }, { x: 20, y: 25 }, { x: 25, y: 25 }]; // Convert grid coordinates to world coordinates function gridToWorld(gridX, gridY) { return { x: gridX * gridSize + gridSize / 2, y: gridY * gridSize + gridSize / 2 }; } // Convert world coordinates to grid coordinates function worldToGrid(worldX, worldY) { return { x: Math.floor(worldX / gridSize), y: Math.floor(worldY / gridSize) }; } // Initialize grid function initializeGrid() { for (var x = 0; x < gridWidth; x++) { grid[x] = []; for (var y = 0; y < gridHeight; y++) { grid[x][y] = { occupied: false, isPath: false }; } } // Mark path cells - create continuous path between all points for (var i = 0; i < pathPoints.length; i++) { var point = pathPoints[i]; if (point.x < gridWidth && point.y < gridHeight) { grid[point.x][point.y].isPath = true; } // If not the last point, draw line to next point if (i < pathPoints.length - 1) { var nextPoint = pathPoints[i + 1]; var startX = point.x; var startY = point.y; var endX = nextPoint.x; var endY = nextPoint.y; // Draw horizontal line first, then vertical if (startX !== endX) { var minX = Math.min(startX, endX); var maxX = Math.max(startX, endX); for (var x = minX; x <= maxX; x++) { if (x < gridWidth && startY < gridHeight) { grid[x][startY].isPath = true; } } } if (startY !== endY) { var minY = Math.min(startY, endY); var maxY = Math.max(startY, endY); for (var y = minY; y <= maxY; y++) { if (endX < gridWidth && y < gridHeight) { grid[endX][y].isPath = true; } } } } } } // Draw grid function drawGrid() { for (var x = 0; x < gridWidth; x++) { for (var y = 0; y < gridHeight; y++) { var worldPos = gridToWorld(x, y); var cell; if (grid[x][y].isPath) { cell = LK.getAsset('pathCell', { anchorX: 0.5, anchorY: 0.5, x: worldPos.x, y: worldPos.y, alpha: 0.8 }); } else { cell = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, x: worldPos.x, y: worldPos.y, alpha: 0.3 }); } game.addChild(cell); } } } // Create path for enemies function createPath() { var path = []; for (var i = 0; i < pathPoints.length; i++) { var point = pathPoints[i]; var worldPos = gridToWorld(point.x, point.y); path.push(worldPos); } return path; } // Place base at the end of path function placeBase() { var lastPoint = pathPoints[pathPoints.length - 1]; var worldPos = gridToWorld(lastPoint.x, lastPoint.y); var base = LK.getAsset('base', { anchorX: 0.5, anchorY: 0.5, x: worldPos.x, y: worldPos.y }); game.addChild(base); } // Place money trees at strategic locations - removed automatic placement for player purchase function placeMoneyTrees() { // Money trees are now purchased by player, no automatic placement } // UI elements var moneyText = new Text2('Money: $' + playerMoney, { size: 40, fill: 0xFFFFFF }); moneyText.anchor.set(0, 0); moneyText.x = 120; moneyText.y = 20; LK.gui.topLeft.addChild(moneyText); var livesText = new Text2('Lives: ' + playerLives, { size: 40, fill: 0xFFFFFF }); livesText.anchor.set(1, 0); livesText.x = -20; livesText.y = 20; LK.gui.topRight.addChild(livesText); var waveText = new Text2('Wave: ' + currentWave + '/' + maxWaves, { size: 40, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); waveText.x = 0; waveText.y = 20; LK.gui.top.addChild(waveText); // Tower shop with button assets positioned at bottom corner var shopY = -300; // Position from bottom var buttonSize = 80; var buttonSpacing = 100; // Archer button (Buton1) var archerButton = LK.getAsset('Buton1', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing, y: shopY }); // Add direct click handler to archer button archerButton.down = function (x, y, obj) { selectedTowerType = 'archer'; showRangeCircle = true; console.log('Archer button clicked directly'); // Add click animation tween(archerButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(archerButton); var archerText = new Text2('Archer', { size: 25, fill: 0xFFFFFF }); archerText.anchor.set(0.5, 0.5); archerText.x = buttonSpacing; archerText.y = shopY; LK.gui.bottomLeft.addChild(archerText); // Ice button (Buton2) var iceButton = LK.getAsset('Buton2', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing * 2, y: shopY }); // Add direct click handler to ice button iceButton.down = function (x, y, obj) { selectedTowerType = 'ice'; showRangeCircle = true; console.log('Ice button clicked directly'); // Add click animation tween(iceButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(iceButton); var iceText = new Text2('Ice', { size: 25, fill: 0xFFFFFF }); iceText.anchor.set(0.5, 0.5); iceText.x = buttonSpacing * 2; iceText.y = shopY; LK.gui.bottomLeft.addChild(iceText); // Cannon button (Buton3) var cannonButton = LK.getAsset('Buton3', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing * 3, y: shopY }); // Add direct click handler to cannon button cannonButton.down = function (x, y, obj) { selectedTowerType = 'cannon'; showRangeCircle = true; console.log('Cannon button clicked directly'); // Add click animation tween(cannonButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(cannonButton); var cannonText = new Text2('Cannon', { size: 25, fill: 0xFFFFFF }); cannonText.anchor.set(0.5, 0.5); cannonText.x = buttonSpacing * 3; cannonText.y = shopY; LK.gui.bottomLeft.addChild(cannonText); // Money Tree button (fourth button) var moneyTreeButton = LK.getAsset('Buton1', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing * 4, y: shopY }); moneyTreeButton.tint = 0xFFD700; // Gold color for money tree // Add direct click handler to money tree button moneyTreeButton.down = function (x, y, obj) { selectedTowerType = 'moneyTree'; showRangeCircle = false; // No range circle for money trees console.log('Money Tree button clicked directly'); // Add click animation tween(moneyTreeButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(moneyTreeButton); var moneyTreeText = new Text2('Money Tree', { size: 20, fill: 0xFFFFFF }); moneyTreeText.anchor.set(0.5, 0.5); moneyTreeText.x = buttonSpacing * 4; moneyTreeText.y = shopY; LK.gui.bottomLeft.addChild(moneyTreeText); // Selected tower info var selectedTowerType = 'archer'; var rangeCircle = null; var showRangeCircle = false; // Add status text to show selected tower type var selectedTowerText = new Text2('Selected: Archer Tower ($50)', { size: 30, fill: 0x00FF00 }); selectedTowerText.anchor.set(1, 0); selectedTowerText.x = -20; selectedTowerText.y = 80; LK.gui.topRight.addChild(selectedTowerText); function showTowerRange(x, y) { if (!showRangeCircle) return; // Get range for selected tower type var towerRanges = { archer: 240, ice: 135, cannon: 90 }; var range = towerRanges[selectedTowerType]; // Remove existing range circle if (rangeCircle) { rangeCircle.destroy(); rangeCircle = null; } // Create new range circle rangeCircle = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: range / 40, // Scale to match range (gridCell is 80px, so 40 is radius) scaleY: range / 40, alpha: 0.3, x: x, y: y }); rangeCircle.tint = 0x0080FF; // Semi-transparent blue game.addChild(rangeCircle); } function hideRangeCircle() { showRangeCircle = false; if (rangeCircle) { rangeCircle.destroy(); rangeCircle = null; } } function updateTowerSelection() { // Stop any existing tweens on buttons tween.stop(archerButton, { tint: true, scaleX: true, scaleY: true }); tween.stop(iceButton, { tint: true, scaleX: true, scaleY: true }); tween.stop(cannonButton, { tint: true, scaleX: true, scaleY: true }); tween.stop(moneyTreeButton, { tint: true, scaleX: true, scaleY: true }); // Reset all button colors and scales archerButton.tint = 0xFFFFFF; iceButton.tint = 0xFFFFFF; cannonButton.tint = 0xFFFFFF; moneyTreeButton.tint = 0xFFD700; // Gold base color for money tree archerButton.scaleX = archerButton.scaleY = 1; iceButton.scaleX = iceButton.scaleY = 1; cannonButton.scaleX = cannonButton.scaleY = 1; moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1; // Highlight selected button with bright green glow and scale if (selectedTowerType === 'archer') { archerButton.tint = 0x00FF00; // Bright green for selected archerButton.scaleX = archerButton.scaleY = 1.2; // Add pulsing effect tween(archerButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } else if (selectedTowerType === 'ice') { iceButton.tint = 0x00FF00; // Bright green for selected iceButton.scaleX = iceButton.scaleY = 1.2; // Add pulsing effect tween(iceButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } else if (selectedTowerType === 'cannon') { cannonButton.tint = 0x00FF00; // Bright green for selected cannonButton.scaleX = cannonButton.scaleY = 1.2; // Add pulsing effect tween(cannonButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } else if (selectedTowerType === 'moneyTree') { moneyTreeButton.tint = 0x00FF00; // Bright green for selected moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1.2; // Add pulsing effect tween(moneyTreeButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } // Update status text var towerCosts = { archer: 50, ice: 75, cannon: 100, moneyTree: 200 }; var cost = towerCosts[selectedTowerType]; var towerName = selectedTowerType === 'moneyTree' ? 'Money Tree' : selectedTowerType.charAt(0).toUpperCase() + selectedTowerType.slice(1); var canAfford = playerMoney >= cost; var statusColor = canAfford ? 0x00FF00 : 0xFF0000; selectedTowerText.tint = statusColor; var itemType = selectedTowerType === 'moneyTree' ? '' : ' Tower'; selectedTowerText.setText('Selected: ' + towerName + itemType + ' ($' + cost + ') - Click grid to place'); } // Upgrade UI Canvas Background var upgradeCanvas = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 6, scaleY: 4, alpha: 0.7 }); upgradeCanvas.tint = 0x333333; // Semi-transparent gray upgradeCanvas.x = 0; upgradeCanvas.y = 280; LK.gui.center.addChild(upgradeCanvas); // Upgrade UI Title var upgradeTitle = new Text2('TOWER INFO', { size: 32, fill: 0xFFFFFF }); upgradeTitle.anchor.set(0.5, 0.5); upgradeTitle.x = 0; upgradeTitle.y = 180; LK.gui.center.addChild(upgradeTitle); // Tower stats display var upgradeText = new Text2('', { size: 28, fill: 0xFFFFFF }); upgradeText.anchor.set(0.5, 0.5); upgradeText.x = 0; upgradeText.y = 250; LK.gui.center.addChild(upgradeText); // Upgrade button with background var upgradeButtonBg = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 0.8, alpha: 0.8 }); upgradeButtonBg.tint = 0xFFD700; upgradeButtonBg.x = 0; upgradeButtonBg.y = 350; LK.gui.center.addChild(upgradeButtonBg); var upgradeButton = new Text2('', { size: 30, fill: 0x000000 }); upgradeButton.anchor.set(0.5, 0.5); upgradeButton.x = 0; upgradeButton.y = 350; upgradeButton.scaleX = 1; upgradeButton.scaleY = 1; LK.gui.center.addChild(upgradeButton); // Sell button with background var sellButtonBg = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 0.8, alpha: 0.8 }); sellButtonBg.tint = 0xFF4444; sellButtonBg.x = 0; sellButtonBg.y = 410; LK.gui.center.addChild(sellButtonBg); var sellButton = new Text2('', { size: 30, fill: 0xFFFFFF }); sellButton.anchor.set(0.5, 0.5); sellButton.x = 0; sellButton.y = 410; sellButton.scaleX = 1; sellButton.scaleY = 1; LK.gui.center.addChild(sellButton); upgradeButton.down = function () { if (showUpgradeUI && selectedTower) { var stats = selectedTower.getStats(); if (playerMoney >= stats.upgradeCost) { // Animate both button and background tween(upgradeButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, onFinish: function onFinish() { tween(upgradeButton, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); tween(upgradeButtonBg, { scaleX: 2.7, scaleY: 0.9 }, { duration: 100, onFinish: function onFinish() { tween(upgradeButtonBg, { scaleX: 2.5, scaleY: 0.8 }, { duration: 100 }); } }); selectedTower.upgrade(); } } }; sellButton.down = function () { if (showUpgradeUI && selectedTower) { // Animate both button and background tween(sellButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, onFinish: function onFinish() { tween(sellButton, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); tween(sellButtonBg, { scaleX: 2.7, scaleY: 0.9 }, { duration: 100, onFinish: function onFinish() { tween(sellButtonBg, { scaleX: 2.5, scaleY: 0.8 }, { duration: 100 }); } }); selectedTower.sell(); } }; function updateUI() { moneyText.setText('Money: $' + playerMoney); livesText.setText('Lives: ' + playerLives); waveText.setText('Wave: ' + currentWave + '/' + maxWaves); updateTowerSelection(); // Update selection display when money changes if (showUpgradeUI && selectedTower) { var isMoneyTree = selectedTower.type === 'moneyTree'; var sellValue = Math.floor(selectedTower.getTotalCost() * 0.75); // Show canvas and UI elements upgradeCanvas.alpha = 0.7; upgradeTitle.alpha = 1; upgradeText.alpha = 1; upgradeButton.alpha = isMoneyTree ? 0 : 1; sellButton.alpha = 1; upgradeButtonBg.alpha = isMoneyTree ? 0 : 0.8; sellButtonBg.alpha = 0.8; sellButton.setText('SELL $' + sellValue); if (isMoneyTree) { // Update text content for Money Tree upgradeTitle.setText('MONEY TREE'); var infoText = 'Generates $' + selectedTower.moneyAmount + ' every ' + selectedTower.dropInterval / 1000 + 's.\n'; infoText += 'Max ' + selectedTower.maxMoney + ' coins can accumulate.'; upgradeText.setText(infoText); } else { // Update text content for normal towers var stats = selectedTower.getStats(); var towerName = selectedTower.type.charAt(0).toUpperCase() + selectedTower.type.slice(1); upgradeTitle.setText(towerName.toUpperCase() + ' TOWER'); upgradeText.setText('Damage: ' + stats.damage + '\nRange: ' + stats.range + '\nFire Rate: ' + Math.round(1000 / stats.fireRate * 60) + '/sec'); upgradeButton.setText('UPGRADE $' + stats.upgradeCost); // Visual feedback for upgrade affordability if (playerMoney >= stats.upgradeCost) { upgradeButtonBg.tint = 0xFFD700; // Gold when affordable upgradeButton.tint = 0x000000; // Black text on gold background } else { upgradeButtonBg.tint = 0x666666; // Gray when not affordable upgradeButton.tint = 0xFFFFFF; // White text on gray background } } // Sell button is always available sellButtonBg.tint = 0xFF4444; sellButton.tint = 0xFFFFFF; } else { upgradeCanvas.alpha = 0; upgradeTitle.alpha = 0; upgradeText.alpha = 0; upgradeButton.alpha = 0; sellButton.alpha = 0; upgradeButtonBg.alpha = 0; sellButtonBg.alpha = 0; } } // Wave management function getWaveData(waveNumber) { if (waveNumber === 5 || waveNumber === 10) { var waveEnemies = ['boss']; var escortTypes = ['basic', 'fast', 'tank']; var numEscorts = waveNumber === 5 ? 8 : 12; for (var i = 0; i < numEscorts; i++) { var type = escortTypes[Math.floor(Math.random() * escortTypes.length)]; waveEnemies.push(type); } // Shuffle the wave so boss doesn't always appear first for (var i = waveEnemies.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = waveEnemies[i]; waveEnemies[i] = waveEnemies[j]; waveEnemies[j] = temp; } return waveEnemies; } var baseEnemies = 5 + waveNumber * 2; var enemyTypes = ['basic']; if (waveNumber >= 3) enemyTypes.push('fast'); if (waveNumber >= 5) enemyTypes.push('tank'); var waveEnemies = []; for (var i = 0; i < baseEnemies; i++) { var type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; waveEnemies.push(type); } return waveEnemies; } function startWave() { if (currentWave > maxWaves) { gameState = 'victory'; LK.showYouWin(); return; } waveInProgress = true; var waveData = getWaveData(currentWave); enemiesInWave = waveData.length; enemiesSpawned = 0; enemySpawnTimer = 0; // Store wave data for spawning game.currentWaveData = waveData; } function spawnEnemy() { if (!waveInProgress || enemiesSpawned >= enemiesInWave) return; var enemyType = game.currentWaveData[enemiesSpawned]; var path = createPath(); var enemy = new Enemy(enemyType, path); enemy.x = path[0].x; enemy.y = path[0].y; enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; } function checkWaveComplete() { if (waveInProgress && enemies.length === 0 && enemiesSpawned >= enemiesInWave) { waveInProgress = false; currentWave++; nextWaveTimer = LK.ticks + 180; // 3 seconds delay updateUI(); } } // Mouse move handler for range circle game.move = function (x, y, obj) { if (showRangeCircle && gameState === 'playing') { showTowerRange(x, y); } }; // Game input handling game.down = function (x, y, obj) { if (gameState !== 'playing') return; console.log('Click detected at raw coordinates:', x, y); // Use the click coordinates directly as they're already in game space var gameX = x; var gameY = y; console.log('Game coordinates:', gameX, gameY); // Check tower shop buttons - convert screen coordinates to GUI coordinates var screenPoint = { x: x, y: y }; var guiPoint = LK.gui.bottomLeft.toLocal(screenPoint); console.log('GUI click coordinates:', guiPoint.x, guiPoint.y); // Check archer button (Buton1) - expanded click area for better touch detection if (guiPoint.x >= 50 && guiPoint.x <= 150 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'archer'; showRangeCircle = true; console.log('Selected tower type: archer'); // Add click animation tween(archerButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Check ice button (Buton2) - expanded click area for better touch detection else if (guiPoint.x >= 150 && guiPoint.x <= 250 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'ice'; showRangeCircle = true; console.log('Selected tower type: ice'); // Add click animation tween(iceButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Check cannon button (Buton3) - expanded click area for better touch detection else if (guiPoint.x >= 250 && guiPoint.x <= 350 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'cannon'; showRangeCircle = true; console.log('Selected tower type: cannon'); // Add click animation tween(cannonButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Check money tree button - expanded click area for better touch detection else if (guiPoint.x >= 350 && guiPoint.x <= 450 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'moneyTree'; showRangeCircle = false; // No range circle for money trees console.log('Selected tower type: money tree'); // Add click animation tween(moneyTreeButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Upgrade and Sell button clicks are now handled by their own .down methods. var centerPos = LK.gui.center.toLocal({ x: x, y: y }); // Hide upgrade UI if clicking elsewhere if (showUpgradeUI) { showUpgradeUI = false; selectedTower = null; updateUI(); } // Hide range circle when clicking elsewhere (cancelling purchase) hideRangeCircle(); // Try to place tower using game coordinates var gridPos = worldToGrid(gameX, gameY); console.log('Grid position:', gridPos.x, gridPos.y); console.log('Grid bounds check:', gridPos.x >= 0, gridPos.x < gridWidth, gridPos.y >= 0, gridPos.y < gridHeight); if (gridPos.x >= 0 && gridPos.x < gridWidth && gridPos.y >= 0 && gridPos.y < gridHeight) { console.log('Grid cell occupied:', grid[gridPos.x][gridPos.y].occupied); console.log('Grid cell is path:', grid[gridPos.x][gridPos.y].isPath); if (!grid[gridPos.x][gridPos.y].occupied && !grid[gridPos.x][gridPos.y].isPath) { var towerCosts = { archer: 50, ice: 75, cannon: 100, moneyTree: 200 }; var cost = towerCosts[selectedTowerType]; console.log('Tower cost:', cost, 'Player money:', playerMoney); if (playerMoney >= cost) { console.log('Placing', selectedTowerType, 'at grid:', gridPos.x, gridPos.y); var worldPos = gridToWorld(gridPos.x, gridPos.y); console.log('World position:', worldPos.x, worldPos.y); if (selectedTowerType === 'moneyTree') { // Place money tree var tree = new MoneyTree(gridPos.x, gridPos.y); tree.x = worldPos.x; tree.y = worldPos.y; // Add placement animation tree.scaleX = 0; tree.scaleY = 0; tween(tree, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); moneyTrees.push(tree); game.addChild(tree); } else { // Place tower var tower = new Tower(selectedTowerType, gridPos.x, gridPos.y); tower.x = worldPos.x; tower.y = worldPos.y; // Add placement animation tower.scaleX = 0; tower.scaleY = 0; tween(tower, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); towers.push(tower); game.addChild(tower); } grid[gridPos.x][gridPos.y].occupied = true; playerMoney -= cost; hideRangeCircle(); // Hide range circle after placing tower updateUI(); console.log(selectedTowerType, 'placed successfully!'); } else { console.log('Not enough money to place', selectedTowerType); } } else { console.log('Grid cell is occupied or on path'); } } else { console.log('Click outside grid bounds'); } }; // Initialize game initializeGrid(); drawGrid(); placeBase(); placeMoneyTrees(); updateUI(); updateTowerSelection(); startWave(); // Main game loop game.update = function () { if (gameState !== 'playing') return; // Update all towers (this enables shooting!) for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update all bullets (this enables bullet movement and collision detection!) for (var i = 0; i < bullets.length; i++) { bullets[i].update(); } // Update all enemies for (var i = 0; i < enemies.length; i++) { enemies[i].update(); } // Update all money trees for (var i = 0; i < moneyTrees.length; i++) { moneyTrees[i].update(); } // Clean up collected money coins for (var i = moneyCoins.length - 1; i >= 0; i--) { if (moneyCoins[i].collected) { moneyCoins.splice(i, 1); } } // Spawn enemies if (waveInProgress && LK.ticks % 60 === 0) { // Every second spawnEnemy(); } // Start next wave if (!waveInProgress && LK.ticks >= nextWaveTimer) { startWave(); } // Clean up destroyed enemies for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i].destroyed) { enemies.splice(i, 1); } } // Clean up destroyed bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].destroyed) { bullets.splice(i, 1); } } checkWaveComplete(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (type, damage, speed, tower) {
var self = Container.call(this);
self.type = type || 'normal';
self.damage = damage || 10;
self.speed = speed || 8;
self.tower = tower;
self.target = null;
self.destroyed = false;
var bulletGraphics;
if (self.type === 'ice') {
bulletGraphics = self.attachAsset('iceBullet', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'cannon') {
bulletGraphics = self.attachAsset('cannonBall', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.update = function () {
// Don't update if bullet is already destroyed
if (self.destroyed) {
return;
}
if (self.target && !self.target.destroyed) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
// Hit target - mark bullet as destroyed first to prevent multiple hits
self.destroyed = true;
// Apply damage to target
self.target.takeDamage(self.damage);
// Apply special effects based on bullet type
if (self.type === 'ice') {
self.target.slow(0.5, 2000); // 50% speed for 2 seconds
self.target.applyIceEffect(); // Apply blue visual effect
} else if (self.type === 'cannon') {
// Splash damage
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var splashDx = enemy.x - self.x;
var splashDy = enemy.y - self.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance < 60 && enemy !== self.target) {
enemy.takeDamage(self.damage * 0.5);
}
}
}
LK.getSound('enemyHit').play();
self.destroy();
return;
}
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
} else {
self.destroyed = true;
self.destroy();
}
};
return self;
});
var Enemy = Container.expand(function (type, path) {
var self = Container.call(this);
self.type = type || 'basic';
self.path = path;
self.pathIndex = 0;
self.health = 0;
self.maxHealth = 0;
self.speed = 0;
self.baseSpeed = 0;
self.reward = 0;
self.slowFactor = 1;
self.slowEndTime = 0;
self.destroyed = false;
self.healAmount = 0;
self.hasHealed = false;
// Enemy stats based on type
var stats = {
basic: {
health: 60,
speed: 1.5,
reward: 10
},
fast: {
health: 40,
speed: 2.5,
reward: 15
},
tank: {
health: 160,
speed: 1,
reward: 25
},
boss: {
health: 500,
speed: 0.8,
reward: 100
}
};
var enemyStats = stats[self.type];
// Double health after wave 5
var healthMultiplier = currentWave > 5 ? 2 : 1;
self.health = enemyStats.health * healthMultiplier;
self.maxHealth = enemyStats.health * healthMultiplier;
self.speed = enemyStats.speed;
self.baseSpeed = enemyStats.speed;
self.reward = enemyStats.reward;
if (self.type === 'boss') {
if (currentWave === 5) {
self.healAmount = 3000;
} else if (currentWave === 10) {
self.healAmount = 10000;
}
}
var enemyGraphics;
if (self.type === 'fast') {
enemyGraphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'tank') {
enemyGraphics = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'boss') {
if (currentWave === 5) {
enemyGraphics = self.attachAsset('Bos1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (currentWave === 10) {
enemyGraphics = self.attachAsset('Bos2', {
anchorX: 0.5,
anchorY: 0.5
});
}
enemyGraphics.scaleX = 1.5;
enemyGraphics.scaleY = 1.5;
} else {
enemyGraphics = self.attachAsset('basicEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Health bar
self.healthBarBg = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1
});
self.healthBarBg.tint = 0x000000;
self.healthBarBg.y = -30;
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1
});
self.healthBar.tint = 0x00FF00;
self.healthBar.y = -30;
self.addChild(self.healthBar);
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
self.die();
}
};
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = 0.6 * healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0xFF0000;
}
};
self.slow = function (factor, duration) {
self.slowFactor = factor;
self.slowEndTime = Date.now() + duration;
};
self.applyIceEffect = function () {
// Stop any existing ice effect tween
tween.stop(enemyGraphics, {
tint: true
});
// Apply blue tint immediately
enemyGraphics.tint = 0x4169E1; // Royal blue color
// Tween back to normal color after 2 seconds
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 2000,
easing: tween.easeOut
});
};
self.die = function () {
self.destroyed = true;
// Stop any ongoing tween effects
tween.stop(enemyGraphics, {
tint: true
});
playerMoney += self.reward;
LK.getSound('enemyDestroyed').play();
updateUI();
self.destroy();
};
self.reachBase = function () {
self.destroyed = true;
playerLives--;
updateUI();
if (playerLives <= 0) {
gameState = 'gameOver';
LK.showGameOver();
}
self.destroy();
};
self.update = function () {
// Boss healing logic
if (self.type === 'boss' && !self.hasHealed && self.health > 0 && self.health / self.maxHealth <= 0.5) {
self.health += self.healAmount;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
}
self.hasHealed = true;
self.updateHealthBar();
// Visual effect for healing - flash green
tween.stop(enemyGraphics, {
tint: true
});
enemyGraphics.tint = 0x00FF00;
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 500
});
}
// Handle slow effect
if (Date.now() > self.slowEndTime) {
self.slowFactor = 1;
}
var currentSpeed = self.baseSpeed * self.slowFactor;
if (self.pathIndex < self.path.length - 1) {
var target = self.path[self.pathIndex + 1];
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.pathIndex++;
if (self.pathIndex >= self.path.length - 1) {
self.reachBase();
return;
}
} else {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * currentSpeed;
self.y += Math.sin(angle) * currentSpeed;
}
} else {
self.reachBase();
}
};
return self;
});
var MoneyCoin = Container.expand(function (value) {
var self = Container.call(this);
self.value = value || 25;
self.collected = false;
var coinGraphics = self.attachAsset('moneyCoin', {
anchorX: 0.5,
anchorY: 0.5
});
coinGraphics.tint = 0xFFD700; // Gold color
// Add value text
self.valueText = new Text2('$' + self.value, {
size: 20,
fill: 0xFFFFFF
});
self.valueText.anchor.set(0.5, 0.5);
self.valueText.x = 0;
self.valueText.y = -25;
self.addChild(self.valueText);
self.collect = function () {
if (self.collected) return;
self.collected = true;
// Add money to player
playerMoney += self.value;
// If this coin was from a money tree, decrement its counter
if (self.sourceTree) {
self.sourceTree.currentMoney--;
}
// Play collection sound
LK.getSound('coinCollect').play();
// Collection animation - fly up and fade out
tween(self, {
y: self.y - 100,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
// Update UI
updateUI();
};
self.down = function (x, y, obj) {
self.collect();
};
return self;
});
var MoneyTree = Container.expand(function (gridX, gridY) {
var self = Container.call(this);
self.type = 'moneyTree';
self.gridX = gridX;
self.gridY = gridY;
self.lastDropTime = 0;
self.dropInterval = 5000; // Drop money every 5 seconds
self.moneyAmount = 25; // Amount of money to drop
self.maxMoney = 2; // Maximum number of money coins that can be accumulated
self.currentMoney = 0; // Current number of money coins from this tree
var treeGraphics = self.attachAsset('moneyTree', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glow effect to distinguish money trees
treeGraphics.tint = 0xFFD700; // Gold tint
self.dropMoney = function () {
// Only drop money if under the limit
if (self.currentMoney >= self.maxMoney) {
return; // Don't drop more money if at maximum
}
// Create money coin
var coin = new MoneyCoin(self.moneyAmount);
coin.x = self.x;
coin.y = self.y - 30; // Start above the tree
coin.sourceTree = self; // Reference to the tree that created this coin
// Increment the tree's money count
self.currentMoney++;
// Add to game
moneyCoins.push(coin);
game.addChild(coin);
// Animate coin dropping with bounce
var targetY = self.y + 50;
tween(coin, {
y: targetY
}, {
duration: 800,
easing: tween.bounceOut
});
// Add floating effect after landing
LK.setTimeout(function () {
tween(coin, {
y: targetY - 10
}, {
duration: 1000,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
}, 800);
};
self.getTotalCost = function () {
return 200; // Cost of money tree is fixed at 200
};
self.sell = function () {
var sellValue = Math.floor(self.getTotalCost() * 0.75);
playerMoney += sellValue;
// Mark grid cell as unoccupied
grid[self.gridX][self.gridY].occupied = false;
// Remove from moneyTrees array
for (var i = moneyTrees.length - 1; i >= 0; i--) {
if (moneyTrees[i] === self) {
moneyTrees.splice(i, 1);
break;
}
}
// Clear selection
selectedTower = null;
showUpgradeUI = false;
// Update UI
updateUI();
// Destroy tree
self.destroy();
};
self.down = function (x, y, obj) {
if (gameState === 'playing') {
selectedTower = self;
showUpgradeUI = true;
}
};
self.update = function () {
var currentTime = Date.now();
if (currentTime - self.lastDropTime >= self.dropInterval) {
self.dropMoney();
self.lastDropTime = currentTime;
}
};
return self;
});
var Tower = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.type = type || 'archer';
self.gridX = gridX;
self.gridY = gridY;
self.level = 1;
self.lastShotTime = 0;
// Tower stats based on type and level
self.getStats = function () {
var stats = {
archer: {
damage: 15 * self.level,
range: 240 + self.level * 20,
fireRate: 800 - self.level * 100,
// ms between shots
cost: 50,
upgradeCost: self.level * 60
},
ice: {
damage: 8 * self.level,
range: 135 + self.level * 15,
fireRate: 1200 - self.level * 150,
cost: 75,
upgradeCost: self.level * 90
},
cannon: {
damage: 40 * self.level,
range: 90 + self.level * 10,
fireRate: 1500 - self.level * 200,
cost: 100,
upgradeCost: self.level * 120
}
};
return stats[self.type];
};
var towerGraphics;
if (self.type === 'ice') {
towerGraphics = self.attachAsset('iceTower', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'cannon') {
towerGraphics = self.attachAsset('cannonTower', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
towerGraphics = self.attachAsset('archerTower', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Level indicator
self.levelText = new Text2(self.level.toString(), {
size: 20,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0.5, 0.5);
self.levelText.x = 0;
self.levelText.y = -30;
self.addChild(self.levelText);
self.findTarget = function () {
var stats = self.getStats();
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= stats.range && distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.shoot = function (target) {
var stats = self.getStats();
var bullet = new Bullet(self.type, stats.damage, 8, self);
bullet.target = target;
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.getTotalCost = function () {
var stats = self.getStats();
var totalCost = stats.cost;
// Add upgrade costs for each level beyond 1
for (var i = 1; i < self.level; i++) {
totalCost += i * (stats.cost * 0.6); // Upgrade cost formula
}
return totalCost;
};
self.upgrade = function () {
var stats = self.getStats();
if (playerMoney >= stats.upgradeCost) {
playerMoney -= stats.upgradeCost;
self.level++;
self.levelText.setText(self.level.toString());
updateUI();
}
};
self.sell = function () {
var sellValue = Math.floor(self.getTotalCost() * 0.75);
playerMoney += sellValue;
// Mark grid cell as unoccupied
grid[self.gridX][self.gridY].occupied = false;
// Remove from towers array
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] === self) {
towers.splice(i, 1);
break;
}
}
// Clear selection
selectedTower = null;
showUpgradeUI = false;
// Update UI
updateUI();
// Destroy tower
self.destroy();
};
self.down = function (x, y, obj) {
if (gameState === 'playing') {
selectedTower = self;
showUpgradeUI = true;
}
};
self.update = function () {
var stats = self.getStats();
var currentTime = Date.now();
if (currentTime - self.lastShotTime >= stats.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShotTime = currentTime;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game state
var gameState = 'playing'; // 'playing', 'gameOver', 'victory'
var playerMoney = 200;
var playerLives = 20;
var currentWave = 1;
var maxWaves = 10;
var waveInProgress = false;
var nextWaveTimer = 0;
var enemySpawnTimer = 0;
var enemiesInWave = 0;
var enemiesSpawned = 0;
// Game objects
var towers = [];
var enemies = [];
var bullets = [];
var moneyTrees = [];
var moneyCoins = [];
var selectedTower = null;
var showUpgradeUI = false;
// Grid system
var gridSize = 80;
var gridWidth = Math.floor(2048 / gridSize);
var gridHeight = Math.floor(2732 / gridSize);
var grid = [];
// Path definition (from top to bottom with some turns)
var pathPoints = [{
x: 0,
y: 5
}, {
x: 3,
y: 5
}, {
x: 3,
y: 10
}, {
x: 8,
y: 10
}, {
x: 8,
y: 15
}, {
x: 15,
y: 15
}, {
x: 15,
y: 20
}, {
x: 20,
y: 20
}, {
x: 20,
y: 25
}, {
x: 25,
y: 25
}];
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: gridX * gridSize + gridSize / 2,
y: gridY * gridSize + gridSize / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
return {
x: Math.floor(worldX / gridSize),
y: Math.floor(worldY / gridSize)
};
}
// Initialize grid
function initializeGrid() {
for (var x = 0; x < gridWidth; x++) {
grid[x] = [];
for (var y = 0; y < gridHeight; y++) {
grid[x][y] = {
occupied: false,
isPath: false
};
}
}
// Mark path cells - create continuous path between all points
for (var i = 0; i < pathPoints.length; i++) {
var point = pathPoints[i];
if (point.x < gridWidth && point.y < gridHeight) {
grid[point.x][point.y].isPath = true;
}
// If not the last point, draw line to next point
if (i < pathPoints.length - 1) {
var nextPoint = pathPoints[i + 1];
var startX = point.x;
var startY = point.y;
var endX = nextPoint.x;
var endY = nextPoint.y;
// Draw horizontal line first, then vertical
if (startX !== endX) {
var minX = Math.min(startX, endX);
var maxX = Math.max(startX, endX);
for (var x = minX; x <= maxX; x++) {
if (x < gridWidth && startY < gridHeight) {
grid[x][startY].isPath = true;
}
}
}
if (startY !== endY) {
var minY = Math.min(startY, endY);
var maxY = Math.max(startY, endY);
for (var y = minY; y <= maxY; y++) {
if (endX < gridWidth && y < gridHeight) {
grid[endX][y].isPath = true;
}
}
}
}
}
}
// Draw grid
function drawGrid() {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var worldPos = gridToWorld(x, y);
var cell;
if (grid[x][y].isPath) {
cell = LK.getAsset('pathCell', {
anchorX: 0.5,
anchorY: 0.5,
x: worldPos.x,
y: worldPos.y,
alpha: 0.8
});
} else {
cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
x: worldPos.x,
y: worldPos.y,
alpha: 0.3
});
}
game.addChild(cell);
}
}
}
// Create path for enemies
function createPath() {
var path = [];
for (var i = 0; i < pathPoints.length; i++) {
var point = pathPoints[i];
var worldPos = gridToWorld(point.x, point.y);
path.push(worldPos);
}
return path;
}
// Place base at the end of path
function placeBase() {
var lastPoint = pathPoints[pathPoints.length - 1];
var worldPos = gridToWorld(lastPoint.x, lastPoint.y);
var base = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
x: worldPos.x,
y: worldPos.y
});
game.addChild(base);
}
// Place money trees at strategic locations - removed automatic placement for player purchase
function placeMoneyTrees() {
// Money trees are now purchased by player, no automatic placement
}
// UI elements
var moneyText = new Text2('Money: $' + playerMoney, {
size: 40,
fill: 0xFFFFFF
});
moneyText.anchor.set(0, 0);
moneyText.x = 120;
moneyText.y = 20;
LK.gui.topLeft.addChild(moneyText);
var livesText = new Text2('Lives: ' + playerLives, {
size: 40,
fill: 0xFFFFFF
});
livesText.anchor.set(1, 0);
livesText.x = -20;
livesText.y = 20;
LK.gui.topRight.addChild(livesText);
var waveText = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 40,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.x = 0;
waveText.y = 20;
LK.gui.top.addChild(waveText);
// Tower shop with button assets positioned at bottom corner
var shopY = -300; // Position from bottom
var buttonSize = 80;
var buttonSpacing = 100;
// Archer button (Buton1)
var archerButton = LK.getAsset('Buton1', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing,
y: shopY
});
// Add direct click handler to archer button
archerButton.down = function (x, y, obj) {
selectedTowerType = 'archer';
showRangeCircle = true;
console.log('Archer button clicked directly');
// Add click animation
tween(archerButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(archerButton);
var archerText = new Text2('Archer', {
size: 25,
fill: 0xFFFFFF
});
archerText.anchor.set(0.5, 0.5);
archerText.x = buttonSpacing;
archerText.y = shopY;
LK.gui.bottomLeft.addChild(archerText);
// Ice button (Buton2)
var iceButton = LK.getAsset('Buton2', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing * 2,
y: shopY
});
// Add direct click handler to ice button
iceButton.down = function (x, y, obj) {
selectedTowerType = 'ice';
showRangeCircle = true;
console.log('Ice button clicked directly');
// Add click animation
tween(iceButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(iceButton);
var iceText = new Text2('Ice', {
size: 25,
fill: 0xFFFFFF
});
iceText.anchor.set(0.5, 0.5);
iceText.x = buttonSpacing * 2;
iceText.y = shopY;
LK.gui.bottomLeft.addChild(iceText);
// Cannon button (Buton3)
var cannonButton = LK.getAsset('Buton3', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing * 3,
y: shopY
});
// Add direct click handler to cannon button
cannonButton.down = function (x, y, obj) {
selectedTowerType = 'cannon';
showRangeCircle = true;
console.log('Cannon button clicked directly');
// Add click animation
tween(cannonButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(cannonButton);
var cannonText = new Text2('Cannon', {
size: 25,
fill: 0xFFFFFF
});
cannonText.anchor.set(0.5, 0.5);
cannonText.x = buttonSpacing * 3;
cannonText.y = shopY;
LK.gui.bottomLeft.addChild(cannonText);
// Money Tree button (fourth button)
var moneyTreeButton = LK.getAsset('Buton1', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing * 4,
y: shopY
});
moneyTreeButton.tint = 0xFFD700; // Gold color for money tree
// Add direct click handler to money tree button
moneyTreeButton.down = function (x, y, obj) {
selectedTowerType = 'moneyTree';
showRangeCircle = false; // No range circle for money trees
console.log('Money Tree button clicked directly');
// Add click animation
tween(moneyTreeButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(moneyTreeButton);
var moneyTreeText = new Text2('Money Tree', {
size: 20,
fill: 0xFFFFFF
});
moneyTreeText.anchor.set(0.5, 0.5);
moneyTreeText.x = buttonSpacing * 4;
moneyTreeText.y = shopY;
LK.gui.bottomLeft.addChild(moneyTreeText);
// Selected tower info
var selectedTowerType = 'archer';
var rangeCircle = null;
var showRangeCircle = false;
// Add status text to show selected tower type
var selectedTowerText = new Text2('Selected: Archer Tower ($50)', {
size: 30,
fill: 0x00FF00
});
selectedTowerText.anchor.set(1, 0);
selectedTowerText.x = -20;
selectedTowerText.y = 80;
LK.gui.topRight.addChild(selectedTowerText);
function showTowerRange(x, y) {
if (!showRangeCircle) return;
// Get range for selected tower type
var towerRanges = {
archer: 240,
ice: 135,
cannon: 90
};
var range = towerRanges[selectedTowerType];
// Remove existing range circle
if (rangeCircle) {
rangeCircle.destroy();
rangeCircle = null;
}
// Create new range circle
rangeCircle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: range / 40,
// Scale to match range (gridCell is 80px, so 40 is radius)
scaleY: range / 40,
alpha: 0.3,
x: x,
y: y
});
rangeCircle.tint = 0x0080FF; // Semi-transparent blue
game.addChild(rangeCircle);
}
function hideRangeCircle() {
showRangeCircle = false;
if (rangeCircle) {
rangeCircle.destroy();
rangeCircle = null;
}
}
function updateTowerSelection() {
// Stop any existing tweens on buttons
tween.stop(archerButton, {
tint: true,
scaleX: true,
scaleY: true
});
tween.stop(iceButton, {
tint: true,
scaleX: true,
scaleY: true
});
tween.stop(cannonButton, {
tint: true,
scaleX: true,
scaleY: true
});
tween.stop(moneyTreeButton, {
tint: true,
scaleX: true,
scaleY: true
});
// Reset all button colors and scales
archerButton.tint = 0xFFFFFF;
iceButton.tint = 0xFFFFFF;
cannonButton.tint = 0xFFFFFF;
moneyTreeButton.tint = 0xFFD700; // Gold base color for money tree
archerButton.scaleX = archerButton.scaleY = 1;
iceButton.scaleX = iceButton.scaleY = 1;
cannonButton.scaleX = cannonButton.scaleY = 1;
moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1;
// Highlight selected button with bright green glow and scale
if (selectedTowerType === 'archer') {
archerButton.tint = 0x00FF00; // Bright green for selected
archerButton.scaleX = archerButton.scaleY = 1.2;
// Add pulsing effect
tween(archerButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
} else if (selectedTowerType === 'ice') {
iceButton.tint = 0x00FF00; // Bright green for selected
iceButton.scaleX = iceButton.scaleY = 1.2;
// Add pulsing effect
tween(iceButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
} else if (selectedTowerType === 'cannon') {
cannonButton.tint = 0x00FF00; // Bright green for selected
cannonButton.scaleX = cannonButton.scaleY = 1.2;
// Add pulsing effect
tween(cannonButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
} else if (selectedTowerType === 'moneyTree') {
moneyTreeButton.tint = 0x00FF00; // Bright green for selected
moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1.2;
// Add pulsing effect
tween(moneyTreeButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
}
// Update status text
var towerCosts = {
archer: 50,
ice: 75,
cannon: 100,
moneyTree: 200
};
var cost = towerCosts[selectedTowerType];
var towerName = selectedTowerType === 'moneyTree' ? 'Money Tree' : selectedTowerType.charAt(0).toUpperCase() + selectedTowerType.slice(1);
var canAfford = playerMoney >= cost;
var statusColor = canAfford ? 0x00FF00 : 0xFF0000;
selectedTowerText.tint = statusColor;
var itemType = selectedTowerType === 'moneyTree' ? '' : ' Tower';
selectedTowerText.setText('Selected: ' + towerName + itemType + ' ($' + cost + ') - Click grid to place');
}
// Upgrade UI Canvas Background
var upgradeCanvas = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 6,
scaleY: 4,
alpha: 0.7
});
upgradeCanvas.tint = 0x333333; // Semi-transparent gray
upgradeCanvas.x = 0;
upgradeCanvas.y = 280;
LK.gui.center.addChild(upgradeCanvas);
// Upgrade UI Title
var upgradeTitle = new Text2('TOWER INFO', {
size: 32,
fill: 0xFFFFFF
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.x = 0;
upgradeTitle.y = 180;
LK.gui.center.addChild(upgradeTitle);
// Tower stats display
var upgradeText = new Text2('', {
size: 28,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeText.x = 0;
upgradeText.y = 250;
LK.gui.center.addChild(upgradeText);
// Upgrade button with background
var upgradeButtonBg = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.8,
alpha: 0.8
});
upgradeButtonBg.tint = 0xFFD700;
upgradeButtonBg.x = 0;
upgradeButtonBg.y = 350;
LK.gui.center.addChild(upgradeButtonBg);
var upgradeButton = new Text2('', {
size: 30,
fill: 0x000000
});
upgradeButton.anchor.set(0.5, 0.5);
upgradeButton.x = 0;
upgradeButton.y = 350;
upgradeButton.scaleX = 1;
upgradeButton.scaleY = 1;
LK.gui.center.addChild(upgradeButton);
// Sell button with background
var sellButtonBg = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.8,
alpha: 0.8
});
sellButtonBg.tint = 0xFF4444;
sellButtonBg.x = 0;
sellButtonBg.y = 410;
LK.gui.center.addChild(sellButtonBg);
var sellButton = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
sellButton.anchor.set(0.5, 0.5);
sellButton.x = 0;
sellButton.y = 410;
sellButton.scaleX = 1;
sellButton.scaleY = 1;
LK.gui.center.addChild(sellButton);
upgradeButton.down = function () {
if (showUpgradeUI && selectedTower) {
var stats = selectedTower.getStats();
if (playerMoney >= stats.upgradeCost) {
// Animate both button and background
tween(upgradeButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(upgradeButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
tween(upgradeButtonBg, {
scaleX: 2.7,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(upgradeButtonBg, {
scaleX: 2.5,
scaleY: 0.8
}, {
duration: 100
});
}
});
selectedTower.upgrade();
}
}
};
sellButton.down = function () {
if (showUpgradeUI && selectedTower) {
// Animate both button and background
tween(sellButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(sellButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
tween(sellButtonBg, {
scaleX: 2.7,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(sellButtonBg, {
scaleX: 2.5,
scaleY: 0.8
}, {
duration: 100
});
}
});
selectedTower.sell();
}
};
function updateUI() {
moneyText.setText('Money: $' + playerMoney);
livesText.setText('Lives: ' + playerLives);
waveText.setText('Wave: ' + currentWave + '/' + maxWaves);
updateTowerSelection(); // Update selection display when money changes
if (showUpgradeUI && selectedTower) {
var isMoneyTree = selectedTower.type === 'moneyTree';
var sellValue = Math.floor(selectedTower.getTotalCost() * 0.75);
// Show canvas and UI elements
upgradeCanvas.alpha = 0.7;
upgradeTitle.alpha = 1;
upgradeText.alpha = 1;
upgradeButton.alpha = isMoneyTree ? 0 : 1;
sellButton.alpha = 1;
upgradeButtonBg.alpha = isMoneyTree ? 0 : 0.8;
sellButtonBg.alpha = 0.8;
sellButton.setText('SELL $' + sellValue);
if (isMoneyTree) {
// Update text content for Money Tree
upgradeTitle.setText('MONEY TREE');
var infoText = 'Generates $' + selectedTower.moneyAmount + ' every ' + selectedTower.dropInterval / 1000 + 's.\n';
infoText += 'Max ' + selectedTower.maxMoney + ' coins can accumulate.';
upgradeText.setText(infoText);
} else {
// Update text content for normal towers
var stats = selectedTower.getStats();
var towerName = selectedTower.type.charAt(0).toUpperCase() + selectedTower.type.slice(1);
upgradeTitle.setText(towerName.toUpperCase() + ' TOWER');
upgradeText.setText('Damage: ' + stats.damage + '\nRange: ' + stats.range + '\nFire Rate: ' + Math.round(1000 / stats.fireRate * 60) + '/sec');
upgradeButton.setText('UPGRADE $' + stats.upgradeCost);
// Visual feedback for upgrade affordability
if (playerMoney >= stats.upgradeCost) {
upgradeButtonBg.tint = 0xFFD700; // Gold when affordable
upgradeButton.tint = 0x000000; // Black text on gold background
} else {
upgradeButtonBg.tint = 0x666666; // Gray when not affordable
upgradeButton.tint = 0xFFFFFF; // White text on gray background
}
}
// Sell button is always available
sellButtonBg.tint = 0xFF4444;
sellButton.tint = 0xFFFFFF;
} else {
upgradeCanvas.alpha = 0;
upgradeTitle.alpha = 0;
upgradeText.alpha = 0;
upgradeButton.alpha = 0;
sellButton.alpha = 0;
upgradeButtonBg.alpha = 0;
sellButtonBg.alpha = 0;
}
}
// Wave management
function getWaveData(waveNumber) {
if (waveNumber === 5 || waveNumber === 10) {
var waveEnemies = ['boss'];
var escortTypes = ['basic', 'fast', 'tank'];
var numEscorts = waveNumber === 5 ? 8 : 12;
for (var i = 0; i < numEscorts; i++) {
var type = escortTypes[Math.floor(Math.random() * escortTypes.length)];
waveEnemies.push(type);
}
// Shuffle the wave so boss doesn't always appear first
for (var i = waveEnemies.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = waveEnemies[i];
waveEnemies[i] = waveEnemies[j];
waveEnemies[j] = temp;
}
return waveEnemies;
}
var baseEnemies = 5 + waveNumber * 2;
var enemyTypes = ['basic'];
if (waveNumber >= 3) enemyTypes.push('fast');
if (waveNumber >= 5) enemyTypes.push('tank');
var waveEnemies = [];
for (var i = 0; i < baseEnemies; i++) {
var type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
waveEnemies.push(type);
}
return waveEnemies;
}
function startWave() {
if (currentWave > maxWaves) {
gameState = 'victory';
LK.showYouWin();
return;
}
waveInProgress = true;
var waveData = getWaveData(currentWave);
enemiesInWave = waveData.length;
enemiesSpawned = 0;
enemySpawnTimer = 0;
// Store wave data for spawning
game.currentWaveData = waveData;
}
function spawnEnemy() {
if (!waveInProgress || enemiesSpawned >= enemiesInWave) return;
var enemyType = game.currentWaveData[enemiesSpawned];
var path = createPath();
var enemy = new Enemy(enemyType, path);
enemy.x = path[0].x;
enemy.y = path[0].y;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function checkWaveComplete() {
if (waveInProgress && enemies.length === 0 && enemiesSpawned >= enemiesInWave) {
waveInProgress = false;
currentWave++;
nextWaveTimer = LK.ticks + 180; // 3 seconds delay
updateUI();
}
}
// Mouse move handler for range circle
game.move = function (x, y, obj) {
if (showRangeCircle && gameState === 'playing') {
showTowerRange(x, y);
}
};
// Game input handling
game.down = function (x, y, obj) {
if (gameState !== 'playing') return;
console.log('Click detected at raw coordinates:', x, y);
// Use the click coordinates directly as they're already in game space
var gameX = x;
var gameY = y;
console.log('Game coordinates:', gameX, gameY);
// Check tower shop buttons - convert screen coordinates to GUI coordinates
var screenPoint = {
x: x,
y: y
};
var guiPoint = LK.gui.bottomLeft.toLocal(screenPoint);
console.log('GUI click coordinates:', guiPoint.x, guiPoint.y);
// Check archer button (Buton1) - expanded click area for better touch detection
if (guiPoint.x >= 50 && guiPoint.x <= 150 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'archer';
showRangeCircle = true;
console.log('Selected tower type: archer');
// Add click animation
tween(archerButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Check ice button (Buton2) - expanded click area for better touch detection
else if (guiPoint.x >= 150 && guiPoint.x <= 250 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'ice';
showRangeCircle = true;
console.log('Selected tower type: ice');
// Add click animation
tween(iceButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Check cannon button (Buton3) - expanded click area for better touch detection
else if (guiPoint.x >= 250 && guiPoint.x <= 350 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'cannon';
showRangeCircle = true;
console.log('Selected tower type: cannon');
// Add click animation
tween(cannonButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Check money tree button - expanded click area for better touch detection
else if (guiPoint.x >= 350 && guiPoint.x <= 450 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'moneyTree';
showRangeCircle = false; // No range circle for money trees
console.log('Selected tower type: money tree');
// Add click animation
tween(moneyTreeButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Upgrade and Sell button clicks are now handled by their own .down methods.
var centerPos = LK.gui.center.toLocal({
x: x,
y: y
});
// Hide upgrade UI if clicking elsewhere
if (showUpgradeUI) {
showUpgradeUI = false;
selectedTower = null;
updateUI();
}
// Hide range circle when clicking elsewhere (cancelling purchase)
hideRangeCircle();
// Try to place tower using game coordinates
var gridPos = worldToGrid(gameX, gameY);
console.log('Grid position:', gridPos.x, gridPos.y);
console.log('Grid bounds check:', gridPos.x >= 0, gridPos.x < gridWidth, gridPos.y >= 0, gridPos.y < gridHeight);
if (gridPos.x >= 0 && gridPos.x < gridWidth && gridPos.y >= 0 && gridPos.y < gridHeight) {
console.log('Grid cell occupied:', grid[gridPos.x][gridPos.y].occupied);
console.log('Grid cell is path:', grid[gridPos.x][gridPos.y].isPath);
if (!grid[gridPos.x][gridPos.y].occupied && !grid[gridPos.x][gridPos.y].isPath) {
var towerCosts = {
archer: 50,
ice: 75,
cannon: 100,
moneyTree: 200
};
var cost = towerCosts[selectedTowerType];
console.log('Tower cost:', cost, 'Player money:', playerMoney);
if (playerMoney >= cost) {
console.log('Placing', selectedTowerType, 'at grid:', gridPos.x, gridPos.y);
var worldPos = gridToWorld(gridPos.x, gridPos.y);
console.log('World position:', worldPos.x, worldPos.y);
if (selectedTowerType === 'moneyTree') {
// Place money tree
var tree = new MoneyTree(gridPos.x, gridPos.y);
tree.x = worldPos.x;
tree.y = worldPos.y;
// Add placement animation
tree.scaleX = 0;
tree.scaleY = 0;
tween(tree, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
moneyTrees.push(tree);
game.addChild(tree);
} else {
// Place tower
var tower = new Tower(selectedTowerType, gridPos.x, gridPos.y);
tower.x = worldPos.x;
tower.y = worldPos.y;
// Add placement animation
tower.scaleX = 0;
tower.scaleY = 0;
tween(tower, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
towers.push(tower);
game.addChild(tower);
}
grid[gridPos.x][gridPos.y].occupied = true;
playerMoney -= cost;
hideRangeCircle(); // Hide range circle after placing tower
updateUI();
console.log(selectedTowerType, 'placed successfully!');
} else {
console.log('Not enough money to place', selectedTowerType);
}
} else {
console.log('Grid cell is occupied or on path');
}
} else {
console.log('Click outside grid bounds');
}
};
// Initialize game
initializeGrid();
drawGrid();
placeBase();
placeMoneyTrees();
updateUI();
updateTowerSelection();
startWave();
// Main game loop
game.update = function () {
if (gameState !== 'playing') return;
// Update all towers (this enables shooting!)
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all bullets (this enables bullet movement and collision detection!)
for (var i = 0; i < bullets.length; i++) {
bullets[i].update();
}
// Update all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update all money trees
for (var i = 0; i < moneyTrees.length; i++) {
moneyTrees[i].update();
}
// Clean up collected money coins
for (var i = moneyCoins.length - 1; i >= 0; i--) {
if (moneyCoins[i].collected) {
moneyCoins.splice(i, 1);
}
}
// Spawn enemies
if (waveInProgress && LK.ticks % 60 === 0) {
// Every second
spawnEnemy();
}
// Start next wave
if (!waveInProgress && LK.ticks >= nextWaveTimer) {
startWave();
}
// Clean up destroyed enemies
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].destroyed) {
enemies.splice(i, 1);
}
}
// Clean up destroyed bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].destroyed) {
bullets.splice(i, 1);
}
}
checkWaveComplete();
};
bow with arrow. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
ice tower. In-Game asset. 2d. High contrast. No shadows
medieval soldier. In-Game asset. 2d. High contrast. No shadows
cannon. In-Game asset. 2d. High contrast. No shadows
knight with horse. In-Game asset. 2d. High contrast. No shadows
cannonball. In-Game asset. 2d. High contrast. No shadows
arrow. In-Game asset. 2d. High contrast. No shadows
ice bullet. In-Game asset. 2d. High contrast. No shadows
build a tower from a bird's eye view. In-Game asset. 2d. High contrast. No shadows
giant. In-Game asset. 2d. High contrast. No shadows
bird's eye view of grass. In-Game asset. 2d. High contrast. No shadows
soil bird's eye view. In-Game asset. 2d. High contrast. No shadows
money. In-Game asset. 2d. High contrast. No shadows
money tree. In-Game asset. 2d. High contrast. No shadows
giand mosnter. In-Game asset. 2d. High contrast. No shadows
bad wizard. In-Game asset. 2d. High contrast. No shadows