User prompt
double the heal of basicenemy fastenemy and tank enemy
User prompt
make bos1 heal 3000 and bos2 heal 10000 and Other enemies come with the boss
User prompt
add boss fight wave 5 and wave 10 and use bos1 and bos2 assets for that
User prompt
I want a Boss For wave 5 and wave 10
User prompt
double the update fee
User prompt
i want add sell canwas for money tree
User prompt
money tree give 25 coin
User prompt
Increase the money earned by updating the money tree
User prompt
A maximum of 2 money can be accumulated in the money tree.
User prompt
The money tree should not grow spontaneously and I should buy it with money and the price should be 200 money.
User prompt
Integrate a 'Money Tree' mechanic into the game that periodically drops money. This tree should be an entity that the player can either place at a specific location or find naturally occurring within the game world ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Place the sell and update buttons a little lower
User prompt
when i clicked tower i dont want to see level at canvas
User prompt
Resolve the overlap issue where the 'Level' text is currently merging with or being obscured by other text or UI elements. Ensure the 'Level' text is always clearly separated and legible from all other UI components
User prompt
Design an improved Canvas for both the 'Sell' and 'Update' menus in the game. This Canvas should feature a subtle, semi-transparent gray background to distinguish it from the game world while maintaining visual consistency. For the 'Sell' menu, include clear labels for items, their selling price, and a prominent 'Sell' button. The 'Update' menu should display the current stats of the selected item or tower, available upgrades, their costs, and an 'Upgrade' button. Ensure the Canvas elements (buttons, text fields, item displays) are well-organized, easy to read, and intuitively navigable. Prioritize a clean, minimalist aesthetic to avoid clutter, and ensure the Canvas scales appropriately across different screen resolutions without distorting elements.
User prompt
ice Tower range add -65
User prompt
make ice ranger double
User prompt
make double archer range
User prompt
When a tower purchase button is clicked, make a semi-transparent blue circle appear on the game field. This circle should visually represent the range of the tower being purchased. The circle should be centered at the potential placement location of the tower and have a radius equal to the tower's range. As the player moves the mouse cursor or changes the potential tower placement location, this circle should dynamically update to reflect the new position and range. The circle must disappear when the tower is placed or when the purchase action is cancelled
User prompt
Currently, when I click on a tower, an upgrade/sell UI appears with relevant text and buttons. However, I can't interact with this UI (e.g., clicking the "Upgrade" or "Sell" text does nothing). Please make the upgrade and sell UI elements interactive. The "Upgrade" button should call the selected tower’s upgrade() method when clicked, and the "Sell" button should sell the tower (remove it, refund money, update the grid, etc.). Ensure proper click detection and that only one UI is active at a time. Use the existing game.down logic or add event listeners to make these UI elements clickable.
User prompt
Currently, when I click on a tower, an upgrade/sell UI appears with relevant text and buttons. However, I can't interact with this UI (e.g., clicking the "Upgrade" or "Sell" text does nothing). Please make the upgrade and sell UI elements interactive. The "Upgrade" button should call the selected tower’s upgrade() method when clicked, and the "Sell" button should sell the tower (remove it, refund money, update the grid, etc.). Ensure proper click detection and that only one UI is active at a time. Use the existing game.down logic or add event listeners to make these UI elements clickable.
User prompt
Add a new button labeled ButonSell to the game's UI. When a player selects a tower and then clicks this button, the selected tower should be removed from the game and the player should receive back 75% of the total cost spent on building and upgrading that tower. Please make sure the sell button is only visible when a tower is selected, and update the UI accordingly. The grid cell where the sold tower was placed should be marked as unoccupied. Also, make sure to remove the tower from the towers array and destroy its visual representation from the game. Add the sell value calculation in the Tower class, and ensure money is correctly refunded to the player when selling. Use the existing GUI system for positioning the button, preferably below the upgrade button.
User prompt
after wave 5 enemies' health is doubled
User prompt
Reduces enemies' health by 1/4
User prompt
I’m creating a 2D tower defense game. Currently, my level 1 archer tower kills basic enemies with a single shot, which I don’t want. The basic enemy has 120 HP, and the level 1 archer tower deals 15 damage per shot (as it should). But somehow the enemy dies in one hit. Here's what I need: – Identify why the archer tower might be killing the enemy in one hit – Suggest how to fix it so the damage is applied correctly (i.e., not instantly killing enemies unless their HP is actually below the damage) – Ensure this fix works consistently with other towers like ice and cannon Here's a simplified version of my code for the Bullet, Enemy, and Tower classes (optional: paste code here if needed). Can you analyze this and help me fix the issue?
/**** * 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; // Enemy stats based on type var stats = { basic: { health: 120, speed: 1.5, reward: 10 }, fast: { health: 80, speed: 2.5, reward: 15 }, tank: { health: 320, speed: 1, reward: 25 } }; var enemyStats = stats[self.type]; self.health = enemyStats.health; self.maxHealth = enemyStats.health; self.speed = enemyStats.speed; self.baseSpeed = enemyStats.speed; self.reward = enemyStats.reward; 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 { 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 () { // 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 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: 120 + self.level * 20, fireRate: 800 - self.level * 100, // ms between shots cost: 50, upgradeCost: self.level * 30 }, ice: { damage: 8 * self.level, range: 100 + self.level * 15, fireRate: 1200 - self.level * 150, cost: 75, upgradeCost: self.level * 45 }, cannon: { damage: 40 * self.level, range: 90 + self.level * 10, fireRate: 1500 - self.level * 200, cost: 100, upgradeCost: self.level * 60 } }; 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.upgrade = function () { var stats = self.getStats(); if (playerMoney >= stats.upgradeCost) { playerMoney -= stats.upgradeCost; self.level++; self.levelText.setText(self.level.toString()); updateUI(); } }; 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 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); } // 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'; 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'; 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'; 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); // Selected tower info var selectedTowerType = 'archer'; // 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 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 }); // Reset all button colors and scales archerButton.tint = 0xFFFFFF; iceButton.tint = 0xFFFFFF; cannonButton.tint = 0xFFFFFF; archerButton.scaleX = archerButton.scaleY = 1; iceButton.scaleX = iceButton.scaleY = 1; cannonButton.scaleX = cannonButton.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 }); } // Update status text var towerCosts = { archer: 50, ice: 75, cannon: 100 }; var cost = towerCosts[selectedTowerType]; var towerName = selectedTowerType.charAt(0).toUpperCase() + selectedTowerType.slice(1); var canAfford = playerMoney >= cost; var statusColor = canAfford ? 0x00FF00 : 0xFF0000; selectedTowerText.tint = statusColor; selectedTowerText.setText('Selected: ' + towerName + ' Tower ($' + cost + ') - Click grid to place'); } // Upgrade UI var upgradeText = new Text2('', { size: 30, fill: 0xFFFFFF }); upgradeText.anchor.set(0.5, 0); upgradeText.x = 0; upgradeText.y = 200; LK.gui.center.addChild(upgradeText); var upgradeButton = new Text2('', { size: 35, fill: 0xFFD700 }); upgradeButton.anchor.set(0.5, 0); upgradeButton.x = 0; upgradeButton.y = 250; LK.gui.center.addChild(upgradeButton); 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 stats = selectedTower.getStats(); upgradeText.setText('Tower Level: ' + selectedTower.level + '\nDamage: ' + stats.damage + '\nRange: ' + stats.range); upgradeButton.setText('Upgrade $' + stats.upgradeCost); upgradeText.alpha = 1; upgradeButton.alpha = 1; } else { upgradeText.alpha = 0; upgradeButton.alpha = 0; } } // Wave management function getWaveData(waveNumber) { 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(); } } // 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'; 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'; 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'; 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 upgrade button var centerPos = LK.gui.center.toLocal({ x: x, y: y }); if (showUpgradeUI && selectedTower && centerPos.x >= -100 && centerPos.x <= 100 && centerPos.y >= 250 && centerPos.y <= 290) { selectedTower.upgrade(); return; } // Hide upgrade UI if clicking elsewhere if (showUpgradeUI) { showUpgradeUI = false; selectedTower = null; updateUI(); } // 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 }; var cost = towerCosts[selectedTowerType]; console.log('Tower cost:', cost, 'Player money:', playerMoney); if (playerMoney >= cost) { console.log('Placing tower at grid:', gridPos.x, gridPos.y); var worldPos = gridToWorld(gridPos.x, gridPos.y); console.log('World position for tower:', worldPos.x, worldPos.y); 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; updateUI(); console.log('Tower placed successfully!'); } else { console.log('Not enough money to place tower'); } } else { console.log('Grid cell is occupied or on path'); } } else { console.log('Click outside grid bounds'); } }; // Initialize game initializeGrid(); drawGrid(); placeBase(); 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(); } // 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;
// Enemy stats based on type
var stats = {
basic: {
health: 120,
speed: 1.5,
reward: 10
},
fast: {
health: 80,
speed: 2.5,
reward: 15
},
tank: {
health: 320,
speed: 1,
reward: 25
}
};
var enemyStats = stats[self.type];
self.health = enemyStats.health;
self.maxHealth = enemyStats.health;
self.speed = enemyStats.speed;
self.baseSpeed = enemyStats.speed;
self.reward = enemyStats.reward;
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 {
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 () {
// 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 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: 120 + self.level * 20,
fireRate: 800 - self.level * 100,
// ms between shots
cost: 50,
upgradeCost: self.level * 30
},
ice: {
damage: 8 * self.level,
range: 100 + self.level * 15,
fireRate: 1200 - self.level * 150,
cost: 75,
upgradeCost: self.level * 45
},
cannon: {
damage: 40 * self.level,
range: 90 + self.level * 10,
fireRate: 1500 - self.level * 200,
cost: 100,
upgradeCost: self.level * 60
}
};
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.upgrade = function () {
var stats = self.getStats();
if (playerMoney >= stats.upgradeCost) {
playerMoney -= stats.upgradeCost;
self.level++;
self.levelText.setText(self.level.toString());
updateUI();
}
};
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 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);
}
// 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';
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';
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';
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);
// Selected tower info
var selectedTowerType = 'archer';
// 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 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
});
// Reset all button colors and scales
archerButton.tint = 0xFFFFFF;
iceButton.tint = 0xFFFFFF;
cannonButton.tint = 0xFFFFFF;
archerButton.scaleX = archerButton.scaleY = 1;
iceButton.scaleX = iceButton.scaleY = 1;
cannonButton.scaleX = cannonButton.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
});
}
// Update status text
var towerCosts = {
archer: 50,
ice: 75,
cannon: 100
};
var cost = towerCosts[selectedTowerType];
var towerName = selectedTowerType.charAt(0).toUpperCase() + selectedTowerType.slice(1);
var canAfford = playerMoney >= cost;
var statusColor = canAfford ? 0x00FF00 : 0xFF0000;
selectedTowerText.tint = statusColor;
selectedTowerText.setText('Selected: ' + towerName + ' Tower ($' + cost + ') - Click grid to place');
}
// Upgrade UI
var upgradeText = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0);
upgradeText.x = 0;
upgradeText.y = 200;
LK.gui.center.addChild(upgradeText);
var upgradeButton = new Text2('', {
size: 35,
fill: 0xFFD700
});
upgradeButton.anchor.set(0.5, 0);
upgradeButton.x = 0;
upgradeButton.y = 250;
LK.gui.center.addChild(upgradeButton);
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 stats = selectedTower.getStats();
upgradeText.setText('Tower Level: ' + selectedTower.level + '\nDamage: ' + stats.damage + '\nRange: ' + stats.range);
upgradeButton.setText('Upgrade $' + stats.upgradeCost);
upgradeText.alpha = 1;
upgradeButton.alpha = 1;
} else {
upgradeText.alpha = 0;
upgradeButton.alpha = 0;
}
}
// Wave management
function getWaveData(waveNumber) {
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();
}
}
// 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';
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';
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';
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 upgrade button
var centerPos = LK.gui.center.toLocal({
x: x,
y: y
});
if (showUpgradeUI && selectedTower && centerPos.x >= -100 && centerPos.x <= 100 && centerPos.y >= 250 && centerPos.y <= 290) {
selectedTower.upgrade();
return;
}
// Hide upgrade UI if clicking elsewhere
if (showUpgradeUI) {
showUpgradeUI = false;
selectedTower = null;
updateUI();
}
// 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
};
var cost = towerCosts[selectedTowerType];
console.log('Tower cost:', cost, 'Player money:', playerMoney);
if (playerMoney >= cost) {
console.log('Placing tower at grid:', gridPos.x, gridPos.y);
var worldPos = gridToWorld(gridPos.x, gridPos.y);
console.log('World position for tower:', worldPos.x, worldPos.y);
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;
updateUI();
console.log('Tower placed successfully!');
} else {
console.log('Not enough money to place tower');
}
} else {
console.log('Grid cell is occupied or on path');
}
} else {
console.log('Click outside grid bounds');
}
};
// Initialize game
initializeGrid();
drawGrid();
placeBase();
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();
}
// 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