User prompt
rename the embedded asset dotGraphics to upgradelevelIndicator
User prompt
Enemies seems to render by half a cell_size to the left and up, please fix this
User prompt
Enemies now seem to render misaligned from the debug grid, please fix this
User prompt
Enemies no longer seem to correctly be removed from the game, please fix this
User prompt
Create layers in the game such that debug tiles are always rendering at the bottom, towers are a separate layers and enemies are always rendered in front of everything else
User prompt
Enemies should always be in front, currently they sometimes render below towers. Make sure that enemies always render above towers, even when we place new towers
User prompt
Enemies should always be in front, currently they sometimes render below towers
User prompt
Update the update level system on towers such that the dots render below the gun graphics.
User prompt
Use a separate dotGraphics asset type for these. Also make sure they are spaced exactly such that the upgrade level of a tower takes up the full width of the tower base
User prompt
In the tower class don't scale elements programmatically. Scale of assets are handled by the engine.
Code edit (2 edits merged)
Please save this source code
User prompt
Preview tower and tower placement themself are off by half a cell_size. This happens because the cell's are have a center anchor point. Please fix this by making sure that tower preview and towers are placed half a cell_size to the left and up
User prompt
Both preview tower and towers themself seem to be misaligned by half a cell_size. I think it's because the game grid uses elements that are center aligned. Please fix this.
User prompt
Ok we now have a nice preview system. Let's add a towers class to the game. I think we only want one tower class for simplicity where we simply pass an id into it on creation to set art and attributes. Towers are upgradable, we show the upgrade level with a simple dots across the bottom of the tower. All towers start at level 1 and can be upgraded to level 6. Visually towers consist of a square base and a gun at the top of the tower that can rotate to point towards the nearest targeted enemy. Towers also have attributes such as (note these can change for each level) * range (how far they can fire) * fire speed * bullet speed/type Implement this class, only creating one tower type for now. When we release our preview tower, and we can place a tower place an instance of this tower on our game board.
User prompt
In the tower preview class we also want to show the range of the tower. We do this with a simple white semi opaque circle. Add that to the tower preview
User prompt
For the preview cell, towers can only be places inside the main square of the board. E.g. where j > 4 and j < gridHeight - 4 in the definition. Please update the preview system to handle this
Code edit (4 edits merged)
Please save this source code
User prompt
We have 76 as cell size and derivatives of this size hardcoded all over the place in the code, please use a single variable instead.
Code edit (1 edits merged)
Please save this source code
User prompt
The tower preview seems misaligned with the actual grid, I think this is because cells are center aligned. Please update the tower preview class accordingly
User prompt
We need to start building the infrastructure to place towers. The very first thing we want to add is a tower placement preview class. Note that Towers are 2x2 grid tiles large and can only be places on the playing grid. Tower preview have a tower sized square that is either green if the tower can be placed or red if it cannot be placed. The preview element should snap to the grid. Please implement this class and create a single instance that we always drag. Do nothing else for now. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
Add a currency to this game, that can be used to build and upgrade towers. Only implement the currency infrastructure for now. Show my coins in the top right corner with a label + a gold coin. Start the game with 80 coins.
Code edit (1 edits merged)
Please save this source code
/**** * Classes ****/ // 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.down = function () { 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); 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 = } debugArrows[a].rotation = angle; // debugArrow.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); }; }); // Enemy class var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speed = Math.random() * .2 + .03; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; // Add health bar to the enemy var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0.5, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; // Position the health bar above the enemy self.healthBar = healthBar; }); //Class for the grid, handles all logic related to grid movement and interaction 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 }; } } /* 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++) { // 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 + 4) { 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; /*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]; 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; processNode(node.upLeft, targetScore, node); processNode(node.upRight, targetScore, node); processNode(node.downRight, targetScore, node); 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) { //Allows enemies to change direction right away. 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 = enemy.currentCellX * CELL_SIZE; enemy.y = enemy.currentCellY * CELL_SIZE; }; }); // Notification class 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; // 2 seconds at 60FPS self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); // TowerPreview class for showing where towers can be placed var TowerPreview = Container.expand(function () { var self = Container.call(this); // Create the preview square (2x2 grid cells) 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; // Update the preview color based on placement possibility self.updatePlacementStatus = function () { // If we can place, tint green, otherwise red previewGraphics.tint = self.canPlace ? 0x00FF00 : 0xFF0000; }; // Check if the tower can be placed at the current position self.checkPlacement = function () { // Get the four cells that would be covered by this tower (2x2 grid) var canPlace = true; // First check if we're in the valid placement area (main square of the board) // Check if any part of the tower would be outside the main square if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { canPlace = 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) { canPlace = false; break; } } if (!canPlace) { break; } } } self.canPlace = canPlace; 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 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.checkPlacement(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 //Init game with black background }); /**** * Game Code ****/ // Define cell size as a global constant var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var defenses = []; var grid = new Grid(25, 29 + 6); grid.x = 112; grid.y = 112 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); game.addChild(grid); 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; game.down = function (x, y, obj) { towerPreview.visible = true; isDragging = true; towerPreview.snapToGrid(x, y); }; game.move = function (x, y, obj) { if (isDragging) { towerPreview.snapToGrid(x, y); } }; game.up = function (x, y, obj) { isDragging = false; // We're just previewing for now, so we don't place the tower yet // Later we'll add actual tower placement here towerPreview.visible = false; }; game.update = function () { if (++offset % 100 == 0) { isFirst = false; var target = grid.spawns[Math.random() * grid.spawns.length >> 0]; var enemy = grid.addChild(new Enemy()); enemy.currentCellX = enemy.cellX = target.x; enemy.currentCellY = enemy.cellY = target.y; enemies.push(enemy); } for (var a = enemies.length - 1; a >= 0; a--) { if (grid.updateEnemy(enemies[a])) { grid.removeChild(enemies[a]); enemies.splice(a--, 1); } } };
===================================================================
--- original.js
+++ change.js
@@ -346,21 +346,27 @@
// Check if the tower can be placed at the current position
self.checkPlacement = function () {
// Get the four cells that would be covered by this tower (2x2 grid)
var canPlace = true;
- // 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) {
- canPlace = false;
+ // First check if we're in the valid placement area (main square of the board)
+ // Check if any part of the tower would be outside the main square
+ if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) {
+ canPlace = 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) {
+ canPlace = false;
+ break;
+ }
+ }
+ if (!canPlace) {
break;
}
}
- if (!canPlace) {
- break;
- }
}
self.canPlace = canPlace;
self.updatePlacementStatus();
};
@@ -374,10 +380,10 @@
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
- self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE; // Center of the 2x2 grid
- self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE; // Center of the 2x2 grid
+ 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.checkPlacement();
};
return self;
@@ -418,9 +424,9 @@
towerPreview.snapToGrid(x, y);
};
game.move = function (x, y, obj) {
if (isDragging) {
- towerPreview.snapToGrid(x - CELL_SIZE / 2, y - CELL_SIZE / 2);
+ towerPreview.snapToGrid(x, y);
}
};
game.up = function (x, y, obj) {
isDragging = false;
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