Movement speeds on different tile types
canvasgamedevtilemaptutorialgraphicsjavascripthtmlarchivedSurface movement speeds
Ever tried running in deep snow? Chances are, you've noticed different surfaces may help or hamper the speed at which you can comfortably walk. In this tutorial, we'll be doing the same for our Character.

The tilesheet used is the same as in the previous tutorial.
Firstly, the Character delayMove property, which before was simply our single-tile movement speed in milliseconds, becomes a list of movement speeds for all of the floorTypes we want our Character to be able to walk on:
this.delayMove = {};
this.delayMove[floorTypes.path] = 400;
this.delayMove[floorTypes.grass] = 800;
this.delayMove[floorTypes.ice] = 300;
this.delayMove[floorTypes.conveyorU] = 200;
this.delayMove[floorTypes.conveyorD] = 200;
this.delayMove[floorTypes.conveyorL] = 200;
this.delayMove[floorTypes.conveyorR] = 200;
As you can see, we've given grass a movement speed of 800 milliseconds per tile, and path a movement speed of 400 milliseconds per tile. Simply put, our Character will be able to cross 2 path tiles in the time it takes to cross 1 grass tile!
We'll add a new temporary variable to the Character processMovement method; immediately after our first if statement to check the Character is truly moving, we'll assign the new moveSpeed variable the delayMove value corresponding to the floorType of the map tile our Character is moving from (tileFrom):
var moveSpeed = this.delayMove[tileTypes[gameMap[toIndex(this.tileFrom[0],this.tileFrom[1])]].floor];
We'll then replace every occurrence of delayMove in this method after this line with the moveSpeed variable. The method, after editing, will look like this:
Character.prototype.processMovement = function(t)
{
if(this.tileFrom[0]==this.tileTo[0] && this.tileFrom[1]==this.tileTo[1]) { return false; }
var moveSpeed = this.delayMove[tileTypes[gameMap[toIndex(this.tileFrom[0],this.tileFrom[1])]].floor];
if((t-this.timeMoved)>=moveSpeed)
{
this.placeAt(this.tileTo[0], this.tileTo[1]);
var tileFloor = tileTypes[gameMap[toIndex(this.tileFrom[0], this.tileFrom[1])]].floor;
if(tileFloor==floorTypes.ice)
{
if(this.canMoveDirection(this.direction))
{
this.moveDirection(this.direction, t);
}
}
else if(tileFloor==floorTypes.conveyorL && this.canMoveLeft()) { this.moveLeft(t); }
else if(tileFloor==floorTypes.conveyorR && this.canMoveRight()) { this.moveRight(t); }
else if(tileFloor==floorTypes.conveyorU && this.canMoveUp()) { this.moveUp(t); }
else if(tileFloor==floorTypes.conveyorD && this.canMoveDown()) { this.moveDown(t); }
}
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 / moveSpeed) * (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 / moveSpeed) * (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;
}
Finally, we'll change the way the canMoveTo suitable floorType check works. Instead of listing all the floorTypes we can move to and returning false if the destination tile floorType isn't in that list, we'll do one simple check: if they Character does not have a value assigned for floorType of the destination tile (ie; if it is undefined), we know the Character cannot move there, and so return false:
if(typeof this.delayMove[tileTypes[gameMap[toIndex(x,y)]].floor]=='undefined') { return false; }
That's all there is to it! Load up the HTML document, and try moving across the different tile types. You'll see big differences in movement speeds!
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, 7, 7, 7, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 3, 4, 4, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 3, 1, 4, 4, 1, 9, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 0, 0, 2, 3, 1, 1, 4, 4, 9, 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, 5, 1, 1, 1, 2, 4, 1, 1, 1, 1, 6, 6, 6, 2, 1, 1, 1, 1, 0, 0, 5, 5, 5, 5, 2, 4, 1, 1, 1, 1, 8, 1, 1, 2, 1, 1, 1, 1, 0, 0, 1, 5, 1, 5, 2, 4, 4, 4, 4, 4, 8, 1, 1, 2, 2, 2, 2, 1, 0, 0, 1, 5, 5, 5, 2, 3, 2, 1, 1, 4, 8, 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, ice : 3, conveyorU : 4, conveyorD : 5, conveyorL : 6, conveyorR : 7, grass : 8 }; var tileTypes = { 0 : { colour:"#685b48", floor:floorTypes.solid, sprite:[{x:0,y:0,w:40,h:40}] }, 1 : { colour:"#5aa457", floor:floorTypes.grass, 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,d:200}, {x:200,y:0,w:40,h:40,d:200}, {x:160,y:40,w:40,h:40,d:200}, {x:200,y:40,w:40,h:40,d:200}, {x:160,y:40,w:40,h:40,d:200}, {x:200,y:0,w:40,h:40,d:200} ]}, 5 : { colour:"#eeeeff", floor:floorTypes.ice, sprite:[{x:120,y:120,w:40,h:40}]}, 6 : { colour:"#cccccc", floor:floorTypes.conveyorL, sprite:[ {x:0,y:40,w:40,h:40,d:200}, {x:40,y:40,w:40,h:40,d:200}, {x:80,y:40,w:40,h:40,d:200}, {x:120,y:40,w:40,h:40,d:200} ]}, 7 : { colour:"#cccccc", floor:floorTypes.conveyorR, sprite:[ {x:120,y:80,w:40,h:40,d:200}, {x:80,y:80,w:40,h:40,d:200}, {x:40,y:80,w:40,h:40,d:200}, {x:0,y:80,w:40,h:40,d:200} ]}, 8 : { colour:"#cccccc", floor:floorTypes.conveyorD, sprite:[ {x:160,y:200,w:40,h:40,d:200}, {x:160,y:160,w:40,h:40,d:200}, {x:160,y:120,w:40,h:40,d:200}, {x:160,y:80,w:40,h:40,d:200} ]}, 9 : { colour:"#cccccc", floor:floorTypes.conveyorU, sprite:[ {x:200,y:80,w:40,h:40,d:200}, {x:200,y:120,w:40,h:40,d:200}, {x:200,y:160,w:40,h:40,d:200}, {x:200,y:200,w:40,h:40,d:200} ]} }; 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 = {}; this.delayMove[floorTypes.path] = 400; this.delayMove[floorTypes.grass] = 800; this.delayMove[floorTypes.ice] = 300; this.delayMove[floorTypes.conveyorU] = 200; this.delayMove[floorTypes.conveyorD] = 200; this.delayMove[floorTypes.conveyorL] = 200; this.delayMove[floorTypes.conveyorR] = 200;
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; } var moveSpeed = this.delayMove[tileTypes[gameMap[toIndex(this.tileFrom[0],this.tileFrom[1])]].floor]; if((t-this.timeMoved)>=moveSpeed) { this.placeAt(this.tileTo[0], this.tileTo[1]); var tileFloor = tileTypes[gameMap[toIndex(this.tileFrom[0], this.tileFrom[1])]].floor; if(tileFloor==floorTypes.ice) { if(this.canMoveDirection(this.direction)) { this.moveDirection(this.direction, t); } } else if(tileFloor==floorTypes.conveyorL && this.canMoveLeft()) { this.moveLeft(t); } else if(tileFloor==floorTypes.conveyorR && this.canMoveRight()) { this.moveRight(t); } else if(tileFloor==floorTypes.conveyorU && this.canMoveUp()) { this.moveUp(t); } else if(tileFloor==floorTypes.conveyorD && this.canMoveDown()) { this.moveDown(t); } } 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 / moveSpeed) * (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 / moveSpeed) * (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(typeof this.delayMove[tileTypes[gameMap[toIndex(x,y)]].floor]=='undefined') { 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.canMoveDirection = function(d) { switch(d) { case directions.up: return this.canMoveUp(); case directions.down: return this.canMoveDown(); case directions.left: return this.canMoveLeft(); default: return this.canMoveRight(); } }; 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; }; Character.prototype.moveDirection = function(d, t) { switch(d) { case directions.up: return this.moveUp(t); case directions.down: return this.moveDown(t); case directions.left: return this.moveLeft(t); default: return this.moveRight(t); } }; function toIndex(x, y) { return((y * mapW) + x); } function getFrame(sprite, duration, time, animated) { if(!animated) { return sprite[0]; } time = time % duration; for(x in sprite) { if(sprite[x].end>=time) { return sprite[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; for(x in tileTypes) { tileTypes[x]['animated'] = tileTypes[x].sprite.length > 1 ? true : false; if(tileTypes[x].animated) { var t = 0; for(s in tileTypes[x].sprite) { tileTypes[x].sprite[s]['start'] = t; t+= tileTypes[x].sprite[s].d; tileTypes[x].sprite[s]['end'] = t; } tileTypes[x]['spriteDuration'] = t; } } }; 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)]]; var sprite = getFrame(tile.sprite, tile.spriteDuration, currentFrameTime, tile.animated); ctx.drawImage(tileset, sprite.x, sprite.y, sprite.w, sprite.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>
Movement speeds 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, 7, 7, 7, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0,
0, 2, 3, 4, 4, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0,
0, 2, 3, 1, 4, 4, 1, 9, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 0,
0, 2, 3, 1, 1, 4, 4, 9, 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, 5, 1, 1, 1, 2, 4, 1, 1, 1, 1, 6, 6, 6, 2, 1, 1, 1, 1, 0,
0, 5, 5, 5, 5, 2, 4, 1, 1, 1, 1, 8, 1, 1, 2, 1, 1, 1, 1, 0,
0, 1, 5, 1, 5, 2, 4, 4, 4, 4, 4, 8, 1, 1, 2, 2, 2, 2, 1, 0,
0, 1, 5, 5, 5, 2, 3, 2, 1, 1, 4, 8, 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,
ice : 3,
conveyorU : 4,
conveyorD : 5,
conveyorL : 6,
conveyorR : 7,
grass : 8
};
var tileTypes = {
0 : { colour:"#685b48", floor:floorTypes.solid, sprite:[{x:0,y:0,w:40,h:40}] },
1 : { colour:"#5aa457", floor:floorTypes.grass, 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,d:200}, {x:200,y:0,w:40,h:40,d:200},
{x:160,y:40,w:40,h:40,d:200}, {x:200,y:40,w:40,h:40,d:200},
{x:160,y:40,w:40,h:40,d:200}, {x:200,y:0,w:40,h:40,d:200}
]},
5 : { colour:"#eeeeff", floor:floorTypes.ice, sprite:[{x:120,y:120,w:40,h:40}]},
6 : { colour:"#cccccc", floor:floorTypes.conveyorL, sprite:[
{x:0,y:40,w:40,h:40,d:200}, {x:40,y:40,w:40,h:40,d:200},
{x:80,y:40,w:40,h:40,d:200}, {x:120,y:40,w:40,h:40,d:200}
]},
7 : { colour:"#cccccc", floor:floorTypes.conveyorR, sprite:[
{x:120,y:80,w:40,h:40,d:200}, {x:80,y:80,w:40,h:40,d:200},
{x:40,y:80,w:40,h:40,d:200}, {x:0,y:80,w:40,h:40,d:200}
]},
8 : { colour:"#cccccc", floor:floorTypes.conveyorD, sprite:[
{x:160,y:200,w:40,h:40,d:200}, {x:160,y:160,w:40,h:40,d:200},
{x:160,y:120,w:40,h:40,d:200}, {x:160,y:80,w:40,h:40,d:200}
]},
9 : { colour:"#cccccc", floor:floorTypes.conveyorU, sprite:[
{x:200,y:80,w:40,h:40,d:200}, {x:200,y:120,w:40,h:40,d:200},
{x:200,y:160,w:40,h:40,d:200}, {x:200,y:200,w:40,h:40,d:200}
]}
};
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 = {};
this.delayMove[floorTypes.path] = 400;
this.delayMove[floorTypes.grass] = 800;
this.delayMove[floorTypes.ice] = 300;
this.delayMove[floorTypes.conveyorU] = 200;
this.delayMove[floorTypes.conveyorD] = 200;
this.delayMove[floorTypes.conveyorL] = 200;
this.delayMove[floorTypes.conveyorR] = 200;
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; }
var moveSpeed = this.delayMove[tileTypes[gameMap[toIndex(this.tileFrom[0],this.tileFrom[1])]].floor];
if((t-this.timeMoved)>=moveSpeed)
{
this.placeAt(this.tileTo[0], this.tileTo[1]);
var tileFloor = tileTypes[gameMap[toIndex(this.tileFrom[0], this.tileFrom[1])]].floor;
if(tileFloor==floorTypes.ice)
{
if(this.canMoveDirection(this.direction))
{
this.moveDirection(this.direction, t);
}
}
else if(tileFloor==floorTypes.conveyorL && this.canMoveLeft()) { this.moveLeft(t); }
else if(tileFloor==floorTypes.conveyorR && this.canMoveRight()) { this.moveRight(t); }
else if(tileFloor==floorTypes.conveyorU && this.canMoveUp()) { this.moveUp(t); }
else if(tileFloor==floorTypes.conveyorD && this.canMoveDown()) { this.moveDown(t); }
}
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 / moveSpeed) * (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 / moveSpeed) * (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(typeof this.delayMove[tileTypes[gameMap[toIndex(x,y)]].floor]=='undefined') { 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.canMoveDirection = function(d) {
switch(d)
{
case directions.up:
return this.canMoveUp();
case directions.down:
return this.canMoveDown();
case directions.left:
return this.canMoveLeft();
default:
return this.canMoveRight();
}
};
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; };
Character.prototype.moveDirection = function(d, t) {
switch(d)
{
case directions.up:
return this.moveUp(t);
case directions.down:
return this.moveDown(t);
case directions.left:
return this.moveLeft(t);
default:
return this.moveRight(t);
}
};
function toIndex(x, y)
{
return((y * mapW) + x);
}
function getFrame(sprite, duration, time, animated)
{
if(!animated) { return sprite[0]; }
time = time % duration;
for(x in sprite)
{
if(sprite[x].end>=time) { return sprite[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;
for(x in tileTypes)
{
tileTypes[x]['animated'] = tileTypes[x].sprite.length > 1 ? true : false;
if(tileTypes[x].animated)
{
var t = 0;
for(s in tileTypes[x].sprite)
{
tileTypes[x].sprite[s]['start'] = t;
t+= tileTypes[x].sprite[s].d;
tileTypes[x].sprite[s]['end'] = t;
}
tileTypes[x]['spriteDuration'] = t;
}
}
};
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)]];
var sprite = getFrame(tile.sprite, tile.spriteDuration,
currentFrameTime, tile.animated);
ctx.drawImage(tileset,
sprite.x, sprite.y, sprite.w, sprite.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>