Now that we have the basics out of the way for creating our canvas and drawing the hero onto the screen from part 1. We’ll start looking into what’s required to capture the arrow keys and using them to move the character around on the screen. Listing out part 1 with the phrase “Simple Game Creation” was a bit of a misnomer as it really didn’t include any of the key pieces required for a game. One of the first pieces you need for any game is a “game loop” — something that will be called / polled constantly to see what has to be done within the game either due to AI, maintenance or player input. There are a lot of advanced methods you can use with HTML5 and JavaScript to make sure your animation frames are accurate, however for the purposes of this simple game tutorial we’ll just be sticking with the setInterval method of game loops. In order to ensure things run at a (fairly) consistent rate, we’ll be tracking how long it’s been since the last call to the game loop and using that to adjust the speed at which things move around the screen. We’re going to use the non-optimal method of clearing the entire canvas and then redrawing it each iteration of the game loop because it is a lot easier than using dirty rectangles when first getting started.
[ccle_javascript]
// Variable to hold the time stamp for the last game loop call
var lastUpdate = null;
// The FPS rate we want to simulate with our loop
var desiredFps = 30
// To get a timer ratio we divide 1s (1000ms) by our desired frame rate
var timerRatio = 1000 / desiredFps;
function gameLoop()
{
var now = Date.now();
// calculate how long as passed since our last iteration
var elapsed = now – lastUpdate;
// tell the context we are going to use a dark green fill color
context.fillStyle = “#004400”;
// fill the entire canvas with the color
context.fillRect(0, 0, gameW, gameH);
// draw the player to the screen again
hero.render();
// update the lastUpdate variable
lastUpdate = now;
}
[/ccle_javascript]
That sets us up with a game loop and the ability to know how long it’s taking between each iteration, now we just have to tell the game to start running the loop. We’ll do this by using a call to setInterval inside our [cciel_javascript]$(window).load(function()[/cciel_javascript] at the very bottom:
[ccle_javascript]
// call the gameLoop as fast as possible
setInterval(gameLoop, 1);
[/ccle_javascript]
Now we’re getting somewhere! Granted, the somewhere we are getting is exactly where we were since we don’t have any logic in place to move or update the hero on the screen… Before we worry about updating the hero, let’s work on getting the framework in place to capture the arrow keys that are being pressed by the player. We’ll need to know when a key is pressed (in order to add it to the hero keys array) and when it is released (in order to remove it from the hero keys array). Luckily jQuery makes doing this extremely simple and we only have to register events for keyup and keydown:
[ccle_javascript]
$(document).keydown(function(event)
{
// check if the key being pressed is one of the arrow keys
if (event.keyCode < 41 && event.keyCode > 36)
{
// block the default browser action for the arrow keys
event.preventDefault();
// check to see if this key is already in the array
// of keys being pressed, if not add it to the array
curKey = $.inArray(event.keyCode, hero.keys);
if (curKey == -1)
hero.keys.push(event.keyCode);
}
});
$(document).keyup(function(event)
{
if (event.keyCode < 41 && event.keyCode > 36)
{
// block the default browser action for the arrow keys
event.preventDefault();
// check to see if this key is already in the array
// of keys being pressed, if so remove it from the array
curKey = $.inArray(event.keyCode, hero.keys);
if (curKey > -1)
hero.keys.splice(curKey, 1);
}
});
[/ccle_javascript]
The first thing the events do is to check to see if this is one of the arrow keys (37 = left arrow, 38 = up arrow, 39 = right arrow, 40 = down arrow). If it is one of the arrow keys we block the event from getting to the main browser (this keeps the arrow keys from shifting the screen around in case it doesn’t quite fit on the page for the player). Next we check to see if we already have the key in the array of keys using the jQuery inArray method. If the key is being pressed down and it’s not in the array (-1 means it wasn’t found in the array anywhere) then we add it to the array, if the key is being released and it is in the array then we remove it from the array. You could use the delete method to clear the value out of the array, but that would end up leaving a hole in the array so I prefer to splice it out.
Now that we are capturing the keys that we are concerned with we need to actually do something with them. To that end we’re going to create an update method for our heroObject that will check to see which keys are being pressed and move the hero around the screen accordingly. Since I’m doing this in a gridbased sprite model, we’re not going to allow diagonal movement so the hero will only be able to move one direction at a time (left, right, up OR down). Because of this our update method will just be a sequence of if statements to determine which way the hero will move. The call to the update method is where we’ll be making use of the timerRatio variable we defined above and we’ll also need to add a new property to the heroObject to let it know how fast we want it to move across the screen. At the top of the heroObject where we have our other properties (right under [cciel_javascript]this.whichSprite = 0[/cciel_javascript] ) we’ll add in:
[ccle_javascript]
// How many pixels do we want to move the hero each loop
this.moveSpeed = 4;
[/ccle_javascript]
At the bottom of our heroObject we’ll create the update function:
[ccle_javascript]
this.update = function(elapsed)
{
// move the hero left on the screen
if ($.inArray(37, this.keys) > -1)
{
this.x -= this.moveSpeed * elapsed;
}
// move the hero up on the screen
else if ($.inArray(38, this.keys) > -1)
{
this.y -= this.moveSpeed * elapsed;
}
// move the hero right on the screen
else if ($.inArray(39, this.keys) > -1)
{
this.x += this.moveSpeed * elapsed;
}
// move the hero down on the screen
else if ($.inArray(40, this.keys) > -1)
{
this.y += this.moveSpeed * elapsed;
}
};
[/ccle_javascript]
Now we have to make one modification to our gameLoop function to make it update the hero before the call to [cciel_javascript]hero.render();[/cciel_javascript] and we’ll be able to make the hero slide around on the screen:
[ccel_javascript]
// Update the hero based upon how long it took for the game loop
hero.update(elapsed / timerRatio);
[/ccel_javascript]
If you test it out at this point you should be able to use the left, right, up and down arrow keys to move the hero around the screen but you’ll probably notice two issues. The first issue is that the hero is able to slide off the canvas and can no longer be seen (you can still control it and bring it back if you remember where it is). The second issue is that the hero is just sliding around and doesn’t appear to be “walking.” The issue of sliding off the screen has two different resolutions: You can set it so that the edge of the screen is a barrier that the hero cannot pass through or you can make it so that if the hero goes off the edge of the screen it will wrap around to the other side.
Here is the code to add to the bottom of your update method if you want the hero to “wrap around” the screen. It checks to see if the hero is completely off the screen on any of the edges and, if so, wraps it around to the opposite edge:
[ccel_javascript]
// This code handles wrapping the hero from one edge of the canvas to the other
if (this.x < -this.width)
{
this.x = gameW - this.width;
}
if (this.x >= gameW)
{
this.x = 0;
}
if (this.y < -this.height)
{
this.y = gameH - this.height;
}
if (this.y >= gameH)
{
this.y = 0;
}
[/ccel_javascript]
This is the code to add to the bottom of your update method if you want the hero to treat the edges of the screen as barriers. It checks to see if the hero is completely off the screen on any of the edges and, if so, undoes the movement from this update call
[ccel_javascript]
// This code would cause the edges of the canvas to be a barrier
if (this.x < 0)
{
this.x += this.moveSpeed * elapsed;
}
if (this.x + this.width >= gameW)
{
this.x -= this.moveSpeed * elapsed;
}
if (this.y < 0)
{
this.y += this.moveSpeed * elapsed;
}
if (this.y + this.height >= gameH)
{
this.y -= this.moveSpeed * elapsed;
}
[/ccel_javascript]
Either one of those options is perfectly viable and it’s up to you to decide which one you prefer. For this tutorial I’m going to be using the option of letting the hero wrap around the screen.
The other issue we had was that it looked like the hero was just sliding around. From part 1, we know that we have sprites representing walking in all of the different directions. We’ll need to adjust our update code to handle changing which sprite is being drawn based upon which direction the hero is moving. Also note that we have a property in the heroObject called animSpeed which is described as the delay we want to use between switching sprites. Due to the rapid calls made to the gameLoop (and therefore made to the heroObject update method) if we don’t delay how often we switch sprites it will look like our hero is in an old school cartoon with his legs running extremely fast as he moves around the screen. This is offset slightly by the timerRatio we have for the main gameLoop, but doing it within the update for the hero allows us to fine-tune the speed at which the sprites flip. The first thing we’ll need to do is calculate how long it’s been since we last updated the sprite being used. We check that in each of our if blocks for movement and update the sprite accordingly.
We’re also going to adjust the update function so that it uses the most recently pressed key to determine which direction we want the hero to move. We do this by using a switch statement on the value in the last index of the keys array in the heroObject. Because of the method we use to push values into the array we can be sure that whatever key code is in the last index of the array was the key that was most recently pressed. We’ll also change the logic that determines whether or not we want to update the sprite being displayed. Previously we only checked to see if the delay between animations had elapsed, we’re going to adjust that to also check if we’ve changed directions since our last call to the update. Here’s the completed update method with sprite animation and wrapping turned on:
[ccle_javascript]
this.update = function(elapsed)
{
var now = Date.now();
// How long has it been since we last updated the sprite
var delta = now – this.lastRender;
// perform a switch statement on the last key pushed into the array
// this allows us to always move the direction of the most recently pressed
// key
switch (this.keys[this.keys.length – 1])
{
case 37:
// move the hero left on the screen
this.x -= this.moveSpeed * elapsed;
// Check if the animation timer has elapsed or if we aren’t using one of the
// two valid sprites for this direction
if (delta > this.animSpeed
|| (this.whichSprite != this.width * 2 && this.whichSprite != this.width * 3))
{
// The sprites for moving left are the 3rd and 4th sprites in the image (0 based index)
this.whichSprite = this.whichSprite == this.width * 2 ? this.width * 3 : this.width * 2;
this.lastRender = now;
}
break;
case 38:
// move the hero up on the screen
this.y -= this.moveSpeed * elapsed;
// Check if the animation timer has elapsed or if we aren’t using one of the
// two valid sprites for this direction
if (delta > this.animSpeed
|| (this.whichSprite != this.width * 6 && this.whichSprite != this.width * 7))
{
// The sprites for moving up are the 7th and 8th sprites in the image (0 based index)
this.whichSprite = this.whichSprite == this.width * 6 ? this.width * 7 : this.width * 6;
this.lastRender = now;
}
break;
case 39:
// move the hero right on the screen
this.x += this.moveSpeed * elapsed;
// Check if the animation timer has elapsed or if we aren’t using one of the
// two valid sprites for this direction
if (delta > this.animSpeed
|| (this.whichSprite != this.width * 4 && this.whichSprite != this.width * 5))
{
// The sprites for moving right are the 5th and 6th sprites in the image (0 based index)
this.whichSprite = this.whichSprite == this.width * 4 ? this.width * 5 : this.width * 4;
this.lastRender = now;
}
break;
case 40:
// move the hero down on the screen
this.y += this.moveSpeed * elapsed;
// Check if the animation timer has elapsed or if we aren’t using one of the
// two valid sprites for this direction
if (delta > this.animSpeed
|| (this.whichSprite != 0 && this.whichSprite != this.width))
{
// The sprites for moving down are the 1st and 2nd sprites in the image (0 based index)
this.whichSprite = this.whichSprite == 0 ? this.width : 0;
this.lastRender = now;
}
break;
}
// This code handles wrapping the hero from one edge of the canvas to the other
if (this.x < -this.width)
{
this.x = gameW - this.width;
}
if (this.x >= gameW)
{
this.x = 0;
}
if (this.y < -this.height)
{
this.y = gameH - this.height;
}
if (this.y >= gameH)
{
this.y = 0;
}
// This code would cause the edges of the canvas to be a barrier
// if (this.x < 0)
// {
// this.x += this.moveSpeed * elapsed;
// }
// if (this.x + this.width >= gameW)
// {
// this.x -= this.moveSpeed * elapsed;
// }
// if (this.y < 0)
// {
// this.y += this.moveSpeed * elapsed;
// }
// if (this.y + this.height >= gameH)
// {
// this.y -= this.moveSpeed * elapsed;
// }
};
[/ccle_javascript]
To toggle the sprite I’m using the ternary ?: operator. This allows me to alternate between the two different sprites that are available for the walking animation in each direction. At this point you have the basics of a game getting started. You have your gameLoop that is constantly being polled to update the screen, you have the ability to capture keypresses to move the hero around on the screen and you have the logic in place to make it appear like your hero is walking around. In the next part we’ll add in some rocks to the scene and implement some basic collision detection for our hero.
Here’s the link to the demo for this code.