Using PNG Tilesheets for Graphics
canvasgamedevtilemaptutorialgraphicsjavascripthtmlarchivedDefining Sprite dimensions
Chances are, we may not want our map tiles to simply be blocks of colour. In this lesson, we'll be replacing our basic drawRect calls to make use of a tileset - an image containing sprites for all the different tile Types.
We'll be building on the previous tutorial, and will also need to put our tileset PNG image in the same folder as our HTML document. You can download this image here.
We'll begin by adding several more global variables:
var tileset = null, tilesetURL = "tileset.jpg", tilesetLoaded = false;
The tileset will store our image once it is loaded by the script, which our script will look for at the address specified by tilesetURL. If you put the image in a subfolder, or rename the file, you'll need to change the value of this variable accordingly. Finally, tilesetLoaded is simply a boolean flag which we change to true on successful loading of the image.
We also want to change our tileTypes variable. Each entry will now also have a sprite property, which is an array that currently contains one entry for each tile type: an object with the properties x and y, which states where the tiles image is on the tilesheet, and a w and h property which is the width and height of the area of the tilesheet we'll be drawing for each tile (which is 40x40 pixels, the same as the tileW and tileH variables.
var tileTypes = {
0 : { colour:"#685b48", floor:floorTypes.solid, sprite:[{x:0,y:0,w:40,h:40}] },
1 : { colour:"#5aa457", floor:floorTypes.path, sprite:[{x:40,y:0,w:40,h:40}] },
2 : { colour:"#e8bd7a", floor:floorTypes.path, sprite:[{x:80,y:0,w:40,h:40}] },
3 : { colour:"#286625", floor:floorTypes.solid, sprite:[{x:120,y:0,w:40,h:40}] },
4 : { colour:"#678fd9", floor:floorTypes.water, sprite:[{x:160,y:0,w:40,h:40}] }
};
We're making the sprite property and Array, despite the fact each tile currently only has one entry, to prepare for supporting animations.
Directions
In-game Characters, and also objects, items, projectiles, and other features we may add might often require directionality. To allow this, we'll begin by creating an object to represent the 4 cardinal directions. Once again, in other languages we may use an enum to achieve this, but in Javascript we're just using a simple object.
var directions = {
up : 0,
right : 1,
down : 2,
left : 3
};
We could simply type the numbers 0-3 anywhere we want to represent a direction, but this method is much more readable.
We'll also extend our Character class so each Character can keep track of their current direction, by adding a simple direction property:
this.direction = directions.up;
We also need to add sprites for Characters. We'll extend the Character class further, by creating a new property sprites, and adding an entry for each direction. Once again, the entries will be Arrays with single entries, containing the x, y and w, h information for the sprite to use from the tilesheet.
this.sprites = {};
this.sprites[directions.up] = [{x:0,y:120,w:30,h:30}];
this.sprites[directions.right] = [{x:0,y:150,w:30,h:30}];
this.sprites[directions.down] = [{x:0,y:180,w:30,h:30}];
this.sprites[directions.left] = [{x:0,y:210,w:30,h:30}];
Now we want Characters to change their direction when they move - we'll do this by extending the moveUp, moveLeft, etc., Character methods to set the Character direction property to the appropriate directions value:
Character.prototype.moveLeft = function(t) { this.tileTo[0]-=1; this.timeMoved = t; this.direction = directions.left; };
Character.prototype.moveRight = function(t) { this.tileTo[0]+=1; this.timeMoved = t; this.direction = directions.right; };
Character.prototype.moveUp = function(t) { this.tileTo[1]-=1; this.timeMoved = t; this.direction = directions.up; };
Character.prototype.moveDown = function(t) { this.tileTo[1]+=1; this.timeMoved = t; this.direction = directions.down; };
The code simply sets this.direction to directions.up in the moveUp method, and so on for each of the 4 directions.
Loading PNG and using drawImage
Loading the tileset

We now need to load our new asset, the tileset image. We need to wait until the page is in a suitable state to do this, so we'll be adding it to the windows onload method. We'll start by creating a new Image object and assigning it to the tileset variable...
tileset = new Image();
Before we tell the Image where we want to load the asset from, we're going to set up a couple of short methods to handle the possible outcomes. First of all, we'll handle an erroneous outcome, in which the image fails to load:
tileset.onerror = function()
{
ctx = null;
alert("Failed loading tileset.");
};
In this event we're doing two things:
- Destroying the ctx Canvas reference; this means next time our drawGame method is called, it'll exit without requesting another frame, so essentially our script halts.
- Showing a message box to alert the user a problem has occured.
Secondly, we'll handle the positive outcome; our tileset loads as expected. In this case we're simply changing our tilesetLoaded flag to true to let the game begin rendering:
tileset.onload = function() { tilesetLoaded = true; };
Now we can go ahead and tell our tileset variable where to fetch the image from, which it'll begin doing straight away.
tileset.src = tilesetURL;
Modifying the drawGame method
Before we change our drawing code, we're going to add another check at the beginning of the drawGame function/method, immediately after our test to see if the ctx context is null. Our new check will see if our asset (tileset) has loaded, and if not tell the browser to let us know when we can try drawing again, and immediately exit the function.
if(!tilesetLoaded) { requestAnimationFrame(drawGame); return; }
Inside our y,x tile drawing loops, we'll completely do away with the fillStyle, fillRect code we have, and instead do two new things: we'll create a temporary variable tile, in which we'll store the tileType of the tile at the current x,y position, fram the gameMap array:
var tile = tileTypes[gameMap[toIndex(x,y)]];
This is to make it simpler to then get the sprite data for this tileType for our drawImage call, which will draw the tile sprite to the Canvas.
ctx.drawImage(tileset,
tile.sprite[0].x, tile.sprite[0].y, tile.sprite[0].w, tile.sprite[0].h,
viewport.offset[0] + (x*tileW), viewport.offset[1] + (y*tileH),
tileW, tileH);
More information about this function can be found in the Mozilla documentation, but essentially the argument list we're passing is as follows: the tileset Image, the x, y position of the tile sprite, and the w, h dimensions, the position of the tile on the Canvas, updated for the viewport offsets, and finally the tileW, tileH tile dimensions for drawing.
We'll also do the same for the player, after the y,x tile drawing loops. First, we'll use a temporary variable to get a reference to the player sprite data, accounting for the direction property of the player:
var sprite = player.sprites[player.direction];
Which allows us to draw the new Character sprite, instead of the filled rectangle we were drawing before for our Character.
ctx.drawImage(tileset,
sprite[0].x, sprite[0].y, sprite[0].w, sprite[0].h,
viewport.offset[0] + player.position[0], viewport.offset[1] + player.position[1],
player.dimensions[0], player.dimensions[1]);
Save and load this up!
Modified source code
<!DOCTYPE html> <html> <head>
<script type="text/javascript"> var ctx = null; var gameMap = [ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 3, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 3, 1, 4, 4, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 0, 0, 2, 3, 1, 1, 4, 4, 1, 2, 3, 3, 2, 1, 1, 2, 1, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 2, 4, 4, 4, 4, 4, 1, 1, 1, 2, 2, 2, 2, 1, 0, 0, 1, 1, 1, 1, 2, 3, 2, 1, 1, 4, 1, 1, 1, 1, 3, 3, 2, 1, 0, 0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 2, 1, 1, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4, 0, 1, 2, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0, 0, 1, 2, 3, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 0, 0, 3, 2, 3, 4, 4, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 1, 2, 1, 0, 0, 3, 2, 3, 4, 4, 3, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 0, 0, 3, 2, 3, 4, 1, 3, 2, 1, 3, 1, 1, 1, 2, 1, 1, 1, 2, 3, 0, 0, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 1, 1, 2, 2, 2, 2, 2, 3, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; var tileW = 40, tileH = 40; var mapW = 20, mapH = 20; var currentSecond = 0, frameCount = 0, framesLastSecond = 0, lastFrameTime = 0;var tileset = null, tilesetURL = "tileset.jpg", tilesetLoaded = false;
var floorTypes = { solid : 0, path : 1, water : 2 };var tileTypes = { 0 : { colour:"#685b48", floor:floorTypes.solid, sprite:[{x:0,y:0,w:40,h:40}] }, 1 : { colour:"#5aa457", floor:floorTypes.path, sprite:[{x:40,y:0,w:40,h:40}] }, 2 : { colour:"#e8bd7a", floor:floorTypes.path, sprite:[{x:80,y:0,w:40,h:40}] }, 3 : { colour:"#286625", floor:floorTypes.solid, sprite:[{x:120,y:0,w:40,h:40}] }, 4 : { colour:"#678fd9", floor:floorTypes.water, sprite:[{x:160,y:0,w:40,h:40}] } }; var directions = { up : 0, right : 1, down : 2, left : 3 };
var keysDown = { 37 : false, 38 : false, 39 : false, 40 : false }; var viewport = { screen : [0,0], startTile : [0,0], endTile : [0,0], offset : [0,0], update : function(px, py) { this.offset[0] = Math.floor((this.screen[0]/2) - px); this.offset[1] = Math.floor((this.screen[1]/2) - py); var tile = [ Math.floor(px/tileW), Math.floor(py/tileH) ]; this.startTile[0] = tile[0] - 1 - Math.ceil((this.screen[0]/2) / tileW); this.startTile[1] = tile[1] - 1 - Math.ceil((this.screen[1]/2) / tileH); if(this.startTile[0] < 0) { this.startTile[0] = 0; } if(this.startTile[1] < 0) { this.startTile[1] = 0; } this.endTile[0] = tile[0] + 1 + Math.ceil((this.screen[0]/2) / tileW); this.endTile[1] = tile[1] + 1 + Math.ceil((this.screen[1]/2) / tileH); if(this.endTile[0] >= mapW) { this.endTile[0] = mapW-1; } if(this.endTile[1] >= mapH) { this.endTile[1] = mapH-1; } } }; var player = new Character(); function Character() { this.tileFrom = [1,1]; this.tileTo = [1,1]; this.timeMoved = 0; this.dimensions = [30,30]; this.position = [45,45]; this.delayMove = 700;this.direction = directions.up; this.sprites = {}; this.sprites[directions.up] = [{x:0,y:120,w:30,h:30}]; this.sprites[directions.right] = [{x:0,y:150,w:30,h:30}]; this.sprites[directions.down] = [{x:0,y:180,w:30,h:30}]; this.sprites[directions.left] = [{x:0,y:210,w:30,h:30}];
} Character.prototype.placeAt = function(x, y) { this.tileFrom = [x,y]; this.tileTo = [x,y]; this.position = [((tileW*x)+((tileW-this.dimensions[0])/2)), ((tileH*y)+((tileH-this.dimensions[1])/2))]; }; Character.prototype.processMovement = function(t) { if(this.tileFrom[0]==this.tileTo[0] && this.tileFrom[1]==this.tileTo[1]) { return false; } if((t-this.timeMoved)>=this.delayMove) { this.placeAt(this.tileTo[0], this.tileTo[1]); } else { this.position[0] = (this.tileFrom[0] * tileW) + ((tileW-this.dimensions[0])/2); this.position[1] = (this.tileFrom[1] * tileH) + ((tileH-this.dimensions[1])/2); if(this.tileTo[0] != this.tileFrom[0]) { var diff = (tileW / this.delayMove) * (t-this.timeMoved); this.position[0]+= (this.tileTo[0]<this.tileFrom[0] ? 0 - diff : diff); } if(this.tileTo[1] != this.tileFrom[1]) { var diff = (tileH / this.delayMove) * (t-this.timeMoved); this.position[1]+= (this.tileTo[1]<this.tileFrom[1] ? 0 - diff : diff); } this.position[0] = Math.round(this.position[0]); this.position[1] = Math.round(this.position[1]); } return true; } Character.prototype.canMoveTo = function(x, y) { if(x < 0 || x >= mapW || y < 0 || y >= mapH) { return false; } if(tileTypes[gameMap[toIndex(x,y)]].floor!=floorTypes.path) { return false; } return true; }; Character.prototype.canMoveUp = function() { return this.canMoveTo(this.tileFrom[0], this.tileFrom[1]-1); }; Character.prototype.canMoveDown = function() { return this.canMoveTo(this.tileFrom[0], this.tileFrom[1]+1); }; Character.prototype.canMoveLeft = function() { return this.canMoveTo(this.tileFrom[0]-1, this.tileFrom[1]); }; Character.prototype.canMoveRight = function() { return this.canMoveTo(this.tileFrom[0]+1, this.tileFrom[1]); };Character.prototype.moveLeft = function(t) { this.tileTo[0]-=1; this.timeMoved = t; this.direction = directions.left; }; Character.prototype.moveRight = function(t) { this.tileTo[0]+=1; this.timeMoved = t; this.direction = directions.right; }; Character.prototype.moveUp = function(t) { this.tileTo[1]-=1; this.timeMoved = t; this.direction = directions.up; }; Character.prototype.moveDown = function(t) { this.tileTo[1]+=1; this.timeMoved = t; this.direction = directions.down; };
function toIndex(x, y) { return((y * mapW) + x); } window.onload = function() { ctx = document.getElementById('game').getContext("2d"); requestAnimationFrame(drawGame); ctx.font = "bold 10pt sans-serif"; window.addEventListener("keydown", function(e) { if(e.keyCode>=37 && e.keyCode<=40) { keysDown[e.keyCode] = true; } }); window.addEventListener("keyup", function(e) { if(e.keyCode>=37 && e.keyCode<=40) { keysDown[e.keyCode] = false; } }); viewport.screen = [document.getElementById('game').width, document.getElementById('game').height];tileset = new Image(); tileset.onerror = function() { ctx = null; alert("Failed loading tileset."); }; tileset.onload = function() { tilesetLoaded = true; }; tileset.src = tilesetURL;
}; function drawGame() { if(ctx==null) { return; }if(!tilesetLoaded) { requestAnimationFrame(drawGame); return; }
var currentFrameTime = Date.now(); var timeElapsed = currentFrameTime - lastFrameTime; var sec = Math.floor(Date.now()/1000); if(sec!=currentSecond) { currentSecond = sec; framesLastSecond = frameCount; frameCount = 1; } else { frameCount++; } if(!player.processMovement(currentFrameTime)) { if(keysDown[38] && player.canMoveUp()) { player.moveUp(currentFrameTime); } else if(keysDown[40] && player.canMoveDown()) { player.moveDown(currentFrameTime); } else if(keysDown[37] && player.canMoveLeft()) { player.moveLeft(currentFrameTime); } else if(keysDown[39] && player.canMoveRight()) { player.moveRight(currentFrameTime); } } viewport.update(player.position[0] + (player.dimensions[0]/2), player.position[1] + (player.dimensions[1]/2)); ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, viewport.screen[0], viewport.screen[1]); for(var y = viewport.startTile[1]; y <= viewport.endTile[1]; ++y) { for(var x = viewport.startTile[0]; x <= viewport.endTile[0]; ++x) {var tile = tileTypes[gameMap[toIndex(x,y)]]; ctx.drawImage(tileset, tile.sprite[0].x, tile.sprite[0].y, tile.sprite[0].w, tile.sprite[0].h, viewport.offset[0] + (x*tileW), viewport.offset[1] + (y*tileH), tileW, tileH);
} }var sprite = player.sprites[player.direction]; ctx.drawImage(tileset, sprite[0].x, sprite[0].y, sprite[0].w, sprite[0].h, viewport.offset[0] + player.position[0], viewport.offset[1] + player.position[1], player.dimensions[0], player.dimensions[1]);
ctx.fillStyle = "#ff0000"; ctx.fillText("FPS: " + framesLastSecond, 10, 20); lastFrameTime = currentFrameTime; requestAnimationFrame(drawGame); } </script> </head> <body> <canvas id="game" width="400" height="400"></canvas> </body> </html>
PNG Tilesheet source code
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var ctx = null;
var gameMap = [
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 2, 2, 0,
0, 2, 3, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0,
0, 2, 3, 1, 4, 4, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 0,
0, 2, 3, 1, 1, 4, 4, 1, 2, 3, 3, 2, 1, 1, 2, 1, 0, 0, 0, 0,
0, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 2, 4, 4, 4, 4, 4, 1, 1, 1, 2, 2, 2, 2, 1, 0,
0, 1, 1, 1, 1, 2, 3, 2, 1, 1, 4, 1, 1, 1, 1, 3, 3, 2, 1, 0,
0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 3, 2, 1, 0,
0, 1, 2, 3, 3, 2, 1, 2, 1, 1, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4,
0, 1, 2, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0,
0, 1, 2, 3, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 0,
0, 3, 2, 3, 4, 4, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 1, 2, 1, 0,
0, 3, 2, 3, 4, 4, 3, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 0,
0, 3, 2, 3, 4, 1, 3, 2, 1, 3, 1, 1, 1, 2, 1, 1, 1, 2, 3, 0,
0, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 1, 1, 2, 2, 2, 2, 2, 3, 0,
0, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
var tileW = 40, tileH = 40;
var mapW = 20, mapH = 20;
var currentSecond = 0, frameCount = 0, framesLastSecond = 0, lastFrameTime = 0;
var tileset = null, tilesetURL = "tileset.jpg", tilesetLoaded = false;
var floorTypes = {
solid : 0,
path : 1,
water : 2
};
var tileTypes = {
0 : { colour:"#685b48", floor:floorTypes.solid, sprite:[{x:0,y:0,w:40,h:40}] },
1 : { colour:"#5aa457", floor:floorTypes.path, sprite:[{x:40,y:0,w:40,h:40}] },
2 : { colour:"#e8bd7a", floor:floorTypes.path, sprite:[{x:80,y:0,w:40,h:40}] },
3 : { colour:"#286625", floor:floorTypes.solid, sprite:[{x:120,y:0,w:40,h:40}] },
4 : { colour:"#678fd9", floor:floorTypes.water, sprite:[{x:160,y:0,w:40,h:40}] }
};
var directions = {
up : 0,
right : 1,
down : 2,
left : 3
};
var keysDown = {
37 : false,
38 : false,
39 : false,
40 : false
};
var viewport = {
screen : [0,0],
startTile : [0,0],
endTile : [0,0],
offset : [0,0],
update : function(px, py) {
this.offset[0] = Math.floor((this.screen[0]/2) - px);
this.offset[1] = Math.floor((this.screen[1]/2) - py);
var tile = [ Math.floor(px/tileW), Math.floor(py/tileH) ];
this.startTile[0] = tile[0] - 1 - Math.ceil((this.screen[0]/2) / tileW);
this.startTile[1] = tile[1] - 1 - Math.ceil((this.screen[1]/2) / tileH);
if(this.startTile[0] < 0) { this.startTile[0] = 0; }
if(this.startTile[1] < 0) { this.startTile[1] = 0; }
this.endTile[0] = tile[0] + 1 + Math.ceil((this.screen[0]/2) / tileW);
this.endTile[1] = tile[1] + 1 + Math.ceil((this.screen[1]/2) / tileH);
if(this.endTile[0] >= mapW) { this.endTile[0] = mapW-1; }
if(this.endTile[1] >= mapH) { this.endTile[1] = mapH-1; }
}
};
var player = new Character();
function Character()
{
this.tileFrom = [1,1];
this.tileTo = [1,1];
this.timeMoved = 0;
this.dimensions = [30,30];
this.position = [45,45];
this.delayMove = 700;
this.direction = directions.up;
this.sprites = {};
this.sprites[directions.up] = [{x:0,y:120,w:30,h:30}];
this.sprites[directions.right] = [{x:0,y:150,w:30,h:30}];
this.sprites[directions.down] = [{x:0,y:180,w:30,h:30}];
this.sprites[directions.left] = [{x:0,y:210,w:30,h:30}];
}
Character.prototype.placeAt = function(x, y)
{
this.tileFrom = [x,y];
this.tileTo = [x,y];
this.position = [((tileW*x)+((tileW-this.dimensions[0])/2)),
((tileH*y)+((tileH-this.dimensions[1])/2))];
};
Character.prototype.processMovement = function(t)
{
if(this.tileFrom[0]==this.tileTo[0] && this.tileFrom[1]==this.tileTo[1]) { return false; }
if((t-this.timeMoved)>=this.delayMove)
{
this.placeAt(this.tileTo[0], this.tileTo[1]);
}
else
{
this.position[0] = (this.tileFrom[0] * tileW) + ((tileW-this.dimensions[0])/2);
this.position[1] = (this.tileFrom[1] * tileH) + ((tileH-this.dimensions[1])/2);
if(this.tileTo[0] != this.tileFrom[0])
{
var diff = (tileW / this.delayMove) * (t-this.timeMoved);
this.position[0]+= (this.tileTo[0]<this.tileFrom[0] ? 0 - diff : diff);
}
if(this.tileTo[1] != this.tileFrom[1])
{
var diff = (tileH / this.delayMove) * (t-this.timeMoved);
this.position[1]+= (this.tileTo[1]<this.tileFrom[1] ? 0 - diff : diff);
}
this.position[0] = Math.round(this.position[0]);
this.position[1] = Math.round(this.position[1]);
}
return true;
}
Character.prototype.canMoveTo = function(x, y)
{
if(x < 0 || x >= mapW || y < 0 || y >= mapH) { return false; }
if(tileTypes[gameMap[toIndex(x,y)]].floor!=floorTypes.path) { return false; }
return true;
};
Character.prototype.canMoveUp = function() { return this.canMoveTo(this.tileFrom[0], this.tileFrom[1]-1); };
Character.prototype.canMoveDown = function() { return this.canMoveTo(this.tileFrom[0], this.tileFrom[1]+1); };
Character.prototype.canMoveLeft = function() { return this.canMoveTo(this.tileFrom[0]-1, this.tileFrom[1]); };
Character.prototype.canMoveRight = function() { return this.canMoveTo(this.tileFrom[0]+1, this.tileFrom[1]); };
Character.prototype.moveLeft = function(t) { this.tileTo[0]-=1; this.timeMoved = t; this.direction = directions.left; };
Character.prototype.moveRight = function(t) { this.tileTo[0]+=1; this.timeMoved = t; this.direction = directions.right; };
Character.prototype.moveUp = function(t) { this.tileTo[1]-=1; this.timeMoved = t; this.direction = directions.up; };
Character.prototype.moveDown = function(t) { this.tileTo[1]+=1; this.timeMoved = t; this.direction = directions.down; };
function toIndex(x, y)
{
return((y * mapW) + x);
}
window.onload = function()
{
ctx = document.getElementById('game').getContext("2d");
requestAnimationFrame(drawGame);
ctx.font = "bold 10pt sans-serif";
window.addEventListener("keydown", function(e) {
if(e.keyCode>=37 && e.keyCode<=40) { keysDown[e.keyCode] = true; }
});
window.addEventListener("keyup", function(e) {
if(e.keyCode>=37 && e.keyCode<=40) { keysDown[e.keyCode] = false; }
});
viewport.screen = [document.getElementById('game').width,
document.getElementById('game').height];
tileset = new Image();
tileset.onerror = function()
{
ctx = null;
alert("Failed loading tileset.");
};
tileset.onload = function() { tilesetLoaded = true; };
tileset.src = tilesetURL;
};
function drawGame()
{
if(ctx==null) { return; }
if(!tilesetLoaded) { requestAnimationFrame(drawGame); return; }
var currentFrameTime = Date.now();
var timeElapsed = currentFrameTime - lastFrameTime;
var sec = Math.floor(Date.now()/1000);
if(sec!=currentSecond)
{
currentSecond = sec;
framesLastSecond = frameCount;
frameCount = 1;
}
else { frameCount++; }
if(!player.processMovement(currentFrameTime))
{
if(keysDown[38] && player.canMoveUp()) { player.moveUp(currentFrameTime); }
else if(keysDown[40] && player.canMoveDown()) { player.moveDown(currentFrameTime); }
else if(keysDown[37] && player.canMoveLeft()) { player.moveLeft(currentFrameTime); }
else if(keysDown[39] && player.canMoveRight()) { player.moveRight(currentFrameTime); }
}
viewport.update(player.position[0] + (player.dimensions[0]/2),
player.position[1] + (player.dimensions[1]/2));
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, viewport.screen[0], viewport.screen[1]);
for(var y = viewport.startTile[1]; y <= viewport.endTile[1]; ++y)
{
for(var x = viewport.startTile[0]; x <= viewport.endTile[0]; ++x)
{
var tile = tileTypes[gameMap[toIndex(x,y)]];
ctx.drawImage(tileset,
tile.sprite[0].x, tile.sprite[0].y, tile.sprite[0].w, tile.sprite[0].h,
viewport.offset[0] + (x*tileW), viewport.offset[1] + (y*tileH),
tileW, tileH);
}
}
var sprite = player.sprites[player.direction];
ctx.drawImage(tileset,
sprite[0].x, sprite[0].y, sprite[0].w, sprite[0].h,
viewport.offset[0] + player.position[0], viewport.offset[1] + player.position[1],
player.dimensions[0], player.dimensions[1]);
ctx.fillStyle = "#ff0000";
ctx.fillText("FPS: " + framesLastSecond, 10, 20);
lastFrameTime = currentFrameTime;
requestAnimationFrame(drawGame);
}
</script>
</head>
<body>
<canvas id="game" width="400" height="400"></canvas>
</body>
</html>