Moving Tank Example
Firstly, I have a major thanks to say to the members of Stack Overflow. Their contributions and helpfulness have been absolutely splendid in regards to this little demo.
In particular, one member suggested I take a look at finite state machines. I’ve tried to implement some of the concepts in the code, but I’m not entirely sure I’ve gotten the hang of them yet. They do interest me, and programming with finite state machine methodology is definitely something I find exciting.
Current Bugs/Issues
I’ll fix these at some point in the future.
- Pressing two arrow keys at once causes the tank to move diagonally and out of control.
- Holding down an arrow key for more than three seconds causes the tank to still be moving after the arrow key has lifted. I need to make sure subsequent arrow key presses have no effect until the current animation is complete.
- No physics whatsoever. The tank can move through walls.
- No border on the map. The tank can move off the canvas and never come back.
The JavaScript
This is the code taken from the canvasApp.js source:
$(document).ready(function() { // Uses Modernizr.js to check for canvas support function canvasSupport() { return Modernizr.canvas; } canvasApp(); function canvasApp() { // Check for canvas support if (!canvasSupport()) { return; } // Grab the canvas and set the context to 2d var theCanvas = $("#canvasOne"); var context = theCanvas.get(0).getContext("2d"); // Load the tile sheet var tileSheet = new Image(); tileSheet.addEventListener('load', startUp, false); tileSheet.src = "./img/tanks_sheet.png"; var mapIndexOffset = -1; // Our array is 0 relative while our tilesheet array is not var mapRows = 10; var mapCols = 10; var tileWidth = 32; var tileHeight = 32; var tilesPerRow = 8; // Tile map array data exported from Tiled as a tilesheet var tileMap = [ [32,31,31,31,1,31,31,31,31,32], [1,1,1,1,1,1,1,1,1,1], [32,1,26,1,26,1,26,1,1,32], [32,26,1,1,26,1,1,26,1,32], [32,1,1,1,26,26,1,26,1,32], [32,1,1,26,1,1,1,26,1,32], [32,1,1,1,1,1,1,26,1,32], [1,1,26,1,26,1,26,1,1,1], [32,1,1,1,1,1,1,1,1,32], [32,31,31,31,1,31,31,31,31,32] ]; // Tank animation frames var animationFrames = [1,2,3,4,5,6,7,8]; // Counter to keep track of the current index of animationFrames var frameIndex = 0; // Tank tiles var tankSourceX = Math.floor(animationFrames[frameIndex] % tilesPerRow) * tileWidth; var tankSourceY = Math.floor(animationFrames[frameIndex] / tilesPerRow) * tileHeight; // Tank position and movement var tankX = 32; var tankY = 32; var tankMoveX = 0; var tankMoveY = 0; // Tank state, direction, and rotation angle (used for rotation later) var tankState = "stopped"; var tankDir = "up"; var rotationAngle = 0; var rotationAngleRad = 0; // rotation angle in radians // Keyboard movement (arrow keys) event listener/handler document.onkeydown = function(e) { e = e?e:window.event; switch (e.keyCode) { // up case 38: if (tankState == "stopped") { if (tankDir != "up") { dirTank("up"); } moveTank("up"); } break; // down case 40: if (tankState == "stopped") { if (tankDir != "down") { dirTank("down"); } moveTank("down"); } break; // right case 39: if (tankState == "stopped") { if (tankDir != "right") { dirTank("right"); } moveTank("right"); } break; // left case 37: if (tankState == "stopped") { if (tankDir != "left") { dirTank("left"); } moveTank("left"); } break; } } function animateMovement() { // Animation frames if (tankState == "moving") { frameIndex += 1; if (frameIndex == animationFrames.length) { frameIndex = 0; } tankSourceX = Math.floor(animationFrames[frameIndex] % tilesPerRow) * tileWidth; tankSourceY = Math.floor(animationFrames[frameIndex] / tilesPerRow) * tileHeight; } } function moveTank(dir) { // Make sure tank only moves a certain number of animations per arrow press var steps = 0; var int = setInterval(function() { tankState = "moving"; steps += 1; if (dir == "up") { tankMoveY = -4; } else if (dir == "down") { tankMoveY = 4; } else if (dir == "left") { tankMoveX = -4; } else if (dir == "right") { tankMoveX = 4; } tankX += tankMoveX; tankY += tankMoveY; animateMovement(); drawScreen(); },120); if (steps = 4) { setTimeout(function() { clearInterval(int); tankMoveX = 0; tankMoveY = 0; tankState = "stopped"; }, 1000); } } function dirTank(dir) { switch (dir) { case "up": if (tankDir == "right") { rotationAngle += -90; } else if (tankDir == "down") { rotationAngle += 180; } else if (tankDir == "left") { rotationAngle += 90; } break; case "down": if (tankDir == "up") { rotationAngle += 180; } else if (tankDir == "right") { rotationAngle += 90; } else if (tankDir == "left") { rotationAngle += -90; } break; case "left": if (tankDir == "up") { rotationAngle += -90; } else if (tankDir == "right") { rotationAngle += 180; } else if (tankDir == "down") { rotationAngle += 90; } break; case "right": if (tankDir == "up") { rotationAngle += 90; } else if (tankDir == "down") { rotationAngle += -90; } else if (tankDir == "left") { rotationAngle += 180; } break; } tankDir = dir; rotationAngle %= 360; } function startUp() { drawScreen(); } function drawScreen() { // Tile map for (var rowCtr = 0; rowCtr < mapRows; rowCtr += 1) { for (var colCtr = 0; colCtr < mapCols; colCtr += 1) { var tileId = tileMap[rowCtr][colCtr] + mapIndexOffset; var sourceX = Math.floor(tileId % tilesPerRow) * tileWidth; var sourceY = Math.floor(tileId / tilesPerRow) * tileHeight; context.drawImage(tileSheet, sourceX, sourceY, tileWidth, tileHeight, colCtr * tileWidth, rowCtr * tileHeight, tileWidth, tileHeight); } } // Draw the tank context.save(); context.setTransform(1,0,0,1,0,0); // identity matrix context.translate(tankX + tileWidth/2, tankY + tileHeight/2); rotationAngleRad = rotationAngle * Math.PI/180; context.rotate(rotationAngleRad); context.drawImage(tileSheet, tankSourceX, tankSourceY, tileWidth, tileHeight, -tileWidth/2, -tileHeight/2, tileWidth, tileHeight); context.restore(); } } });
The Demo
You can find the current working version here: https://www.zesix.com/html5/movingTankExample