Code edit (2 edits merged)
Please save this source code
User prompt
Enemies can currently outrun bullets. Find a clever way to solve this issue. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Our towers should be able to shoot at our enemies. The current tower type is a simple bullet type tower. So make it just shoot bullets at our enemies. Make sure that we only shoot at enemies that are still alive after having been hit by all bullets that have already been fired at said enemy
User prompt
Sometimes when we try to build a tower by releasing the screen, the tower fails to build because an enemy is in the way. In those cases please show a notification that it failed and why.
User prompt
Sometimes when we try to build a tower by releasing the screen, the tower fails to build because an enemy is in the way. In those cases please show a notification that it failed and why.
User prompt
When a tower fails to build because an enemy was in the way, show a notification informing the user of that it happened and why
User prompt
When a tower fails to build because an enemy was in the way, show a notification informing the user of that it happened and why
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'self.cells[i][j].towersInRange = []')' in or related to this line: 'self.cells[i][j].towersInRange = []; // Cache for towers in range' Line Number: 145
User prompt
Ok let's look into targeting from towers. Simply doing a full check of all towers vs all enemies is going to be slow when we have hundreds of each. Therefore each tower should only look for enemies who are currently occupying cells inside the towers reach. You might have to create some caches such that each cell knows which towers are inside it and each tower knows which cells are inside its range.
Code edit (3 edits merged)
Please save this source code
User prompt
currently the start and end gabs in the walls are 7 cells wide, reduce this to 6
Code edit (2 edits merged)
Please save this source code
User prompt
Tower preview based on if enemies are in the way does not seem to work. If I move my mouse a bit it updates correctly, but it does not seem to automatically update correctly when enemies move in and out of the preview area
User prompt
For the tower preview, factor in if the placement of the tower is blocked due to an enemy being where the tower would be placed. This should be live such that the state changes as enemies move. (E.g. update on each update on game)
User prompt
For the tower preview, factor in if the placement of the tower is blocked due to an enemy being where the tower would be placed. This should be live such that the state changes as enemies move.
User prompt
For the tower preview, factor in if the placement of the tower is blocked due to an enemy being where the tower would be placed. This should be live such that the state changes as enemies move.
User prompt
As we can't place a tower if an enemy is currently occupying part of where the tower would be placed. Please update the tower preview to also factor this in.
User prompt
Currently enemies can pathfinder trough a tight corner where two corners of towers touch. I don't think I like that. However I still want towers to be able to traverse diagonally across the board. Can you please update the pathfinding accordingly.
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'dotGraphics.tint = 0xFFFF00; // Yellow for active levels' Line Number: 397
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'dotGraphics.width = dotSize;' Line Number: 373
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
/**** * 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 () { 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); 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 + 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; /*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; // 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) { 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) { //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 = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.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; }); // 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.fireRate = 60; // frames between shots self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; // Create the tower base (a square) var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); // 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(); 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 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[0]; // Light up dots up to current level if (i < self.level) { towerLevelIndicator.tint = 0xFFFF00; // Yellow for active levels } else { towerLevelIndicator.tint = 0xCCCCCC; // Gray for inactive levels } } }; // Initialize level indicators self.updateLevelIndicators(); // Method to upgrade the tower self.upgrade = function () { if (self.level < self.maxLevel) { 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.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; // Update level indicators self.updateLevelIndicators(); return true; } return false; }; // Method to find the nearest enemy within range self.findTarget = function () { var closestEnemy = null; var closestDistance = self.range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; // Update method self.update = function () { // Find target if we don't have one or if current target is out of range if (!self.targetEnemy || !self.isInRange(self.targetEnemy)) { 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; } } }; // Check if enemy is in range 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; }; // Fire at the current target self.fire = function () { if (self.targetEnemy) { // Create bullet (you would implement this in game.update) // For now we'll just log that we fired console.log("Tower fired at enemy!"); // Later we'll create a bullet class and handle this properly } }; // 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 } } } }; 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; // 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) 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 () { // Check if any enemy is occupying the space where the tower would be placed 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.canPlace = false; break; } } // 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 // 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.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 towers = []; var defenses = []; // 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); grid.x = 112; grid.y = 112 - 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); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; // Mark as wall } } } // 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; } // Place a tower at the grid position function placeTower(gridX, gridY) { var tower = new Tower('default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); // Update grid pathing grid.pathFind(); grid.renderDebug(); } 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; // Only place tower if we can if (towerPreview.canPlace) { // Check if placing here would block the path if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { // Place the tower placeTower(towerPreview.gridX, towerPreview.gridY); } 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; } } towerPreview.visible = false; }; game.update = function () { // Spawn enemies periodically if (++offset % 100 == 0) { var target = grid.spawns[Math.random() * grid.spawns.length >> 0]; var enemy = enemyLayer.addChild(new Enemy()); enemy.currentCellX = enemy.cellX = target.x; enemy.currentCellY = enemy.cellY = target.y; enemies.push(enemy); } // Update enemies for (var a = enemies.length - 1; a >= 0; a--) { if (grid.updateEnemy(enemies[a])) { enemyLayer.removeChild(enemies[a]); enemies.splice(a, 1); } } // Update towers for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update tower preview placement status towerPreview.checkPlacement(); };
===================================================================
--- original.js
+++ change.js
@@ -144,9 +144,9 @@
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 (i > 11 - 3 && i <= 11 + 3) {
if (j === 0) {
cellType = 2;
self.spawns.push(cell);
} else if (j <= 4) {
@@ -593,9 +593,9 @@
// 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(25, 29 + 5);
+var grid = new Grid(24, 29 + 6);
grid.x = 112;
grid.y = 112 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
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