URL to my (in-progress) game: http://shaunutter.com/code/tower/
I’m trying to create a game where you place “turrets” on a grid map and the enemy goes from the left side to the right side, finding the shortest path around the turrets. So if you arrange turrets like a maze, the enemy will go through the maze. The turrets fire “bullets” at the enemy when in range.
The issue is some bullets seem to stop moving before reaching the target. If you place one “turret” on the map, the last bullet fired stops after moving only a few pixels. It’s easier to see the issue when you place multiple turrets on the map.
//----------------------------------------------------------------------------------//
//--------------------------------- number values ----------------------------------//
//----------------------------------------------------------------------------------//
var tileWidth = 36;
var tileHeight = 36;
var mapWidth = 15; // number of tiles
var mapHeight = 15; // number of tiles
var imageWidth = 22; // turret and enemy images
var imageHeight = 22; // turret and enemy images
var bulletSize = 2;
var bulletSpeed = 4; // pixels bullet moves per interval
var turretRange = 96; // fires bullet when enemy in range
var hitRange = 10; // counts as hit when bullet in range
var fireRate = 20; // how many intervals until new bullet is fired
//----------------------------------------------------------------------------------//
//------------------------------------- colors -------------------------------------//
//----------------------------------------------------------------------------------//
var gridColor1 = "#000000";
var gridColor2 = "#0d0e0f";
var borderColor = "#17181a";
var turretTileColor = "#2e3133";
var textColor = "#5d6166";
var bulletColor1 = "#00ebb4";
var bulletColor2 = "#ff0066";
var bulletColor3 = "#6200ff";
var bulletColor4 = "#00a6ff";
var bulletColor5 = "#a100ff";
//----------------------------------------------------------------------------------//
//----------------------------------- variables ------------------------------------//
//----------------------------------------------------------------------------------//
var selectedTurretButton;
var selectedTurret;
var activeTurrets = [];
var activeBullets = [];
var enemy;
var enemyPos = 0;
var enemyActive = false;
var interval = null;
//----------------------------------------------------------------------------------//
//-------------------------- variables used to find path ---------------------------//
//----------------------------------------------------------------------------------//
var start = [(mapHeight-1)/2, 0];
var end = [(mapHeight-1)/2, mapWidth-1];
var path;
//----------------------------------------------------------------------------------//
//---------------------------- matrix used to find path ----------------------------//
//----------------------------------------------------------------------------------//
var matrix = [];
for (var i = 0; i < mapHeight; i++)
{
matrix.push(new Array(0));
}
var col = 0;
for (var m = 0; m < matrix.length; m++)
{
col = m;
for (var n = 0; n < mapWidth; n++)
{
matrix[col].push(0);
}
}
//----------------------------------------------------------------------------------//
//---------------------------- call find path function -----------------------------//
//----------------------------------------------------------------------------------//
path = findPath(start, end);
//----------------------------------------------------------------------------------//
//------------------------- find shortest path for enemies -------------------------//
//----------------------------------------------------------------------------------//
function findPath(position, end)
{
var temp = [];
for (var a = 0; a < matrix.length; a++)
{
temp[a] = matrix[a].slice();
}
var queue = [];
temp[position[0]][position[1]] = 1;
queue.push([position]);
while (queue.length > 0)
{
var path = queue.shift();
var pos = path[path.length-1];
var direction =
[
[pos[0]+1, pos[1]],
[pos[0], pos[1]+1],
[pos[0]-1, pos[1]],
[pos[0], pos[1]-1]
];
for (var d = 0; d < direction.length; d++)
{
if (direction[d][0] == end[0] && direction[d][1] == end[1])
{
return path.concat([end]);
}
if (direction[d][0]<0 || direction[d][0]>=temp.length || direction[d][1]<0 || direction[d][1]>=temp[0].length || temp[direction[d][0]][direction[d][1]]!=0)
{
continue;
}
temp[direction[d][0]][direction[d][1]] = 1;
queue.push(path.concat([direction[d]]));
}
}
}
//----------------------------------------------------------------------------------//
//---------------------------- create map and interface ----------------------------//
//----------------------------------------------------------------------------------//
function createMap()
{
// create borders
for (var b = 0; b < 6; b++)
{
var border = document.createElement("div");
border.style.position = "absolute";
border.style.backgroundColor = borderColor;
if (b < 2) // top, bottom borders
{
border.style.width = tileWidth*(mapWidth+1) + "px";
border.style.height = tileHeight/2 + "px";
}
else // two left, two right borders
{
border.style.width = tileWidth/2 + "px";
border.style.height = tileHeight*(mapHeight/2) + "px";
}
if (b > 3) // two right borders
{
border.style.left = tileWidth*(mapWidth+1) + "px";
}
else // top, bottom, two left borders
{
border.style.left = tileWidth/2 + "px";
}
if (b == 1) // bottom border
{
border.style.top = tileHeight*(mapHeight+1) + "px";
}
else if (b%2) // left-bottom, right-bottom borders
{
border.style.top = tileHeight*((mapHeight+3)/2) + "px";
}
else // top, left-top, right-top borders
{
border.style.top = tileHeight/2 + "px";
}
document.body.appendChild(border);
}
// create grid tiles
for (var w = 0; w < mapWidth; w++)
{
for (var h = 0; h < mapHeight; h++)
{
var tile = document.createElement("div");
tile.style.width = tileWidth + "px";
tile.style.height = tileHeight + "px";
tile.style.position = "absolute";
tile.style.left = tileWidth*(w+1) + "px";
tile.style.top = tileHeight*(h+1) + "px";
if (w%2 && h%2 || w%2 == 0 && h%2 == 0)
{
tile.style.backgroundColor = gridColor1;
}
else
{
tile.style.backgroundColor = gridColor2;
}
tile.setAttribute("xPos", w); // used to update the matrix when tile is filled with turret
tile.setAttribute("yPos", h); // used to update the matrix when tile is filled with turret
tile.setAttribute("filled", "false"); // used when placing turrets on tiles
tile.addEventListener("mouseover", tileMouseOver);
tile.addEventListener("click", tileClick);
document.body.appendChild(tile);
}
}
// create turret buttons
for (var t = 1; t < 6; t++)
{
var turretButton = document.createElement("div");
turretButton.style.width = tileWidth-2 + "px";
turretButton.style.height = tileHeight-2 + "px";
turretButton.style.position = "absolute";
turretButton.style.left = tileWidth*(mapWidth+t+1) + "px";
turretButton.style.top = tileHeight*2+1 + "px";
turretButton.style.backgroundColor = turretTileColor;
turretButton.setAttribute("id", "turretButton"+String(t)); // used when clicking on turret button thats already selected
turretButton.setAttribute("bulletColor", window["bulletColor"+String(t)]); // used when creating bullets
turretButton.addEventListener("click", turretButtonClick);
var img = document.createElement("img");
img.src = "images/turret-"+String(t)+".png";
img.style.width = imageWidth + "px";
img.style.height = imageHeight + "px";
img.style.position = "relative";
img.style.left = (tileWidth-2-imageWidth)/2 + "px";
img.style.top = (tileHeight-2-imageHeight)/2 + "px";
turretButton.appendChild(img);
document.body.appendChild(turretButton);
}
// create start button
var startButton = document.createElement("div");
startButton.style.width = tileWidth*5-2 + "px";
startButton.style.position = "absolute";
startButton.style.left = tileWidth*(mapWidth+2) + "px";
startButton.style.top = tileHeight/2 + "px";
startButton.style.backgroundColor = borderColor;
startButton.innerHTML = "START";
startButton.style.padding = "9px 0 10px 0";
startButton.style.color = textColor;
startButton.style.textAlign = "center";
startButton.style.cursor = "default";
startButton.id = "startButton";
startButton.addEventListener("click", startButtonClick);
document.body.appendChild(startButton);
}
//----------------------------------------------------------------------------------//
//----------------------------- placing turrets on map -----------------------------//
//----------------------------------------------------------------------------------//
function turretButtonClick()
{
if (selectedTurret == null) // if no turret selected
{
// select turret button
selectedTurretButton = this;
selectedTurretButton.style.outline = "2px solid " + textColor;
// create copy of turret to be placed on map
selectedTurret = this.cloneNode(true);
selectedTurret.style.pointerEvents = "none";
document.body.appendChild(selectedTurret);
}
else if (this.getAttribute("id") == selectedTurret.getAttribute("id")) // if turret button already selected
{
// deselect turret button
this.style.outline = "none";
document.body.removeChild(selectedTurret);
selectedTurretButton = null;
selectedTurret = null;
}
}
function tileMouseOver()
{
if (selectedTurret && this.getAttribute("filled") == "false") // if turret selected and tile empty
{
// move turret to this tile
selectedTurret.style.left = parseInt(this.style.left)+1 + "px";
selectedTurret.style.top = parseInt(this.style.top)+1 + "px";
}
}
function tileClick()
{
if (selectedTurret && this.getAttribute("filled") == "false") // if turret selected and tile empty
{
// make turret active, deselect turret and turret button
activeTurrets.push(selectedTurret);
selectedTurretButton.style.outline = "none";
selectedTurretButton = null;
selectedTurret.setAttribute("fireCount", 0);
selectedTurret.style.outline = "none";
selectedTurret = null;
// set tile to filled, update matrix and enemy path
this.setAttribute("filled", "true");
matrix[this.getAttribute("yPos")][this.getAttribute("xPos")] = 1;
path = findPath(path[enemyPos], end);
enemyPos = 0;
}
}
//----------------------------------------------------------------------------------//
//------------------------ on start button click, add enemy ------------------------//
//----------------------------------------------------------------------------------//
function startButtonClick()
{
// disable start button
this.style.pointerEvents = "none";
this.style.opacity = 0.5;
// create enemy
enemy = document.createElement("img");
enemy.src = "images/enemy.png";
enemy.style.width = imageWidth + "px";
enemy.style.height = imageHeight + "px";
enemy.style.position = "absolute";
enemy.style.left = (tileWidth-imageWidth)/2 + "px";
enemy.style.top = tileHeight*(mapHeight/2+1)-imageHeight/2 + "px";
enemy.style.pointerEvents = "none";
document.body.appendChild(enemy);
enemyActive = true;
path = findPath(start, end); // find enemy path
interval = setInterval(function(){moveStuff();}, 10); // set interval for moving enemies and bullets
}
//----------------------------------------------------------------------------------//
//---------------------------- move enemies and bullets ----------------------------//
//----------------------------------------------------------------------------------//
function moveStuff()
{
var currentX = parseInt(enemy.style.left);
var currentY = parseInt(enemy.style.top);
// move enemy along path
if (enemyPos < path.length) // if enemy has not completed path
{
var targetX = tileWidth*(path[enemyPos][1]+1)+(tileWidth-imageWidth)/2;
var targetY = tileHeight*(path[enemyPos][0]+1)+(tileHeight-imageHeight)/2;
if (currentX < targetX)
{
currentX++;
enemy.style.left = currentX + "px";
}
else if (currentX > targetX)
{
currentX--;
enemy.style.left = currentX + "px";
}
else if (currentY < targetY)
{
currentY++;
enemy.style.top = currentY + "px";
}
else if (currentY > targetY)
{
currentY--;
enemy.style.top = currentY + "px";
}
if (currentX == targetX && currentY == targetY) // if enemy reached target tile
{
enemyPos++; // set enemy to next position in path
}
}
else // enemy completed path
{
if (currentX == tileWidth*(mapWidth+1)+(tileWidth-imageWidth)/2) // if enemy reached final off-map tile
{
// remove enemy and interval
enemyActive = false;
clearInterval(interval);
document.body.removeChild(enemy);
enemyPos = 0;
// reset start button
document.getElementById("startButton").style.pointerEvents = "auto";
document.getElementById("startButton").style.opacity = 1;
}
else // enemy has not reached final off-map tile
{
// move enemy to the right
currentX++;
enemy.style.left = currentX + "px";
}
}
// create bullets
for (var s = 0; s < activeTurrets.length; s++)
{
var turret = activeTurrets[s];
var turretX = parseInt(turret.style.left);
var turretY = parseInt(turret.style.top);
var turretWidth = parseInt(turret.style.width);
var turretHeight = parseInt(turret.style.height);
var turretFireCount = parseInt(turret.getAttribute("fireCount"))
// if enemy active and fire count is 0 and enemy in range of turret
if (enemyActive && turretFireCount==0 && Math.abs(turretX-currentX)<turretRange && Math.abs(turretY-currentY)<turretRange)
{
// create bullet
var bullet = document.createElement("div");
bullet.style.width = bulletSize + "px";
bullet.style.height = bulletSize + "px";
bullet.style.position = "absolute";
bullet.style.left = turretX+turretWidth/2-bulletSize/2 + "px";
bullet.style.top = turretY+turretHeight/2-bulletSize/2 + "px";
bullet.style.backgroundColor = turret.getAttribute("bulletColor");
bullet.style.pointerEvents = "none";
activeBullets.push(bullet);
document.body.appendChild(bullet);
}
if (turretFireCount == fireRate) // if fire count equals fire rate
{
// reset fire count to 0
turret.setAttribute("fireCount", 0);
}
else // fire count does not equal fire rate
{
// increase fire count by 1
turret.setAttribute("fireCount", turretFireCount+1);
}
}
// move bullets
for (var u = 0; u < activeBullets.length; u++)
{
var currentBullet = activeBullets[u];
var bulletX = parseInt(currentBullet.style.left);
var bulletY = parseInt(currentBullet.style.top);
var enemyX = parseInt(enemy.style.left) + parseInt(enemy.style.width)/2;
var enemyY = parseInt(enemy.style.top) + parseInt(enemy.style.height)/2;
// if enemy not active or bullet hit target
if (enemyActive == "false" || Math.abs(bulletX-enemyX) < hitRange && Math.abs(bulletY-enemyY) < hitRange)
{
// remove bullet
document.body.removeChild(currentBullet);
activeBullets.splice(u);
}
else // enemy is active and bullet has not hit target
{
// move bullet
var differenceX = enemyX-bulletX;
var differenceY = enemyY-bulletY;
var distance = Math.sqrt(differenceX*differenceX + differenceY*differenceY);
if (distance <= bulletSpeed) // if distance between bullet and enemy less than or equal to bullet speed
{
// move bullet directly to enemy
currentBullet.style.left = enemyX + 'px';
currentBullet.style.top = enemyY + 'px';
}
else
{
// move bullet towards enemy
var moveX = differenceX/distance*bulletSpeed;
var moveY = differenceY/distance*bulletSpeed;
currentBullet.style.left = (bulletX+moveX) + 'px';
currentBullet.style.top = (bulletY+moveY) + 'px';
}
}
}
}
//----------------------------------------------------------------------------------//
//--------------------------- on window load, create map ---------------------------//
//----------------------------------------------------------------------------------//
window.onload = function()
{
createMap();
}
<html>
<head>
<title>Tower Defense</title>
<style>
body
{
background-color: #000000;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: bold;
font-size: 14px;
}
</style>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
</body>
</html>


