Pausing and changing game speeds
canvasgamedevtilemaptutorialgraphicsjavascripthtmlarchivedAltering game timing
Now we're looking at the speed and timing of things, it's time to overhaul the way we manage game speed. In doing so, we'll not only be able to increase or decrease the game speed, but also pause the game entirely.

Thankfully, because of the way in which we've already set up our code, we'll only need to make some minor additions and changes. Let's get started!
We'll begin by creating some new global variables: gameTime, which keeps track of the time which has currently elapsed in the game, and currentSpeed, which is the index of the current speed modifier in the gameSpeeds array.
We're using gameSpeeds to provide a list of speeds at which our game can run. Each entry in the array consists of two parameters: the name of the speed, for reference purposes, and mult, which is the multiplication factor of normal game speeds at which this entry causes the game to run:
var gameTime = 0;
var gameSpeeds = [
{name:"Normal", mult:1},
{name:"Slow", mult:0.3},
{name:"Fast", mult:3},
{name:"Paused", mult:0}
];
var currentSpeed = 0;
As you can see, "Normal" (index 0) just runs the game at 1x normal game speed, ie; does not alter the speed at all. "Fast" runs the game at 3x normal game speeds, and "Paused" runs at 0x; nothing progresses.
Next, in our windows onload method, we're going to add another key we want to listen for in the "keyup" event listener. After the if line that checks if an arrow key (keyCodes 37 to 40) has been pressed, we'll add the following:
if(e.keyCode==83)
{
currentSpeed = (currentSpeed>=(gameSpeeds.length-1) ? 0 : currentSpeed+1);
}
This will listen for the letter "s" on the keyboard being pressed and released. When this key is released, it will select the next gameSpeeds entry (or loop back to the first, or 0 index if the currentSpeed as at the end of the list), and set the currentSpeed value to the index of that speed.
Modifying the drawGame function
Finally, we'll make some changes to the drawGame function. After we've calculated the timeElapsed variable value, we'll add a line to increase the gameTime by the amount of time elapsed since the last frame, multiplied by the current gameSpeeds entry at index currentSpeed's mult property.
gameTime+= Math.floor(timeElapsed * gameSpeeds[currentSpeed].mult);
We'll also add a check to the players movement input if block - we'll not only check that processMovement returns false, but also that the game isn't currently paused, by checking that the current gameSpeeds mult property value isn't 0. We'll also change all of the movement processing methods time arguments from currentFrameTime to gameTime:
if(!player.processMovement(gameTime) && gameSpeeds[currentSpeed].mult!=0)
{
if(keysDown[38] && player.canMoveUp()) { player.moveUp(gameTime); }
else if(keysDown[40] && player.canMoveDown()) { player.moveDown(gameTime); }
else if(keysDown[37] && player.canMoveLeft()) { player.moveLeft(gameTime); }
else if(keysDown[39] && player.canMoveRight()) { player.moveRight(gameTime); }
}
Also, for our information, we'll render another bit of text after we've shown the FPS; this will show us the currently selected game speed:
ctx.fillText("Game speed: " + gameSpeeds[currentSpeed].name, 10, 40);
Finally, we'll change the time used for finding the current frame for animated tiles inside the y,x nested loops for tile rendering from currentFrameTime to gameTime:
var sprite = getFrame(tile.sprite, tile.spriteDuration,
gameTime, tile.animated);
And that's it. Any more game speeds we want to add, we can add to the gameSpeeds list. Managing time in this method allows us to easily manipulate game speed for both rendering (animated effects) and logic (movement, etc.).
Save your HTML document and load it up, or try our online example!
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 gameTime = 0; var gameSpeeds = [ {name:"Normal", mult:1}, {name:"Slow", mult:0.3}, {name:"Fast", mult:3}, {name:"Paused", mult:0} ]; var currentSpeed = 0;
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; }if(e.keyCode==83) { currentSpeed = (currentSpeed>=(gameSpeeds.length-1) ? 0 : currentSpeed+1); }
}); 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;gameTime+= Math.floor(timeElapsed * gameSpeeds[currentSpeed].mult);
var sec = Math.floor(Date.now()/1000); if(sec!=currentSecond) { currentSecond = sec; framesLastSecond = frameCount; frameCount = 1; } else { frameCount++; }if(!player.processMovement(gameTime) && gameSpeeds[currentSpeed].mult!=0) { if(keysDown[38] && player.canMoveUp()) { player.moveUp(gameTime); } else if(keysDown[40] && player.canMoveDown()) { player.moveDown(gameTime); } else if(keysDown[37] && player.canMoveLeft()) { player.moveLeft(gameTime); } else if(keysDown[39] && player.canMoveRight()) { player.moveRight(gameTime); } }
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, gameTime, 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);ctx.fillText("Game speed: " + gameSpeeds[currentSpeed].name, 10, 40);
lastFrameTime = currentFrameTime; requestAnimationFrame(drawGame); } </script> </head> <body> <canvas id="game" width="400" height="400"></canvas> </body> </html>
Game time 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 gameTime = 0;
var gameSpeeds = [
{name:"Normal", mult:1},
{name:"Slow", mult:0.3},
{name:"Fast", mult:3},
{name:"Paused", mult:0}
];
var currentSpeed = 0;
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; }
if(e.keyCode==83)
{
currentSpeed = (currentSpeed>=(gameSpeeds.length-1) ? 0 : currentSpeed+1);
}
});
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;
gameTime+= Math.floor(timeElapsed * gameSpeeds[currentSpeed].mult);
var sec = Math.floor(Date.now()/1000);
if(sec!=currentSecond)
{
currentSecond = sec;
framesLastSecond = frameCount;
frameCount = 1;
}
else { frameCount++; }
if(!player.processMovement(gameTime) && gameSpeeds[currentSpeed].mult!=0)
{
if(keysDown[38] && player.canMoveUp()) { player.moveUp(gameTime); }
else if(keysDown[40] && player.canMoveDown()) { player.moveDown(gameTime); }
else if(keysDown[37] && player.canMoveLeft()) { player.moveLeft(gameTime); }
else if(keysDown[39] && player.canMoveRight()) { player.moveRight(gameTime); }
}
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,
gameTime, 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);
ctx.fillText("Game speed: " + gameSpeeds[currentSpeed].name, 10, 40);
lastFrameTime = currentFrameTime;
requestAnimationFrame(drawGame);
}
</script>
</head>
<body>
<canvas id="game" width="400" height="400"></canvas>
</body>
</html>