User prompt
Increase the amount of gold being rewarded by .5 per wave (rounded down)
User prompt
Pressing the next wave button should just spawn the wave we are getting to next. Currently it seems to spawn two waves. E.g. if we are currently between wave 1 and 2 and you press next wave, we should spawn wave 2 but not wave 3
User prompt
Pressing the next wave button should just spawn the wave we are getting to next. Currently it seems to spawn two waves
User prompt
The distance for sniper towers start large but seems to shrink while upgrading. Please update this tower such that it makes sense.
User prompt
We have a bunch of diffrent types of towers. Make sure they actually do the effects that their names indicates
User prompt
For the rows of towers at the bottom from where we can start a build of a new tower, make these entries have 50% opacity for towers we can't currently afford. This should be live updated based on how much gold I have
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'self.update = function () { __$(1170); var previousHasEnoughGold = self.hasEnoughGold; __$(1171); self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed __$(1172); if (previousHasEnoughGold !== self.hasEnoughGold) { __$(1173); self.updateAppearance(); } }')' in or related to this line: 'self.update = function () {' Line Number: 1738
User prompt
If you can't afford a tower, the current placement preview color should be red (Similar to blocking and occupied location). However if you earn the gold needed to place the tower while hovering a tower on a build place, the color should update accordingly. Please update the drag preview code accordingly.
User prompt
Please fix the bug: 'TypeError: self.updatePlacementStatus is not a function. (In 'self.updatePlacementStatus()', 'self.updatePlacementStatus' is undefined)' in or related to this line: 'self.updatePlacementStatus();' Line Number: 79
User prompt
The code has a large amount of unnecessary comments, clean this up
User prompt
If you can't afford a tower, the current preview color should be red. However if you earn the gold needed to place the tower while hovering a tower on a build place, the color should update accordingly.
Code edit (3 edits merged)
Please save this source code
User prompt
Show a + gold indicator when you kill an enemy showing you how much gold you earned for killing the given enemy. This should animate in and out where the enemy was killed ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The sell dialogue should correctly indicate that you get full value back from towers when selling before pressing start. To ensure issues like this never happen again, create a convenience method that is used for displaying and calculating costs everywhere
User prompt
If we sell a tower before we have started the game, you should get the full value of the tower back
Code edit (1 edits merged)
Please save this source code
User prompt
indicator and indicator2 currently forms the top and bottom of what is meant to be a square border with a transparent middle. Please update this to have walls as well
User prompt
Create a container for the next wave button and move it down by 20 px
Code edit (1 edits merged)
Please save this source code
User prompt
Move the next wave button to bottom right corner. Hide it until we press start game
User prompt
I think enemies spawn to far away from the entry point now, E.g. they take to long to appear on screen after they have been spawned
Code edit (1 edits merged)
Please save this source code
User prompt
You should be able to press the next wave button at anytime to spawn the next wave
User prompt
Add a next wave button that allows me to spawn a wave whenever I want. (Except before the game has been started by pressing the start game button)
User prompt
Waves should be able to spawn concurrently. E.g. it should be possible for multiple waves to be active at the same time. Update the game to support this. As an example as soon as we hit wave 2 in the wave indicator it should spawn regardless of the state of previous waves
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); self.update = function () { if (self.visible) { var towerCost = getTowerCost(self.towerType); if (gold >= towerCost && !self.canPlace) { self.updatePlacementStatus(); } } }; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = true; var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { cellGraphics.tint = 0x88 - tint << 8 | tint; } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = .5; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; } break; } case 1: { self.removeArrows(); cellGraphics.tint = 0xaaaaaa; numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speed = .05; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: angle }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 11 - 3 && i <= 11 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j > 3 && j <= gridHeight - 4) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; if (node.up && node.left && node.up.type != 1 && node.left.type != 1) { processNode(node.upLeft, targetScore, node); } if (node.up && node.right && node.up.type != 1 && node.right.type != 1) { processNode(node.upRight, targetScore, node); } if (node.down && node.right && node.down.type != 1 && node.right.type != 1) { processNode(node.downRight, targetScore, node); } if (node.down && node.left && node.down.type != 1 && node.left.type != 1) { processNode(node.downLeft, targetScore, node); } targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked"); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } waveTimer = nextWaveTime; if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; waveInProgress = true; waveSpawned = false; } var notification = game.addChild(new Notification("Wave " + currentWave + " activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); switch (self.towerType) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'splash': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0xAAAAAA; } var towerCost = getTowerCost(self.towerType); var costLabel = new Text2('Cost: ' + towerCost, { size: 40, fill: 0xFFFFFF, weight: 800 }); costLabel.anchor.set(0.5, 0); costLabel.y = baseGraphics.height / 2 + 5; self.addChild(costLabel); return self; }); var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 8; break; case 'splash': self.fireRate = 75; self.damage = 15; self.range = 3 * CELL_SIZE; self.bulletSpeed = 4; break; case 'slow': self.fireRate = 50; self.damage = 8; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 5; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; break; } var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); switch (self.id) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'splash': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0xAAAAAA; } var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; if (i < self.level) { towerLevelIndicator.tint = 0xFFFFFF; } else { switch (self.id) { case 'rapid': towerLevelIndicator.tint = 0x00AAFF; break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; break; case 'splash': towerLevelIndicator.tint = 0x33CC00; break; case 'slow': towerLevelIndicator.tint = 0x9900FF; break; case 'poison': towerLevelIndicator.tint = 0x00FFAA; break; default: towerLevelIndicator.tint = 0xAAAAAA; } } } }; self.updateLevelIndicators(); self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.range / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; for (var i = 1; i < self.level; i++) { totalInvestment += 10 + i * 5; } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { var upgradeCost = 10 + self.level * 5; if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; self.range = (3 + self.level * 0.5) * CELL_SIZE; self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; if (enemy.cellX === cell.x && enemy.cellY === cell.y) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } }; self.down = function (x, y, obj) { var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.range * 2; rangeGraphics.alpha = 0.3; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.range; }; self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.updateAppearance = function () { switch (self.towerType) { case 'rapid': rangeGraphics.width = rangeGraphics.height = 2.5 * CELL_SIZE * 2; previewGraphics.tint = 0x00AAFF; break; case 'sniper': rangeGraphics.width = rangeGraphics.height = 5 * CELL_SIZE * 2; previewGraphics.tint = 0xFF5500; break; case 'splash': rangeGraphics.width = rangeGraphics.height = 3 * CELL_SIZE * 2; previewGraphics.tint = 0x33CC00; break; case 'slow': rangeGraphics.width = rangeGraphics.height = 3.5 * CELL_SIZE * 2; previewGraphics.tint = 0x9900FF; break; case 'poison': rangeGraphics.width = rangeGraphics.height = 3.2 * CELL_SIZE * 2; previewGraphics.tint = 0x00FFAA; break; default: rangeGraphics.width = rangeGraphics.height = 3 * CELL_SIZE * 2; previewGraphics.tint = 0xAAAAAA; } if (!self.canPlace) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 0) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; var upgradeCost = isMaxLevel ? 0 : 10 + self.tower.level * 5; buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { upgradeCost = 10 + self.tower.level * 5; statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.range * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.range * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } var currentUpgradeCost = 10 + self.tower.level * 5; var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.indicatorWidth = 0; var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; if (i % 10 === 0) { block.tint = 0xFF5500; } else if (i % 5 === 0) { block.tint = 0xFFFF00; } else { block.tint = 0xAAAAAA; } var waveNum = new Text2((i + 1).toString(), { size: 40, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(0.5, 0.5); marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.tint = 0x00AA00; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var notification = game.addChild(new Notification("Wave " + currentWave + " incoming with 20 enemies!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ var isHidingUpgradeMenu = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var gold = 80; var lives = 20; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 3000; var sourceTower = null; var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); var enemyLayer = new Container(); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); game.addChild(debugLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'splash': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); towerPreview.snapToGrid(x, y); break; } } }; game.move = function (x, y, obj) { if (isDragging) { towerPreview.snapToGrid(x, y); } }; game.up = function (x, y, obj) { var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison']; var sourceTowers = []; var towerSpacing = 250; var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = startX + i * towerSpacing; tower.y = towerY; towerLayer.addChild(tower); sourceTowers.push(tower); } sourceTower = null; enemiesToSpawn = 10; game.update = function () { if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; for (var i = 0; i < enemiesToSpawn; i++) { var target = grid.spawns[Math.random() * grid.spawns.length >> 0]; var enemy = enemyLayer.addChild(new Enemy()); enemy.maxHealth = 100 + currentWave * 10; enemy.health = enemy.maxHealth; enemy.speed = 0.02 + currentWave * 0.002; var randomOffsetX = Math.random() * 2048; var randomOffsetY = -100 - Math.random() * 200; enemy.cellX = target.x; enemy.cellY = target.y; enemy.currentCellX = (randomOffsetX - grid.x) / CELL_SIZE; enemy.currentCellY = (randomOffsetY - grid.y) / CELL_SIZE; enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } var goldEarned = 1; var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); score += 5; updateUI(); enemyLayer.removeChild(enemy); enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { enemyLayer.removeChild(enemy); enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } for (var i = 0; i < towers.length; i++) { towers[i].update(); } for (var i = bullets.length - 1; i >= 0; i--) { bullets[i].update(); if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (waveIndicator) { waveIndicator.update(); } if (nextWaveButton) { nextWaveButton.update(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -5,76 +5,66 @@
/****
* Classes
****/
-// Enemy class
-// Bullet class
var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
var self = Container.call(this);
- // Bullet properties
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.x = startX;
self.y = startY;
- // Visual representation of bullet
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
- // Update method - called every frame
self.update = function () {
- // If target is gone, destroy this bullet
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
- // Move toward target
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // If we hit the target
if (distance < self.speed) {
- // Deal damage to enemy
self.targetEnemy.health -= self.damage;
- // Update health bar visual
if (self.targetEnemy.health <= 0) {
- // Enemy is defeated
self.targetEnemy.health = 0;
} else {
- // Update health bar width based on remaining health percentage
self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70;
}
- // Remove this bullet
self.destroy();
} else {
- // Move toward target
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
-// DebugCell class
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.tint = Math.random() * 0xffffff;
- // Add a debug arrow to the debug cells
var debugArrows = [];
- // Add a number label to the debug cells
var numberLabel = new Text2('0', {
size: 30,
fill: 0xFFFFFF,
weight: 800
});
numberLabel.anchor.set(.5, .5);
self.addChild(numberLabel);
- self.update = function () {};
+ self.update = function () {
+ if (self.visible) {
+ var towerCost = getTowerCost(self.towerType);
+ if (gold >= towerCost && !self.canPlace) {
+ self.updatePlacementStatus();
+ }
+ }
+ };
self.down = function () {
return;
if (self.cell.type == 0 || self.cell.type == 1) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
@@ -105,13 +95,12 @@
return;
}
numberLabel.visible = true;
var tint = Math.floor(data.score / maxScore * 0x88);
- // Check if we have a selected tower and this cell is in its range
var towerInRangeHighlight = false;
if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) {
towerInRangeHighlight = true;
- cellGraphics.tint = 0x0088ff; // Highlight cells in range of selected tower
+ cellGraphics.tint = 0x0088ff;
} else {
cellGraphics.tint = 0x88 - tint << 8 | tint;
}
while (debugArrows.length > data.targets.length) {
@@ -128,12 +117,10 @@
anchorY: 0.5
});
debugArrows[a].alpha = .5;
self.addChildAt(debugArrows[a], 1);
- // debugArrows =
}
debugArrows[a].rotation = angle;
- // debugArrow.rotation = angle;
}
break;
}
case 1:
@@ -165,13 +152,12 @@
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
- self.maxHealth = 100; // Starting max health
- self.health = self.maxHealth; // Current health
- self.bulletsTargetingThis = []; // Track bullets targeting this enemy
- self.waveNumber = currentWave; // Track which wave this enemy belongs to
- // Add health bar to the enemy
+ self.maxHealth = 100;
+ self.health = self.maxHealth;
+ self.bulletsTargetingThis = [];
+ self.waveNumber = currentWave;
var healthBarOutline = self.attachAsset('healthBarOutline', {
anchorX: 0,
anchorY: 0.5
});
@@ -182,43 +168,33 @@
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
- healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; // Position the health bar above the enemy
+ healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10;
healthBarOutline.x = -healthBarOutline.width / 2;
healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5;
healthBar.tint = 0x00ff00;
healthBarBG.tint = 0xff0000;
self.healthBar = healthBar;
- // Update method - called every frame
self.update = function () {
- // Check if health has depleted
if (self.health <= 0) {
self.health = 0;
self.healthBar.width = 0;
}
- // Calculate direction of travel if we have a current target
if (self.currentTarget) {
var ox = self.currentTarget.x - self.currentCellX;
var oy = self.currentTarget.y - self.currentCellY;
if (ox !== 0 || oy !== 0) {
- // Calculate the angle of movement
var angle = Math.atan2(oy, ox);
- // Smoothly rotate the enemy graphics using tween
- // First, store the current rotation
if (enemyGraphics.targetRotation === undefined) {
enemyGraphics.targetRotation = angle;
enemyGraphics.rotation = angle;
} else {
- // Only update if the angle has changed significantly
if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) {
- // Stop any existing rotation tweens
tween.stop(enemyGraphics, {
rotation: true
});
- // Set the new target rotation
enemyGraphics.targetRotation = angle;
- // Tween to the new rotation
tween(enemyGraphics, {
rotation: angle
}, {
duration: 250,
@@ -227,61 +203,52 @@
}
}
}
}
- // Update health bar position relative to the enemy
healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10;
};
return self;
});
-// GoldIndicator class
var GoldIndicator = Container.expand(function (value, x, y) {
var self = Container.call(this);
- // Create text showing gold earned
var goldText = new Text2("+" + value, {
size: 45,
fill: 0xFFD700,
- // Gold color
weight: 800
});
goldText.anchor.set(0.5, 0.5);
self.addChild(goldText);
- // Position at the enemy's death location
self.x = x;
self.y = y;
- // Set up initial state for animation
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
- // Animate in
tween(self, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
- y: y - 40 // Float upward slightly
+ y: y - 40
}, {
- duration: 100,
+ duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
- // Hold briefly, then animate out
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
- y: y - 80 // Continue floating upward
+ y: y - 80
}, {
duration: 600,
easing: tween.easeIn,
- delay: 1000,
+ delay: 800,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
-//Class for the gr0x412e2eles all logic related to grid movement and interaction
var Grid = Container.expand(function (gridWidth, gridHeight) {
var self = Container.call(this);
self.cells = [];
self.spawns = [];
@@ -291,9 +258,9 @@
for (var j = 0; j < gridHeight; j++) {
self.cells[i][j] = {
score: 0,
pathId: 0,
- towersInRange: [] // Cache for towers in range
+ towersInRange: []
};
}
}
/*
@@ -304,9 +271,8 @@
3: Goal
*/
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
- // Set the type to 1 (wall) if the cell is on the edge of the grid, otherwise set it to 0 (ground)
var cell = self.cells[i][j];
var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0;
if (i > 11 - 3 && i <= 11 + 3) {
if (j === 0) {
@@ -323,14 +289,8 @@
}
cell.type = cellType;
cell.x = i;
cell.y = j;
- /*if (cell.type == 0) {
- if (Math.random() > .5) {
- cell.type = 1;
- }
- }*/
- //Cache neighbours for speed
cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
cell.up = self.cells[i - 1] && self.cells[i - 1][j];
cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
cell.left = self.cells[i][j - 1];
@@ -385,9 +345,8 @@
toProcess = [];
for (var a = 0; a < nodes.length; a++) {
var node = nodes[a];
var targetScore = node.score + 14142;
- // Check for diagonal movement and ensure both adjacent cells are not walls
if (node.up && node.left && node.up.type != 1 && node.left.type != 1) {
processNode(node.upLeft, targetScore, node);
}
if (node.up && node.right && node.up.type != 1 && node.right.type != 1) {
@@ -447,9 +406,8 @@
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
- //Allows enemies to change direction right away.
if (cell.score < enemy.currentTarget.score) {
enemy.currentTarget = cell;
}
var ox = enemy.currentTarget.x - enemy.currentCellX;
@@ -468,66 +426,55 @@
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
};
});
-// NextWaveButton class
var NextWaveButton = Container.expand(function () {
var self = Container.call(this);
- // Create button background
var buttonBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 300;
buttonBackground.height = 100;
buttonBackground.tint = 0x0088FF;
- // Add button text
var buttonText = new Text2("Next Wave", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
- // Button state variables
self.enabled = false;
- self.visible = false; // Hide the button initially
- // Update method
+ self.visible = false;
self.update = function () {
- // Show and enable button only after game has started
if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) {
self.enabled = true;
- self.visible = true; // Show the button
+ self.visible = true;
buttonBackground.tint = 0x0088FF;
self.alpha = 1;
} else {
self.enabled = false;
- self.visible = false; // Hide the button
+ self.visible = false;
buttonBackground.tint = 0x888888;
self.alpha = 0.7;
}
};
- // Click handler
self.down = function () {
if (!self.enabled) {
return;
}
- // Spawn next wave immediately
waveTimer = nextWaveTime;
- // Force wave spawn regardless of other wave status
if (waveIndicator.gameStarted && currentWave < totalWaves) {
currentWave++;
waveInProgress = true;
waveSpawned = false;
}
- // Show notification
var notification = game.addChild(new Notification("Wave " + currentWave + " activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
};
return self;
});
-// Notification class
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
@@ -541,9 +488,9 @@
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
- var fadeOutTime = 120; // 2 seconds at 60FPS
+ var fadeOutTime = 120;
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
@@ -552,41 +499,35 @@
}
};
return self;
});
-// SourceTower class
var SourceTower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'default';
- // Create the tower base
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
- // Customize visuals based on tower type
switch (self.towerType) {
case 'rapid':
- baseGraphics.tint = 0x00AAFF; // Blue
+ baseGraphics.tint = 0x00AAFF;
break;
case 'sniper':
- baseGraphics.tint = 0xFF5500; // Orange
+ baseGraphics.tint = 0xFF5500;
break;
case 'splash':
- baseGraphics.tint = 0x33CC00; // Bright Green
+ baseGraphics.tint = 0x33CC00;
break;
case 'slow':
- baseGraphics.tint = 0x9900FF; // Deep Purple
+ baseGraphics.tint = 0x9900FF;
break;
case 'poison':
- baseGraphics.tint = 0x00FFAA; // Aqua
+ baseGraphics.tint = 0x00FFAA;
break;
default:
baseGraphics.tint = 0xAAAAAA;
- // Gray
}
- // Calculate cost based on tower type
var towerCost = getTowerCost(self.towerType);
- // Add a label to indicate tower cost
var costLabel = new Text2('Cost: ' + towerCost, {
size: 40,
fill: 0xFFFFFF,
weight: 800
@@ -595,188 +536,161 @@
costLabel.y = baseGraphics.height / 2 + 5;
self.addChild(costLabel);
return self;
});
-// Tower class that can be placed on the grid
var Tower = Container.expand(function (id) {
var self = Container.call(this);
- // Tower attributes
self.id = id || 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
- self.range = 3 * CELL_SIZE; // initial range (3 grid cells)
- self.cellsInRange = []; // Cache for cells in range
- self.fireRate = 60; // frames between shots
+ self.range = 3 * CELL_SIZE;
+ self.cellsInRange = [];
+ self.fireRate = 60;
self.bulletSpeed = 5;
self.damage = 10;
self.lastFired = 0;
self.targetEnemy = null;
- // Set tower attributes based on type
switch (self.id) {
case 'rapid':
- self.fireRate = 30; // Faster firing rate
- self.damage = 5; // Lower damage
- self.range = 2.5 * CELL_SIZE; // Slightly shorter range
- self.bulletSpeed = 7; // Faster bullets
+ self.fireRate = 30;
+ self.damage = 5;
+ self.range = 2.5 * CELL_SIZE;
+ self.bulletSpeed = 7;
break;
case 'sniper':
- self.fireRate = 90; // Slower firing rate
- self.damage = 25; // Higher damage
- self.range = 5 * CELL_SIZE; // Longer range
- self.bulletSpeed = 8; // Fast bullets
+ self.fireRate = 90;
+ self.damage = 25;
+ self.range = 5 * CELL_SIZE;
+ self.bulletSpeed = 8;
break;
case 'splash':
- self.fireRate = 75; // Medium firing rate
- self.damage = 15; // Medium damage
- self.range = 3 * CELL_SIZE; // Medium range
- self.bulletSpeed = 4; // Slower bullets
+ self.fireRate = 75;
+ self.damage = 15;
+ self.range = 3 * CELL_SIZE;
+ self.bulletSpeed = 4;
break;
case 'slow':
- self.fireRate = 50; // Medium firing rate
- self.damage = 8; // Lower damage
- self.range = 3.5 * CELL_SIZE; // Medium-high range
- self.bulletSpeed = 5; // Medium bullets
+ self.fireRate = 50;
+ self.damage = 8;
+ self.range = 3.5 * CELL_SIZE;
+ self.bulletSpeed = 5;
break;
case 'poison':
- self.fireRate = 70; // Medium firing rate
- self.damage = 12; // Medium damage
- self.range = 3.2 * CELL_SIZE; // Medium range
- self.bulletSpeed = 5; // Medium bullets
+ self.fireRate = 70;
+ self.damage = 12;
+ self.range = 3.2 * CELL_SIZE;
+ self.bulletSpeed = 5;
break;
}
- // Create the tower base (a square)
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
- // Set tower color based on type
switch (self.id) {
case 'rapid':
- baseGraphics.tint = 0x00AAFF; // Blue
+ baseGraphics.tint = 0x00AAFF;
break;
case 'sniper':
- baseGraphics.tint = 0xFF5500; // Orange
+ baseGraphics.tint = 0xFF5500;
break;
case 'splash':
- baseGraphics.tint = 0x33CC00; // Bright Green
+ baseGraphics.tint = 0x33CC00;
break;
case 'slow':
- baseGraphics.tint = 0x9900FF; // Deep Purple
+ baseGraphics.tint = 0x9900FF;
break;
case 'poison':
- baseGraphics.tint = 0x00FFAA; // Aqua
+ baseGraphics.tint = 0x00FFAA;
break;
default:
baseGraphics.tint = 0xAAAAAA;
- // Gray
}
- // Level indicators (dots at bottom of tower)
var levelIndicators = [];
var maxDots = self.maxLevel;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
for (var i = 0; i < maxDots; i++) {
var dot = new Container();
- // Create black outline circle behind the indicator
var outlineCircle = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
- outlineCircle.width = dotSize + 4; // Slightly larger for outline
+ outlineCircle.width = dotSize + 4;
outlineCircle.height = dotSize + 4;
- outlineCircle.tint = 0x000000; // Black outline
- // Add the colored indicator on top
+ outlineCircle.tint = 0x000000;
var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
- towerLevelIndicator.tint = 0xCCCCCC; // Gray dots by default
- // Position dot at bottom of tower
+ towerLevelIndicator.tint = 0xCCCCCC;
dot.x = -CELL_SIZE + dotSpacing * (i + 1);
dot.y = CELL_SIZE * 0.7;
self.addChild(dot);
levelIndicators.push(dot);
}
- // Create the gun turret that will rotate
var gunContainer = new Container();
self.addChild(gunContainer);
var gunGraphics = gunContainer.attachAsset('defense', {
anchorX: 0.5,
anchorY: 0.5
});
- //gunGraphics.y = -CELL_SIZE * 0.3; // Position the gun slightly up from center
- // Update the level indicators
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
var dot = levelIndicators[i];
- var towerLevelIndicator = dot.children[1]; // Index 1 is the colored indicator (index 0 is the black outline)
- // Light up dots up to current level with white color
+ var towerLevelIndicator = dot.children[1];
if (i < self.level) {
- // All upgraded levels should be white
- towerLevelIndicator.tint = 0xFFFFFF; // White for active levels
+ towerLevelIndicator.tint = 0xFFFFFF;
} else {
- // Levels not yet unlocked should match the tower's background color
switch (self.id) {
case 'rapid':
- towerLevelIndicator.tint = 0x00AAFF; // Blue
+ towerLevelIndicator.tint = 0x00AAFF;
break;
case 'sniper':
- towerLevelIndicator.tint = 0xFF5500; // Orange
+ towerLevelIndicator.tint = 0xFF5500;
break;
case 'splash':
- towerLevelIndicator.tint = 0x33CC00; // Bright Green
+ towerLevelIndicator.tint = 0x33CC00;
break;
case 'slow':
- towerLevelIndicator.tint = 0x9900FF; // Deep Purple
+ towerLevelIndicator.tint = 0x9900FF;
break;
case 'poison':
- towerLevelIndicator.tint = 0x00FFAA; // Aqua
+ towerLevelIndicator.tint = 0x00FFAA;
break;
default:
towerLevelIndicator.tint = 0xAAAAAA;
- // Gray for default
}
}
}
};
- // Initialize level indicators
self.updateLevelIndicators();
- // Method to refresh which cells are in range of this tower
self.refreshCellsInRange = function () {
- // First clear current cells in range
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
- // Recalculate cells in range with the new range using a circle approach
self.cellsInRange = [];
- var rangeRadius = self.range / CELL_SIZE; // Convert range to grid cell units
- // Get the exact center coordinates of the tower in grid space
- var centerX = self.gridX + 1; // Add .5 as our cells are offset with half a width
- var centerY = self.gridY + 1; // Add .5 as our cells are offset with half a width
- // Expand the search area to catch all cells that might intersect
+ var rangeRadius = self.range / CELL_SIZE;
+ var centerX = self.gridX + 1;
+ var centerY = self.gridY + 1;
var minI = Math.floor(centerX - rangeRadius - 0.5);
var maxI = Math.ceil(centerX + rangeRadius + 0.5);
var minJ = Math.floor(centerY - rangeRadius - 0.5);
var maxJ = Math.ceil(centerY + rangeRadius + 0.5);
- // Iterate through all cells in the bounding box
for (var i = minI; i <= maxI; i++) {
for (var j = minJ; j <= maxJ; j++) {
- // Find the closest point on the cell to the circle center
var closestX = Math.max(i, Math.min(centerX, i + 1));
var closestY = Math.max(j, Math.min(centerY, j + 1));
- // Calculate distance from circle center to closest point on the cell
var deltaX = closestX - centerX;
var deltaY = closestY - centerY;
var distanceSquared = deltaX * deltaX + deltaY * deltaY;
- // If any part of the cell is within the range radius, include this cell
if (distanceSquared <= rangeRadius * rangeRadius) {
var cell = grid.getCell(i, j);
if (cell) {
self.cellsInRange.push(cell);
@@ -784,44 +698,32 @@
}
}
}
}
- // Refresh debug cell rendering to update visuals for tower range
grid.renderDebug();
};
- // Method to calculate the total value of the tower including all upgrades
self.getTotalValue = function () {
- // Base tower cost based on tower type
var baseTowerCost = getTowerCost(self.id);
- // Calculate total investment (base cost + upgrade costs)
var totalInvestment = baseTowerCost;
for (var i = 1; i < self.level; i++) {
totalInvestment += 10 + i * 5;
}
return totalInvestment;
};
- // Method to upgrade the tower
self.upgrade = function () {
if (self.level < self.maxLevel) {
- // Calculate upgrade cost based on level
var upgradeCost = 10 + self.level * 5;
- // Check if player has enough gold
if (gold >= upgradeCost) {
setGold(gold - upgradeCost);
self.level++;
- // Increase stats based on level
self.range = (3 + self.level * 0.5) * CELL_SIZE;
- self.fireRate = Math.max(20, 60 - self.level * 8); // Faster firing rate
+ self.fireRate = Math.max(20, 60 - self.level * 8);
self.damage = 10 + self.level * 5;
self.bulletSpeed = 5 + self.level * 0.5;
- // Update targeting cells based on new range
self.refreshCellsInRange();
- // Update level indicators with animation
self.updateLevelIndicators();
- // Add a flash animation to the newly activated level indicator
if (self.level > 1) {
- var levelDot = levelIndicators[self.level - 1].children[1]; // Get the newly activated indicator
- // Flash the indicator by scaling it up and back
+ var levelDot = levelIndicators[self.level - 1].children[1];
tween(levelDot, {
scaleX: 1.5,
scaleY: 1.5
}, {
@@ -839,75 +741,60 @@
});
}
return true;
} else {
- // Show notification for not enough gold
var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
return false;
};
- // Method to find the nearest enemy within range
self.findTarget = function () {
var closestEnemy = null;
- var closestScore = Infinity; // Use pathfinding score to determine proximity to goal
+ var closestScore = Infinity;
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
if (enemy.cellX === cell.x && enemy.cellY === cell.y) {
- // Double-check actual distance to ensure enemy is within range
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // Only consider enemies that are actually within the tower's range
if (distance <= self.range) {
- // Check if this enemy is closer to the goal based on pathfinding score
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
}
}
}
}
- // Reset targetEnemy if no valid target is found
if (!closestEnemy) {
self.targetEnemy = null;
}
return closestEnemy;
};
- // Update method
self.update = function () {
- // Always find the target every frame to ensure state consistency
self.targetEnemy = self.findTarget();
- // Rotate gun toward target
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
- // Rotate the gun container
gunContainer.rotation = angle;
- // Fire at target if cooldown is complete
if (LK.ticks - self.lastFired >= self.fireRate) {
self.fire();
self.lastFired = LK.ticks;
}
}
};
- // Add click event handler to open upgrade menu
self.down = function (x, y, obj) {
- // Check if there's already an upgrade menu open
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
- // Check if this tower already has an upgrade menu open
var hasOwnMenu = false;
var rangeCircle = null;
- // Find this tower's range indicator if it exists
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
rangeCircle = game.children[i];
break;
@@ -918,69 +805,53 @@
hasOwnMenu = true;
break;
}
}
- // If this tower already has a menu open, close it and return
if (hasOwnMenu) {
- // Find and animate/destroy this tower's upgrade menu
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hideUpgradeMenu(existingMenus[i]);
}
}
- // Remove range circle if it exists
if (rangeCircle) {
game.removeChild(rangeCircle);
}
- // Clear selected tower
selectedTower = null;
- // Update grid render to remove highlights
grid.renderDebug();
return;
}
- // Remove any existing menus and range circles
for (var i = 0; i < existingMenus.length; i++) {
existingMenus[i].destroy();
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
- // Set this tower as the selected tower
selectedTower = self;
- // Show the tower's range circle first (add it before the upgrade menu so it appears underneath)
var rangeIndicator = new Container();
- rangeIndicator.isTowerRange = true; // Mark this as a tower range indicator
- rangeIndicator.tower = self; // Reference to the tower it belongs to
+ rangeIndicator.isTowerRange = true;
+ rangeIndicator.tower = self;
game.addChild(rangeIndicator);
- // Position at tower center
rangeIndicator.x = self.x;
rangeIndicator.y = self.y;
- // Create a range circle with the tower's current range
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
- rangeGraphics.width = rangeGraphics.height = self.range * 2; // Diameter is twice the radius
- rangeGraphics.alpha = 0.3; // Make it semi-transparent
- // Create new upgrade menu (after adding range indicator so menu appears on top)
+ rangeGraphics.width = rangeGraphics.height = self.range * 2;
+ rangeGraphics.alpha = 0.3;
var upgradeMenu = new UpgradeMenu(self);
game.addChild(upgradeMenu);
- // Position menu horizontally centered
- upgradeMenu.x = 2048 / 2; // Horizontally centered
- // Animate the upgrade menu from below the screen to its position
+ upgradeMenu.x = 2048 / 2;
tween(upgradeMenu, {
- y: 2732 - 225 // Move to final position (bottom of screen with menu centered)
+ y: 2732 - 225
}, {
duration: 200,
- // Twice as fast (400 -> 200)
easing: tween.backOut
});
- // Update grid render to highlight cells in range
grid.renderDebug();
};
- // Check if enemy is in range
self.isInRange = function (enemy) {
if (!enemy) {
return false;
}
@@ -988,129 +859,101 @@
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance <= self.range;
};
- // Fire at the current target
self.fire = function () {
if (self.targetEnemy) {
- // Verify the target is alive and calculate damage including future damage
var potentialDamage = 0;
for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
}
- // Only shoot if enemy would still be alive after all current bullets hit
if (self.targetEnemy.health > potentialDamage) {
- // Calculate bullet starting position
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
- // Create a new bullet
var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed);
game.addChild(bullet);
- // Add bullet to global bullets array and to enemy's tracking array
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
}
}
};
- // Place tower on the grid
self.placeOnGrid = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
- // Adjust by half a cell size to align with grid cell centers
self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
- // Mark these grid cells as occupied
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
- cell.type = 1; // Mark as wall/occupied
+ cell.type = 1;
}
}
}
- // Calculate which cells are in range
self.refreshCellsInRange();
};
return self;
});
-// TowerPreview class for showing where towers can be placed
var TowerPreview = Container.expand(function () {
var self = Container.call(this);
- // Define tower range (in grid cells)
var towerRange = 3;
var rangeInPixels = towerRange * CELL_SIZE;
- // Tower type being dragged
self.towerType = 'default';
- // Create the range indicator (circle)
var rangeIndicator = new Container();
self.addChild(rangeIndicator);
- // Since we don't have direct access to Graphics,
- // we'll create a white semi-transparent circle shape asset
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
- rangeGraphics.alpha = 0.3; // Make it semi-transparent
- // Create the preview square (2x2 grid cells)
+ rangeGraphics.alpha = 0.3;
var previewGraphics = self.attachAsset('towerpreview', {
anchorX: 0.5,
anchorY: 0.5
});
- // Set the size to cover 2x2 grid cells
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
- // Initial state
self.canPlace = false;
self.gridX = 0;
self.gridY = 0;
- self.blockedByEnemy = false; // Track if placement is blocked by enemy
- // Update appearance based on tower type
+ self.blockedByEnemy = false;
self.updateAppearance = function () {
- // Update range size based on tower type
switch (self.towerType) {
case 'rapid':
rangeGraphics.width = rangeGraphics.height = 2.5 * CELL_SIZE * 2;
- previewGraphics.tint = 0x00AAFF; // Blue
+ previewGraphics.tint = 0x00AAFF;
break;
case 'sniper':
rangeGraphics.width = rangeGraphics.height = 5 * CELL_SIZE * 2;
- previewGraphics.tint = 0xFF5500; // Orange
+ previewGraphics.tint = 0xFF5500;
break;
case 'splash':
rangeGraphics.width = rangeGraphics.height = 3 * CELL_SIZE * 2;
- previewGraphics.tint = 0x33CC00; // Bright Green
+ previewGraphics.tint = 0x33CC00;
break;
case 'slow':
rangeGraphics.width = rangeGraphics.height = 3.5 * CELL_SIZE * 2;
- previewGraphics.tint = 0x9900FF; // Deep Purple
+ previewGraphics.tint = 0x9900FF;
break;
case 'poison':
rangeGraphics.width = rangeGraphics.height = 3.2 * CELL_SIZE * 2;
- previewGraphics.tint = 0x00FFAA; // Aqua
+ previewGraphics.tint = 0x00FFAA;
break;
default:
rangeGraphics.width = rangeGraphics.height = 3 * CELL_SIZE * 2;
previewGraphics.tint = 0xAAAAAA;
- // Gray
}
- // Reset to original preview color based on placement possibility
if (!self.canPlace) {
- previewGraphics.tint = 0xFF0000; // Red if can't place
+ previewGraphics.tint = 0xFF0000;
}
};
- // Update the preview color based on placement possibility
self.updatePlacementStatus = function () {
- // First check if placement is valid from a grid perspective
var validGridPlacement = true;
- // Check if we're in the valid placement area (main square of the board)
if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) {
validGridPlacement = false;
} else {
- // Check each of the 2x2 cells
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
- // Can't place if any cell is undefined or not of type 0 (empty ground)
if (!cell || cell.type !== 0) {
validGridPlacement = false;
break;
}
@@ -1119,21 +962,16 @@
break;
}
}
}
- // Now check if any enemy is occupying the space where the tower would be placed
- // or if any cell is the current target of an enemy
self.blockedByEnemy = false;
if (validGridPlacement) {
- // Check for enemies occupying cells
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
- // Check if enemy is on this cell
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
- // Check if enemy's current target is one of these cells
if (enemy.currentTarget) {
var targetX = enemy.currentTarget.x;
var targetY = enemy.currentTarget.y;
if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) {
@@ -1142,201 +980,156 @@
}
}
}
}
- // Set final placement status
self.canPlace = validGridPlacement && !self.blockedByEnemy;
- // Update appearance based on placement and tower type
self.updateAppearance();
};
- // Check if the tower can be placed at the current position
self.checkPlacement = function () {
self.updatePlacementStatus();
};
- // Snap the preview to the grid
self.snapToGrid = function (x, y) {
- // Convert screen coordinates to grid coordinates
- // Offset by the grid's position
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
- // Convert to grid coordinates and round down to get the top-left cell
self.gridX = Math.floor(gridPosX / CELL_SIZE);
self.gridY = Math.floor(gridPosY / CELL_SIZE);
- // Position the preview at the intersection of the four cells
- // Adding CELL_SIZE to gridX and gridY gives us the center point
- // Adjust by half a cell size to align with grid cell centers
- self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; // Center of the 2x2 grid
- self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; // Center of the 2x2 grid
- // Check if we can place the tower here
+ self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2;
+ self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2;
self.checkPlacement();
};
return self;
});
-// Function to handle closing/hiding upgrade menus with animation
-// UpgradeMenu class
var UpgradeMenu = Container.expand(function (tower) {
var self = Container.call(this);
self.tower = tower;
- // Set initial position below the screen
- self.y = 2732 + 225; // Position below the screen (half the menu height)
- // Create background - wider to accommodate horizontal layout
+ self.y = 2732 + 225;
var menuBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
- menuBackground.width = 2048; // Full screen width
- menuBackground.height = 500; // Shorter for horizontal layout
+ menuBackground.width = 2048;
+ menuBackground.height = 500;
menuBackground.tint = 0x444444;
- menuBackground.alpha = 0.9; // More opaque for bottom toolbar
- // Tower name/type header
+ menuBackground.alpha = 0.9;
var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
towerTypeText.anchor.set(0, 0);
- towerTypeText.x = -840; // Align with stats
- towerTypeText.y = -160; // Higher up to make room for stats
+ towerTypeText.x = -840;
+ towerTypeText.y = -160;
self.addChild(towerTypeText);
- // Tower stats - positioned on the left side
var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', {
size: 70,
fill: 0xFFFFFF,
weight: 400
});
statsText.anchor.set(0, 0.5);
- statsText.x = -840; // Keep the left alignment
- statsText.y = 50; // Move down to make room for headline
+ statsText.x = -840;
+ statsText.y = 50;
self.addChild(statsText);
- // Create buttons container for the right side
var buttonsContainer = new Container();
- buttonsContainer.x = 500; // More to the right
+ buttonsContainer.x = 500;
self.addChild(buttonsContainer);
- // Create upgrade button
var upgradeButton = new Container();
buttonsContainer.addChild(upgradeButton);
var buttonBackground = upgradeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
- buttonBackground.width = 500; // Reduced width for side-by-side layout
- buttonBackground.height = 150; // Reduced height
- // Check if tower is already at max level
+ buttonBackground.width = 500;
+ buttonBackground.height = 150;
var isMaxLevel = self.tower.level >= self.tower.maxLevel;
- // Calculate upgrade cost if not at max level
var upgradeCost = isMaxLevel ? 0 : 10 + self.tower.level * 5;
- // Set button color based on max level or affordability
buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888;
- // Create button text
var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(buttonText);
- // Create sell button
var sellButton = new Container();
buttonsContainer.addChild(sellButton);
var sellButtonBackground = sellButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
- sellButtonBackground.width = 500; // Reduced width for side-by-side layout
- sellButtonBackground.height = 150; // Reduced height
- sellButtonBackground.tint = 0xCC0000; // Red color for sell button
- // Calculate sell value using the getTowerSellValue helper function
+ sellButtonBackground.width = 500;
+ sellButtonBackground.height = 150;
+ sellButtonBackground.tint = 0xCC0000;
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
- var sellValue = getTowerSellValue(totalInvestment); // Calculate sell value based on game state
- // Create sell button text
+ var sellValue = getTowerSellValue(totalInvestment);
var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
- // Position buttons side by side
- upgradeButton.y = -85; // Position upgrade button on top
- sellButton.y = 85; // Position sell button below
- // Tower indicator removed
- // Close button
+ upgradeButton.y = -85;
+ sellButton.y = 85;
var closeButton = new Container();
self.addChild(closeButton);
var closeBackground = closeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
- closeBackground.width = 90; // 50% larger than current: 60 * 1.5
- closeBackground.height = 90; // 50% larger than current: 60 * 1.5
+ closeBackground.width = 90;
+ closeBackground.height = 90;
closeBackground.tint = 0xAA0000;
var closeText = new Text2('X', {
size: 68,
fill: 0xFFFFFF,
weight: 800
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
- closeButton.x = menuBackground.width / 2 - 57; // 50% larger than current: 38 * 1.5
- closeButton.y = -menuBackground.height / 2 + 57; // 50% larger than current: 38 * 1.5
- // Event handlers
+ closeButton.x = menuBackground.width / 2 - 57;
+ closeButton.y = -menuBackground.height / 2 + 57;
upgradeButton.down = function (x, y, obj) {
- // First check if tower is already at max level
if (self.tower.level >= self.tower.maxLevel) {
- // Just show a notification about max level
var notification = game.addChild(new Notification("Tower is already at max level!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
}
- // Then check if upgrade succeeds
if (self.tower.upgrade()) {
- // Update stats text after upgrade
upgradeCost = 10 + self.tower.level * 5;
statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s');
buttonText.setText('Upgrade: ' + upgradeCost + ' gold');
- // Update sell value after upgrade - use the tower's properties directly
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
- var sellValue = Math.floor(totalInvestment * 0.6); // 60% of total value
- // Update sell button text
+ var sellValue = Math.floor(totalInvestment * 0.6);
sellButtonText.setText('Sell: +' + sellValue + ' gold');
- // If tower is max level, disable button
if (self.tower.level >= self.tower.maxLevel) {
buttonBackground.tint = 0x888888;
buttonText.setText('Max Level');
}
- // Find and update range indicator if it exists
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
rangeCircle = game.children[i];
break;
}
}
- // Always ensure we have a range indicator when upgrading
if (rangeCircle) {
- // Update the existing range indicator with new range
var rangeGraphics = rangeCircle.children[0];
- rangeGraphics.width = rangeGraphics.height = self.tower.range * 2; // Update diameter to reflect new range
+ rangeGraphics.width = rangeGraphics.height = self.tower.range * 2;
} else {
- // Create a new range indicator
var newRangeIndicator = new Container();
newRangeIndicator.isTowerRange = true;
newRangeIndicator.tower = self.tower;
- // Add it to the beginning of the children array so it appears at the bottom
game.addChildAt(newRangeIndicator, 0);
- // Position at tower center
newRangeIndicator.x = self.tower.x;
newRangeIndicator.y = self.tower.y;
- // Create a range circle with the tower's updated range
var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
- rangeGraphics.width = rangeGraphics.height = self.tower.range * 2; // Diameter is twice the radius
- rangeGraphics.alpha = 0.3; // Make it semi-transparent
+ rangeGraphics.width = rangeGraphics.height = self.tower.range * 2;
+ rangeGraphics.alpha = 0.3;
}
- // Add a nice animation effect
tween(self, {
scaleX: 1.05,
scaleY: 1.05
}, {
@@ -1353,52 +1146,40 @@
}
});
}
};
- // Sell button handler
sellButton.down = function (x, y, obj) {
- // Use getTowerSellValue function to calculate correct sell value based on game state
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = getTowerSellValue(totalInvestment);
- // Award gold to player
setGold(gold + sellValue);
- // Show sale notification
var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
- // Mark cells as empty again
var gridX = self.tower.gridX;
var gridY = self.tower.gridY;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
- cell.type = 0; // Mark as empty ground
- // Remove this tower from cell's towersInRange array
+ cell.type = 0;
var towerIndex = cell.towersInRange.indexOf(self.tower);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
}
}
- // If the tower being sold is the selected tower, clear the selected tower
if (selectedTower === self.tower) {
selectedTower = null;
}
- // Remove tower from towers array
var towerIndex = towers.indexOf(self.tower);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
- // Remove tower from the game
towerLayer.removeChild(self.tower);
- // Update pathing
grid.pathFind();
grid.renderDebug();
- // Close upgrade menu
self.destroy();
- // Remove range indicator
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
@@ -1406,273 +1187,213 @@
}
};
closeButton.down = function (x, y, obj) {
hideUpgradeMenu(self);
- // Clear selected tower
selectedTower = null;
- // Update grid render to remove highlights
grid.renderDebug();
};
- // Add update method to check affordability in real-time
self.update = function () {
- // Check if at max level
if (self.tower.level >= self.tower.maxLevel) {
- // Ensure button shows max level state
if (buttonText.text !== 'Max Level') {
buttonText.setText('Max Level');
buttonBackground.tint = 0x888888;
}
return;
}
- // Calculate current upgrade cost
var currentUpgradeCost = 10 + self.tower.level * 5;
- // Update button color based on current gold
var canAfford = gold >= currentUpgradeCost;
buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888;
- // Update text to reflect current status if needed
var newText = 'Upgrade: ' + currentUpgradeCost + ' gold';
if (buttonText.text !== newText) {
buttonText.setText(newText);
}
};
return self;
});
-// WaveIndicator class
var WaveIndicator = Container.expand(function () {
var self = Container.call(this);
- // Track if game has started
self.gameStarted = false;
- // Create wave markers - now as blocks instead of dots
self.waveMarkers = [];
- self.indicatorWidth = 0; //background.width - 400; // Leave space on edges
- var blockWidth = 400; // Width of each wave block
- // Calculate total content width needed (50 blocks)
+ self.indicatorWidth = 0;
+ var blockWidth = 400;
var totalBlocksWidth = blockWidth * totalWaves;
- // Create the start game block first (level 0)
var startMarker = new Container();
- // Start block marker with different styling
var startBlock = startMarker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
startBlock.width = blockWidth - 10;
startBlock.height = 70 * 2;
- startBlock.tint = 0x00AA00; // Green for start button
- // Add "Start Game" text
+ startBlock.tint = 0x00AA00;
var startText = new Text2("Start Game", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
startText.anchor.set(0.5, 0.5);
startMarker.addChild(startText);
- // Position start marker at level 0 position
startMarker.x = -self.indicatorWidth;
self.addChild(startMarker);
self.waveMarkers.push(startMarker);
- // Add click event to start marker
startMarker.down = function () {
if (!self.gameStarted) {
self.gameStarted = true;
currentWave = 0;
- waveTimer = nextWaveTime; // This will trigger the first wave immediately
- // Change appearance to show it's been activated
+ waveTimer = nextWaveTime;
startBlock.tint = 0x00FF00;
startText.setText("Started!");
- // Show notification
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
- // Position blocks further apart so they don't all fit on screen
for (var i = 0; i < totalWaves; i++) {
var marker = new Container();
- // Block marker
var block = marker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
- block.width = blockWidth - 10; // Slight gap between blocks, scaled up by 100%
- block.height = 70 * 2; // Scale height by 100%
- // Set block color
+ block.width = blockWidth - 10;
+ block.height = 70 * 2;
if (i % 10 === 0) {
- // Special waves (every 10th)
- block.tint = 0xFF5500; // Orange for major waves
+ block.tint = 0xFF5500;
} else if (i % 5 === 0) {
- // Medium waves (every 5th)
- block.tint = 0xFFFF00; // Yellow for medium waves
+ block.tint = 0xFFFF00;
} else {
- // Regular waves
- block.tint = 0xAAAAAA; // Gray for regular waves
+ block.tint = 0xAAAAAA;
}
- // Add wave number text to every block
var waveNum = new Text2((i + 1).toString(), {
size: 40,
fill: 0xFFFFFF,
weight: 800
});
waveNum.anchor.set(0.5, 0.5);
marker.addChild(waveNum);
- // Position marker horizontally - spread blocks further apart than screen width
- marker.x = -self.indicatorWidth + (i + 1) * blockWidth; // Add 1 to account for start button
+ marker.x = -self.indicatorWidth + (i + 1) * blockWidth;
self.addChild(marker);
self.waveMarkers.push(marker);
}
- // Create current position indicator
self.positionIndicator = new Container();
var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = blockWidth - 10;
indicator.height = 16;
- indicator.tint = 0xffad0e; // Orange for top border
+ indicator.tint = 0xffad0e;
indicator.y = -65;
var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator2.width = blockWidth - 10;
indicator2.height = 16;
- indicator2.tint = 0xffad0e; // Orange for bottom border
+ indicator2.tint = 0xffad0e;
indicator2.y = 65;
- // Add left wall
var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
leftWall.width = 16;
- leftWall.height = 146; // Height to connect top and bottom indicators
- leftWall.tint = 0xffad0e; // Orange for left border
- leftWall.x = -(blockWidth - 16) / 2; // Align with left edge of top/bottom indicators
- // Add right wall
+ leftWall.height = 146;
+ leftWall.tint = 0xffad0e;
+ leftWall.x = -(blockWidth - 16) / 2;
var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
rightWall.width = 16;
- rightWall.height = 146; // Height to connect top and bottom indicators
- rightWall.tint = 0xffad0e; // Orange for right border
- rightWall.x = (blockWidth - 16) / 2; // Align with right edge of top/bottom indicators
- // Add wave number text
+ rightWall.height = 146;
+ rightWall.tint = 0xffad0e;
+ rightWall.x = (blockWidth - 16) / 2;
self.addChild(self.positionIndicator);
- // Update method to move the position indicator and scroll the wave blocks
self.update = function () {
- // Update position indicator based on current wave progress
var progress = waveTimer / nextWaveTime;
- // Calculate position for the wave markers
- // Move all markers to position the current wave at the center of the screen
- var moveAmount = (progress + currentWave) * blockWidth; // Center current wave block
- // Move all markers (creates scrolling effect)
- // Adjust so that the level is centered on the screen
+ var moveAmount = (progress + currentWave) * blockWidth;
for (var i = 0; i < self.waveMarkers.length; i++) {
var marker = self.waveMarkers[i];
marker.x = -moveAmount + i * blockWidth;
}
- // Position the indicator at the center of the screen
self.positionIndicator.x = 0;
- // Update completed wave markers
for (var i = 0; i < totalWaves + 1; i++) {
var marker = self.waveMarkers[i];
if (i === 0) {
- // This is the start button
continue;
}
var block = marker.children[0];
if (i - 1 < currentWave) {
- // Subtract 1 to account for start button
- // Completed waves
- block.tint = 0x00AA00; // Green for completed waves
+ block.tint = 0x00AA00;
}
}
- // Handle wave progression
self.handleWaveProgression = function () {
- // Don't start any waves until the game has been started
if (!self.gameStarted) {
return;
}
- // Only process if we haven't reached max waves
if (currentWave < totalWaves) {
waveTimer++;
- // Check if it's time to start a new wave
if (waveTimer >= nextWaveTime) {
waveTimer = 0;
currentWave++;
waveInProgress = true;
waveSpawned = false;
- // Show wave notification
if (currentWave != 1) {
var notification = game.addChild(new Notification("Wave " + currentWave + " incoming with 20 enemies!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
}
};
- // Call wave progression handler
self.handleWaveProgression();
};
return self;
});
/****
* Initialize Game
****/
-// Add update method to check affordability in real-time
var game = new LK.Game({
- backgroundColor: 0x333333 //Init game with black background
+ backgroundColor: 0x333333
});
/****
* Game Code
****/
-// Import tween plugin for animations
-// Define cell size as a global constant
-// Variable to track if an upgrade menu is being hidden
var isHidingUpgradeMenu = false;
-// Function to handle closing/hiding upgrade menus with animation
function hideUpgradeMenu(menu) {
- // Prevent double calls to hide the same menu
if (isHidingUpgradeMenu) {
return;
}
- // Set flag to indicate we're hiding a menu
isHidingUpgradeMenu = true;
- // Animate menu out before destroying
tween(menu, {
- y: 2732 + 225 // Animate below the screen
+ y: 2732 + 225
}, {
duration: 150,
- // Twice as fast as before
easing: tween.easeIn,
onFinish: function onFinish() {
menu.destroy();
- isHidingUpgradeMenu = false; // Reset flag after animation completes
+ isHidingUpgradeMenu = false;
}
});
}
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
var enemies = [];
var towers = [];
-var bullets = []; // Array to store active bullets
+var bullets = [];
var defenses = [];
-var selectedTower = null; // Keep track of the currently selected tower
-// Game resources and scores
+var selectedTower = null;
var gold = 80;
var lives = 20;
var score = 0;
var currentWave = 0;
var totalWaves = 50;
var waveTimer = 0;
var waveInProgress = false;
var waveSpawned = false;
-var nextWaveTime = 3000; // Time between waves (in frames), increased by 10x
-// Source tower that players drag from to create new towers
+var nextWaveTime = 3000;
var sourceTower = null;
-// Create UI elements for resources
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0xFFD700,
weight: 800
@@ -1689,40 +1410,29 @@
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
-// Position UI elements at the top of the screen
-// Avoid top left corner (pause button area)
-// Center the UI elements with proper spacing
-var topMargin = 50; // Reduced by 30px from original 80px
+var topMargin = 50;
var centerX = 2048 / 2;
-var spacing = 400; // Adjusted spacing for larger text
-// Position the elements centered on the screen
-// First, attach to the top center container
+var spacing = 400;
LK.gui.top.addChild(goldText);
LK.gui.top.addChild(livesText);
LK.gui.top.addChild(scoreText);
-// Position lives text in the center
-livesText.x = 0; // Center position (relative to top center)
+livesText.x = 0;
livesText.y = topMargin;
-// Position gold text to the left of lives
goldText.x = -spacing;
goldText.y = topMargin;
-// Position score text to the right of lives
scoreText.x = spacing;
scoreText.y = topMargin;
-// Helper function to update UI
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
-// Centralized function to modify gold with UI updates
function setGold(value) {
gold = value;
updateUI();
}
-// Create separate layers for debug tiles, towers, and enemies
var debugLayer = new Container();
var towerLayer = new Container();
var enemyLayer = new Container();
var grid = new Grid(24, 29 + 6);
@@ -1730,22 +1440,17 @@
grid.y = 200 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
-// Add layers to the game in the correct order
game.addChild(debugLayer);
game.addChild(towerLayer);
game.addChild(enemyLayer);
var offset = 0;
-// Create tower preview
var towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
-// Handle drag events for tower placement
var isDragging = false;
-// Check if placing a tower would block the path
function wouldBlockPath(gridX, gridY) {
- // Temporarily mark cells as walls
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
@@ -1753,26 +1458,22 @@
cells.push({
cell: cell,
originalType: cell.type
});
- cell.type = 1; // Mark as wall
+ cell.type = 1;
}
}
}
- // Check if path is still valid
var blocked = grid.pathFind();
- // Restore original cell types
for (var i = 0; i < cells.length; i++) {
cells[i].cell.type = cells[i].originalType;
}
- // Re-find path with original cells
grid.pathFind();
grid.renderDebug();
return blocked;
}
-// Helper function to get tower cost based on type
function getTowerCost(towerType) {
- var cost = 5; // Default tower cost
+ var cost = 5;
switch (towerType) {
case 'rapid':
cost = 15;
break;
@@ -1790,57 +1491,42 @@
break;
}
return cost;
}
-// Helper function to calculate tower sell value based on game state
function getTowerSellValue(totalValue) {
- // Full refund before game starts, 60% after game starts
- return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) :
- // 60% of total value after game starts
- totalValue; // 100% refund before game starts
+ return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue;
}
-// Place a tower at the grid position
function placeTower(gridX, gridY, towerType) {
- // Tower cost based on tower type
var towerCost = getTowerCost(towerType);
- // Check if player has enough gold
if (gold >= towerCost) {
var tower = new Tower(towerType || 'default');
tower.placeOnGrid(gridX, gridY);
towerLayer.addChild(tower);
towers.push(tower);
- // Deduct gold
setGold(gold - towerCost);
- // Update grid pathing
grid.pathFind();
grid.renderDebug();
return true;
} else {
- // Not enough gold
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
game.down = function (x, y, obj) {
- // Check if an upgrade menu is currently visible - prevent tower drag when menu is showing
var upgradeMenuVisible = game.children.some(function (child) {
return child instanceof UpgradeMenu;
});
- // Exit early if upgrade menu is visible
if (upgradeMenuVisible) {
return;
}
- // Check if click is on any source tower
for (var i = 0; i < sourceTowers.length; i++) {
var tower = sourceTowers[i];
if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) {
towerPreview.visible = true;
isDragging = true;
- // Store which tower type we're dragging
towerPreview.towerType = tower.towerType;
- // Update preview appearance based on tower type
towerPreview.updateAppearance();
towerPreview.snapToGrid(x, y);
break;
}
@@ -1851,93 +1537,72 @@
towerPreview.snapToGrid(x, y);
}
};
game.up = function (x, y, obj) {
- // Track if we clicked on a tower to show the upgrade menu
var clickedOnTower = false;
- // Check if click was on a tower
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
- // Calculate tower bounds
var towerLeft = tower.x - tower.width / 2;
var towerRight = tower.x + tower.width / 2;
var towerTop = tower.y - tower.height / 2;
var towerBottom = tower.y + tower.height / 2;
- // Check if click is inside tower bounds
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
- // Check for clicking outside upgrade menu
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
- // If there are upgrade menus open, check if we clicked outside
if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) {
var clickedOnMenu = false;
- // Check if the click is inside any upgrade menu
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
- // Calculate menu bounds
- var menuWidth = 2048; // Full screen width
- var menuHeight = 450; // Menu height
+ var menuWidth = 2048;
+ var menuHeight = 450;
var menuLeft = menu.x - menuWidth / 2;
var menuRight = menu.x + menuWidth / 2;
var menuTop = menu.y - menuHeight / 2;
var menuBottom = menu.y + menuHeight / 2;
- // Check if click is inside menu bounds
if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
clickedOnMenu = true;
break;
}
}
- // If clicked outside all menus, close them
if (!clickedOnMenu) {
- // Animate menu out before destroying
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
hideUpgradeMenu(menu);
}
- // Also remove any range indicators
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
- // Clear selected tower
selectedTower = null;
- // Update grid render to remove highlights
grid.renderDebug();
}
}
if (isDragging) {
isDragging = false;
- // Check placement possibilities
if (towerPreview.canPlace) {
- // Check if placing here would block the path
if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
- // Place the tower (placeTower will handle gold check internally)
placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType);
} else {
- // Show notification that we can't place here
var notification = game.addChild(new Notification("Tower would block the path!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
} else if (towerPreview.blockedByEnemy) {
- // Show notification that an enemy is in the way
var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else if (towerPreview.visible) {
- // Generic notification for other reasons
var notification = game.addChild(new Notification("Cannot build here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
towerPreview.visible = false;
- // Close any upgrade menus when placing towers
if (isDragging) {
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
@@ -1946,26 +1611,21 @@
}
}
}
};
-// Create wave indicator at the bottom of the screen
var waveIndicator = new WaveIndicator();
waveIndicator.x = 2048 / 2;
waveIndicator.y = 2732 - 80;
-// Adjust initial position to ensure first wave block is visible
game.addChild(waveIndicator);
-// Note: Wave progression is now fully handled by the WaveIndicator class
-// Add next wave button
var nextWaveButtonContainer = new Container();
var nextWaveButton = new NextWaveButton();
-nextWaveButton.x = 2048 - 200; // Position at right side of screen
-nextWaveButton.y = 2732 - 100 + 20; // Position at bottom right corner, moved down by 20px
+nextWaveButton.x = 2048 - 200;
+nextWaveButton.y = 2732 - 100 + 20;
nextWaveButtonContainer.addChild(nextWaveButton);
game.addChild(nextWaveButtonContainer);
-// Create a row of source towers
var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison'];
var sourceTowers = [];
-var towerSpacing = 250; // Increased spacing from 170 to 250
+var towerSpacing = 250;
var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2;
var towerY = 2732 - CELL_SIZE * 3 - 90;
for (var i = 0; i < towerTypes.length; i++) {
var tower = new SourceTower(towerTypes[i]);
@@ -1973,126 +1633,94 @@
tower.y = towerY;
towerLayer.addChild(tower);
sourceTowers.push(tower);
}
-// Set sourceTower to null as we'll use the array instead
sourceTower = null;
enemiesToSpawn = 10;
game.update = function () {
- // Wave progress is now handled by the WaveIndicator class
- // Spawn enemies when wave is in progress
if (waveInProgress) {
- // Spawn all enemies at once when the wave starts
if (!waveSpawned) {
waveSpawned = true;
for (var i = 0; i < enemiesToSpawn; i++) {
var target = grid.spawns[Math.random() * grid.spawns.length >> 0];
var enemy = enemyLayer.addChild(new Enemy());
- // Make enemies stronger in later waves
enemy.maxHealth = 100 + currentWave * 10;
enemy.health = enemy.maxHealth;
enemy.speed = 0.02 + currentWave * 0.002;
- // Calculate random starting position just outside the screen (above)
- var randomOffsetX = Math.random() * 2048; // Random X position
- var randomOffsetY = -100 - Math.random() * 200; // Reduced distance above screen
- // Set cell position to target spawn
+ var randomOffsetX = Math.random() * 2048;
+ var randomOffsetY = -100 - Math.random() * 200;
enemy.cellX = target.x;
enemy.cellY = target.y;
- // Set current cell position to be outside screen
- // We'll convert from screen position to grid position
enemy.currentCellX = (randomOffsetX - grid.x) / CELL_SIZE;
enemy.currentCellY = (randomOffsetY - grid.y) / CELL_SIZE;
- enemy.waveNumber = currentWave; // Assign current wave number to this enemy
+ enemy.waveNumber = currentWave;
enemies.push(enemy);
}
}
- // Check if all enemies for this wave have been spawned and eliminated
var currentWaveEnemiesRemaining = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].waveNumber === currentWave) {
currentWaveEnemiesRemaining = true;
break;
}
}
- // Reset wave state when all enemies of this wave are gone
if (waveSpawned && !currentWaveEnemiesRemaining) {
waveInProgress = false;
waveSpawned = false;
}
}
- // Update enemies
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
- // Check if enemy is defeated
if (enemy.health <= 0) {
- // Clear bullet targeting references
for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
var bullet = enemy.bulletsTargetingThis[i];
- bullet.targetEnemy = null; // This will cause bullet to destroy itself on next update
+ bullet.targetEnemy = null;
}
- // Award gold and score for defeating enemy
- var goldEarned = 1; // Base gold earned for killing enemy
- // Create gold indicator at enemy position
+ var goldEarned = 1;
var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
game.addChild(goldIndicator);
- // Update player's gold and score
setGold(gold + goldEarned);
score += 5;
updateUI();
- // Remove enemy
enemyLayer.removeChild(enemy);
enemies.splice(a, 1);
continue;
}
- // Update enemy movement
if (grid.updateEnemy(enemy)) {
- // Enemy reached goal
enemyLayer.removeChild(enemy);
enemies.splice(a, 1);
- // Reduce lives when enemy reaches goal
lives = Math.max(0, lives - 1);
updateUI();
- // Check if game over (no lives left)
if (lives <= 0) {
LK.showGameOver();
}
}
}
- // Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
- // Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
- // Remove destroyed bullets
if (!bullets[i].parent) {
- // If this bullet has a target, remove the bullet from the target's tracking array
if (bullets[i].targetEnemy) {
var targetEnemy = bullets[i].targetEnemy;
var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
if (bulletIndex !== -1) {
targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
- // Ensure bullet is removed from global bullets array
bullets.splice(i, 1);
}
}
- // Update tower preview placement status on every frame
- // This ensures the placement status updates in real-time as enemies move
if (towerPreview.visible) {
towerPreview.checkPlacement();
}
- // Wave indicator update handles the wave progression
if (waveIndicator) {
waveIndicator.update();
}
- // Update next wave button state
if (nextWaveButton) {
nextWaveButton.update();
}
- // Check win condition
if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) {
LK.showYouWin();
}
};
\ No newline at end of file
White circle with two eyes, seen from above.. In-Game asset. 2d. High contrast. No shadows
White simple circular enemy seen from above, black outline. Black eyes, with a single shield in-font of it. Black and white only. Blue background.
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows