Saturday 1 December 2012

HTML5 Game Development Tutorial

If you want to make a HTML5 game and just want a nice reference page you can go back to, or if you are a complete beginner and need to learn as much as possible step-by-step, then you're at the right place.
I also have a guide split into multiple pages here, if you prefer that: HTML5 Game Development Guide.
There's also a video tutorial for the basics of HTML5 game development: Video Intro to HTML5 Game Dev

If you don't know much about JavaScript, then have a look at the Introduction to JavaScript.

If you want to know how to make money from your games, have a look at this article:
How to Make Money From Your Games


If I post any little snippets here, feel free to use them in any way.
Although if you use a lot of it, it would be appreciated if you place something like this in your JavaScript:
/* Some code taken from www.slashgame.net */

Contents

What is the HTML5 Canvas?
How to Draw an Image to the Canvas
How to Draw Text to the Canvas
Set up a Game Loop
Keyboard Input
Mouse Input
Add Simple Game Physics
How to Rotate an Image on Canvas
Use HTML5 Audio
Conclusion of Tutorial



What is the HTML5 Canvas? (back to top)

The <canvas> HTML element, which came to us in HTML5 was the first step towards HTML5 games becoming big.

The canvas allows so many things that you wouldn't have been able to do otherwise, and does it rather efficiently.

Canvas renders 2D graphics onto a given area in the browser window. It does this dynamically via scripting (i.e. JavaScript). The shapes and images which are rendered on the canvas can be manipulated in several ways. The most obvious and useful ways are scaling and rotating the image; which you would not have been able to do without the canvas, unless you did some rather inefficient and complicated php scripting to alter an image at the pixel-level to make it appear to do a similar thing.

JavaScript has a 2D graphics API which accesses the HTML5 canvas drawing area, which is how games can be written. But just because it only has a 2D API, don't let that make you think 3D isn't possible.

For a start, you could simply write your own JavaScript 3D engine, which uses normal 2D functions to generate a 3D object and render the 2D projection on the canvas. Doing so just requires a bit of mathematics. But making it a huge and playable game is where it gets difficult.

You could create a whole 3D engine if you wanted, but there is a 3D graphics API available. It's called WebGL, and it does what I just described, but it simplifies it by allowing you to just call the functions, rather than having to think up all that complicated stuff yourself. It also means that the 3D engine already has optimized code, meaning it will run faster.



How to Draw an Image to the Canvas (back to top)

This is just about the simplest example of the use of HTML5 canvas you can get. This is just to show how to set up a canvas and draw in it.

Firstly, you would have something like this in the HTML of the page:
<canvas height="400" id="canvas" width="400">
Browser not compatible with HTML5 canvas
</canvas>

The id of the canvas element can be anything, but here it has been set to "canvas". This is used so the JavaScript can identify the correct canvas element to manipulate.

I've set the width and height of the canvas to 400px. It normally wouldn't be a perfect square for a game, but this is just an example.

And if an incompatible browser is used, it will display the text "Browser not compatible with HTML5 canvas".


The JavaScript goes in the section of the page.
You could have all the code in script tags (<script>CODE HERE</script>) or have it in a seperate .js file and call it from src attribute of script tags (<script src"javascriptfile.js"></script>).

Here is an example of the kind of JavaScript you could use to draw an image:
var canvas = document.getElementById('canvas');
var context2D = canvas.getContext('2d');

var img = new Image();
img.src = "http://www.blogger.com/img/logo40.png";
context2D.drawImage(img, 100, 50);

So first it gets the canvas element by using the id we gave it. It then creates the 2D context with that canvas element.

It then loads a new Image object and then tells it to use the image from the location we give it by assigning it to the src variable of the Image object.

Then it uses the 2D graphics API by calling the drawImg() function.

The first parameter of the function is looking for an image object to draw, the second is the x-position and the third is the y-position. The positions are always relative to the top left corner by default.



How to Draw Text to the Canvas (back to top)

context2D.fillStyle = "white";
context2D.font = 'bold 30px sans-serif';
context2D.fillText("POINTS: "+numOfPoints, canvas.width-200, 30);

That right there is an example of drawing text to the canvas. It writes it on the top right of the canvas. That particular example is what would serve as part of the HUD in a game.
Here is a little function that draws text to the canvas with a specified position, color and font. This also shows you the different things you can do with text.
However, you may want to use this function just as a means to being able to not worry about remembering the different parts to it.
function drawCanvasText(txt, pX, pY, clr, fnt) {
 context2D.fillStyle = clr; // color
 context2D.font = fnt; // font
 context2D.fillText(txt, pX, pY); // text at position X, Y
}
Example Usage:
drawCanvasText("drawing text yas", 50, 50, "black", "bold 30px sans-serif");


Set up a Game Loop (back to top)

A game loop is the normal structure a game would be build in. This is something you would find in any kind of game, not just HTML5 games.
First it would initiate the loop (what I tend to do is trigger the initiation function when the window is loaded), then the loop would involve checking for user input, then doing what needs to be done to all the objects, then rendering/drawing the scene, and start again from checking user input.
This is an example of doing this in javascript:
const FPS = 30;

var playerPosX = 0;
var playerPosY = 0;

var playerImg = new Image();
playerImg.src = "http://filevaults.com/media/Slash/553481-smiley.png";

var canvas = null;
var context2D = null;
var keys = new Array();

window.onload = init;

window.addEventListener('keydown',keyDown,true);
window.addEventListener('keyup',keyUp,true);
function keyDown(evt){
 keys[evt.keyCode] = true;
}
function keyUp(evt){
 keys[evt.keyCode] = false;
}

function init()
{
 canvas = document.getElementById('canvas');
 context2D = canvas.getContext('2d');
 setInterval(draw, 1000 / FPS);
}

function draw()
{
 if ((37 in keys &amp;&amp; keys[37]) || (65 in keys &amp;&amp; keys[65])){ //left
  playerPosX -= 2;
 }
 if ((39 in keys &amp;&amp; keys[39]) || (68 in keys &amp;&amp; keys[68])){ //right
  playerPosX += 2;
 }
 
 context2D.clearRect(0, 0, canvas.width, canvas.height);
 context2D.drawImage(playerImg, playerPosX, playerPosY);
}

The code initiates the loop in the init() function, which is called as soon as the window is loaded up. This function uses the setInterval() function to call the draw() function 30 times per second (it uses the FPS variable to determine that frame rate).

It then adds an "event listener" for key being pressed, and for key being released. This means that when a key is pressed, it calls the function which adds the key's code to an array. When the key is released, another function is called which sets the value of that array element to false. This means that every time the loop comes to the point where it checks for user input, all it has to do is look in the array and see if it's there and that it is set to true.

Once it knows if the key is pressed, it then decides what to do with the playerPos variables. This is the stage where it changes the "virtual world".

Then the last stage of the loop is rendering/drawing the scene based on the "virtual world's" variables.
To do so, it clears the canvas fully then draws the image in the correct spot.

Here's the result of the example I just gave (use arrow keys or A and D to move left and right): http://slashgame.net/html5gamedevexample2.php


From the knowledge you have learned so far, you can make a simple game, if you have the ability to figure things out. However, I will continue and explain things in further detail and bring up new points of interest.



Keyboard Input (back to top)

As explained in the Game Loop section of this post (just above this section); it goes through the loop, and each time, it checks if the key is pressed by checking an array to see if it is set to true or false.
An event listener calls a function when a key is pressed down, and sets the array element of the ASCII code of the key, and sets it to true. And another event listener checks for keys being released, and sets the element value to false.

Looking up ASCII values all the time can sometimes be a pain in the ass, so what I've done is made a little snippet of code you can paste into your code which allows you to simply call the function with the key you want to check as a parameter. This is simple enough to use.

Examples of use:
If you wanted to check if the A key is pressed down:
if (isKeyPressed('a')==true) { /* do whatever */ }
If you wanted to check for the left arrow key being pressed:
isKeyPressed('left')
For escape key:
isKeyPressed('esc')


The Code:
window.addEventListener('keydown',keyPressDown,true);
window.addEventListener('keyup',keyPressUp,true);

function keyPressDown(evt){
 keys[evt.keyCode] = true;
}
function keyPressUp(evt){
 keys[evt.keyCode] = false;
}


function isKeyPressed(checkKey) {
 
 // alphabet
 if ((checkKey == 'a' || checkKey == 'A') &amp;&amp; (65 in keys &amp;&amp; keys[65])){
  return true;
 }
 else if ((checkKey == 'b' || checkKey == 'B') &amp;&amp; (66 in keys &amp;&amp; keys[66])){
  return true;
 }
 else if ((checkKey == 'c' || checkKey == 'C') &amp;&amp; (67 in keys &amp;&amp; keys[67])){
  return true;
 }
 else if ((checkKey == 'd' || checkKey == 'D') &amp;&amp; (68 in keys &amp;&amp; keys[68])){
  return true;
 }
 else if ((checkKey == 'e' || checkKey == 'E') &amp;&amp; (69 in keys &amp;&amp; keys[69])){
  return true;
 }
 else if ((checkKey == 'f' || checkKey == 'F') &amp;&amp; (70 in keys &amp;&amp; keys[70])){
  return true;
 }
 else if ((checkKey == 'g' || checkKey == 'G') &amp;&amp; (71 in keys &amp;&amp; keys[71])){
  return true;
 }
 else if ((checkKey == 'h' || checkKey == 'H') &amp;&amp; (72 in keys &amp;&amp; keys[72])){
  return true;
 }
 else if ((checkKey == 'i' || checkKey == 'I') &amp;&amp; (73 in keys &amp;&amp; keys[73])){
  return true;
 }
 else if ((checkKey == 'j' || checkKey == 'J') &amp;&amp; (74 in keys &amp;&amp; keys[74])){
  return true;
 }
 else if ((checkKey == 'k' || checkKey == 'K') &amp;&amp; (75 in keys &amp;&amp; keys[75])){
  return true;
 }
 else if ((checkKey == 'l' || checkKey == 'L') &amp;&amp; (76 in keys &amp;&amp; keys[76])){
  return true;
 }
 else if ((checkKey == 'm' || checkKey == 'M') &amp;&amp; (77 in keys &amp;&amp; keys[77])){
  return true;
 }
 else if ((checkKey == 'n' || checkKey == 'N') &amp;&amp; (78 in keys &amp;&amp; keys[78])){
  return true;
 }
 else if ((checkKey == 'o' || checkKey == 'O') &amp;&amp; (79 in keys &amp;&amp; keys[79])){
  return true;
 }
 else if ((checkKey == 'p' || checkKey == 'P') &amp;&amp; (80 in keys &amp;&amp; keys[80])){
  return true;
 }
 else if ((checkKey == 'q' || checkKey == 'Q') &amp;&amp; (81 in keys &amp;&amp; keys[81])){
  return true;
 }
 else if ((checkKey == 'r' || checkKey == 'R') &amp;&amp; (82 in keys &amp;&amp; keys[82])){
  return true;
 }
 else if ((checkKey == 's' || checkKey == 'S') &amp;&amp; (83 in keys &amp;&amp; keys[83])){
  return true;
 }
 else if ((checkKey == 't' || checkKey == 'T') &amp;&amp; (84 in keys &amp;&amp; keys[84])){
  return true;
 }
 else if ((checkKey == 'u' || checkKey == 'U') &amp;&amp; (85 in keys &amp;&amp; keys[85])){
  return true;
 }
 else if ((checkKey == 'v' || checkKey == 'V') &amp;&amp; (86 in keys &amp;&amp; keys[86])){
  return true;
 }
 else if ((checkKey == 'w' || checkKey == 'W') &amp;&amp; (87 in keys &amp;&amp; keys[87])){
  return true;
 }
 else if ((checkKey == 'x' || checkKey == 'X') &amp;&amp; (88 in keys &amp;&amp; keys[88])){
  return true;
 }
 else if ((checkKey == 'y' || checkKey == 'Y') &amp;&amp; (89 in keys &amp;&amp; keys[89])){
  return true;
 }
 else if ((checkKey == 'z' || checkKey == 'Z') &amp;&amp; (90 in keys &amp;&amp; keys[90])){
  return true;
 }
 
 
 //numbers
 else if ((checkKey == '0') &amp;&amp; (48 in keys &amp;&amp; keys[48])){
  return true;
 }
 else if ((checkKey == '1') &amp;&amp; (49 in keys &amp;&amp; keys[49])){
  return true;
 }
 else if ((checkKey == '2') &amp;&amp; (50 in keys &amp;&amp; keys[50])){
  return true;
 }
 else if ((checkKey == '3') &amp;&amp; (51 in keys &amp;&amp; keys[51])){
  return true;
 }
 else if ((checkKey == '4') &amp;&amp; (52 in keys &amp;&amp; keys[52])){
  return true;
 }
 else if ((checkKey == '5') &amp;&amp; (53 in keys &amp;&amp; keys[53])){
  return true;
 }
 else if ((checkKey == '6') &amp;&amp; (54 in keys &amp;&amp; keys[54])){
  return true;
 }
 else if ((checkKey == '7') &amp;&amp; (55 in keys &amp;&amp; keys[55])){
  return true;
 }
 else if ((checkKey == '8') &amp;&amp; (56 in keys &amp;&amp; keys[56])){
  return true;
 }
 else if ((checkKey == '9') &amp;&amp; (57 in keys &amp;&amp; keys[57])){
  return true;
 }
 
 
 // special keys
 else if ((checkKey == 'left' || checkKey == 'LEFT') &amp;&amp; (37 in keys &amp;&amp; keys[37])){
  return true;
 }
 else if ((checkKey == 'right' || checkKey == 'RIGHT') &amp;&amp; (39 in keys &amp;&amp; keys[39])){
  return true;
 }
 else if ((checkKey == 'up' || checkKey == 'UP') &amp;&amp; (38 in keys &amp;&amp; keys[38])){
  return true;
 }
 else if ((checkKey == 'down' || checkKey == 'DOWN') &amp;&amp; (40 in keys &amp;&amp; keys[40])){
  return true;
 }
 else if ((checkKey == 'esc' || checkKey == 'ESC' || checkKey == 'escape' || checkKey == 'ESCAPE' ) &amp;&amp; (27 in keys &amp;&amp; keys[27])){
  return true;
 }
 
}


Mouse Input (back to top)

It's fairly simple to add mouse input to your game; just use an eventListener for the event "mousemove" and use a function that takes the event as a parameter (in my example below, I used mouseMoved(e)), and use the e.pageX and e.pageY for the mouse's X and Y position.

And for mouse clicks, just use eventListener for the "click" event, and use a function that does whatever. In the example I give, it sets the position of an image to the position of the mouse at the point it is clicked. In this case, a bullethole in the position the cursor was pointing at.

Here is the code:
<html>
<head>
<script type="text/javascript">
const FPS = 60;

var canvas = null;
var context2D = null;

var cursorImg = new Image();
cursorImg.src = "images/cursorexample.png";

var bulletHoleImg = new Image();
bulletHoleImg.src = "images/bullethole.png";
var bulletHolesX = new Array();
var bulletHolesY = new Array();
for (var i=0; i<50; i++) {
 bulletHolesX[i] = -500;
 bulletHolesY[i] = -500;
}
bulletNum = 0;

window.addEventListener('mousemove', mouseMoved, true);
window.addEventListener('click', clicked, true);
var mouseX = 0;
var mouseY = 0;

window.onload = init;
function init() {
 canvas = document.getElementById('canvas1');
 context2D = canvas.getContext('2d');
 
 setInterval(draw, 1000/FPS);
}

function draw() {
 context2D.clearRect(0, 0, canvas.width, canvas.height);
 for (var i=0; i<50; i++) {
  context2D.drawImage(bulletHoleImg, bulletHolesX[i]-(bulletHoleImg.width/2), bulletHolesY[i]-(bulletHoleImg.height/2));
 }
 context2D.drawImage(cursorImg, mouseX-(cursorImg.width/2), mouseY-(cursorImg.width/2));
}

function mouseMoved(e) {
 
 mouseX = e.pageX;
 mouseY = e.pageY;
 
}

function clicked(e) {
 
 bulletHolesX[bulletNum] = mouseX;
 bulletHolesY[bulletNum] = mouseY;
 bulletNum++;
 if (bulletNum > 50) {
  bulletNum = 0;
 }
 
}
</script>
</head>
<body style="padding: 0px; margin: 0px;">
 <canvas id="canvas1" width="600" height="480" style="background-color: #A9A9A9;">
 Browser not compatible with HTML5 canvas
 </canvas>
</body>
</html>



Add Simple Game Physics (back to top)

Even the simplest physics can make a game look and feel so much better. Instead of just suddenly moving full speed to the right when you press right key, it can be that the character builds speed until they reach normal speed. It could be a case of only taking 1-2 seconds to build the speed, or it could take more like 10 seconds. It all depends on your game.
And there's gravity. There needs to be a fairly realistic rate of falling towards the ground. I don't mean close to earth's gravity, I just mean it can't just fall at a constant speed towards the ground. It needs to accelerate. (Although, this does depend on the game. There may be certain instances where a constant velocity is fine)



So let's start with a canvas of width 500x500. So that there will be room to move, to appreciate the physics a bit more.
<canvas height="500" id="canvas" style="background-color: darkgrey;" width="500">
Browser not compatible with HTML5 canvas
</canvas>


Now here's the javascript:
const FPS = 30;

var playerPosX = 0;
var playerPosY = 0;
var playerVelX = 0;
var playerVelY = 0;

var onGround = 0;

var playerImg = new Image();
playerImg.src = "http://filevaults.com/media/Slash/553481-smiley.png";

var canvas = null;
var context2D = null;
var keys = new Array();

window.onload = init;

window.addEventListener('keydown',keyDown,true);
window.addEventListener('keyup',keyUp,true);
function keyDown(evt){
 keys[evt.keyCode] = true;
}
function keyUp(evt){
 keys[evt.keyCode] = false;
}

function init()
{
 canvas = document.getElementById('canvas');
 context2D = canvas.getContext('2d');
 setInterval(draw, 1000 / FPS);
}

function draw()
{
 // detect user input
 if ((37 in keys &amp;&amp; keys[37]) || (65 in keys &amp;&amp; keys[65])){ //left
  playerVelX -= 3;
 }
 if ((39 in keys &amp;&amp; keys[39]) || (68 in keys &amp;&amp; keys[68])){ //right
  playerVelX += 3;
 }
 if ((38 in keys &amp;&amp; keys[38]) || (87 in keys &amp;&amp; keys[87])){ //up
  jump();
 }
 
 addPhysics();
 
 context2D.clearRect(0, 0, canvas.width, canvas.height);
 context2D.drawImage(playerImg, playerPosX, playerPosY);
}

function jump() {
 if (onGround == 1) {
  playerVelY -= 30;
 }
}

function addPhysics() {

 // because of these two lines, motion can be set using velocity variables
 playerPosX += playerVelX;
 playerPosY += playerVelY;
 
 
if (playerPosY &gt; canvas.height-playerImg.height) {
 onGround = 1;
}
else {
 onGround = 0;
}

// stop character from building up too much speed
// this means the character can realistically accelerate and continue at normal speed
 if (playerVelX &gt;= 10) {
  playerVelX = 10;
 } else if (playerVelX &lt;= -10) {
  playerVelX = -10;
 }
 
// friction
playerVelX *= 0.85;
if (playerVelX &lt; 0.2 &amp;&amp; playerVelX &gt; -0.2)
{ // so that it leaves it as a clean zero when it gets to a stop.
 playerVelX = 0;
}

 // gravity (acceleration of the velocity towards the ground)
 if (onGround == 0) {
 playerVelY += 3;
 }
 else {
 playerVelY = 0; // when on ground, gravity doesn't pull you through the floor
 }
 
}
Result here: http://slashgame.net/html5physics.php



How to Rotate an Image on Canvas (back to top)

Rotation is a tricky one to understand fully, especially for beginners. A lot of people prefer to just blindy accept the way of coding it, and other like to know what the heck they are actually coding. So I'll give the opportunity for both types of people here.

Firstly, I'll put this function here for you to use, this is helpful for any kind of person, but for those of you who just want the code, you only need to take the function and read my bit on how to use it. I'll then explain how rotation works, if you're interested.

Here's the function:
function drawImg(img, pX, pY, oX, oY, w, h, rot) {
 context2D.save();
  context2D.translate(pX+oX, pY+oY);
  context2D.rotate(rot * Math.PI / 180);
  context2D.drawImage(img, 0, 0, w, h, -(oX), -(oY), w, h);
 context2D.restore();
}

How it's used:
img: the image object
pX: the x position of the image
pY: the y position of the image
oX: how far across the image that the origin is
oY: how far down the image that the origin is
w: width of the image
h: height of the image
rot: angle of rotation


Ok, so it's not as simple as just "rotate image". There's a lot more to it. It's more like rotating the canvas, then setting the image's orientation, then resetting the canvas.

- So first it saves the canvas the way it is.
- Then the translate line makes the "start" of the image at the origin you want it to spin.
- It then rotates the actual canvas the amount you want the player to rotate.
- Then the image is drawn, but because the start of the image has changed place, it needs to start drawing at a different place (at the origin).
- It then brings the canvas back to where it was before, leaving the drawn image still showing as rotated.

It took me a while to figure that out at first. I was never able to find a good explanation of how it works. I just hope this helps you readers better.
Although to be honest, it isn't incredibly important that you know how it works. Just that you know how to use it.
Here's an example usage of rotation with canvas: http://slashgame.net/html5gamedevexample4.php



Use HTML5 Audio (back to top)

I'll just show HTML5 Audio being used with javascript without a canvas. Using it in canvas you just include it in the javascript like normal by calling the function that plays the sound. I very much so assume that by this point you can figure that kind of thing out yourself.
The JavaScript accesses the HTML5 audio, and in effect makes an

Example:
var boingSound = new Audio("http://slashgame.net/boing.wav");
function playSound() {
 boingSound.currentTime = 0;
 boingSound.play();
}

Notice how it sets the currentTime to 0 before playing. This is because you can't play the same sound object twice without moving the time back to the start again.


Here is a use of the result of that:
---CLICK HERE---



Conclusion of Tutorial (back to top)

So, by now you should be able to put your wonderful imagination to use, and put your new knowledge of HTML5 game development to practice, and create something awesome.

There may be some things that I haven't explained, but my hope is that I've explained enough so that you are able to figure new things out yourself. Things like complicated physics can be figured out with some Maths knowledge, and game design for the most part comes from your imagination and your ability to understand what people like in games.

3 comments:

  1. Hi there,

    I've been trying to modify
    http://slashgame.net/html5physics.php

    I want to swap out the character's raster image for a vector-drawn shape. I tried changing things like the following in the .js file

    var playerImg = context2D.strokeRect(0,0,80,80);

    but to no avail. What would it take to do this?

    Thanks for the tutorials. They were helpful!

    ReplyDelete
  2. Well, you would take away the var playerImg line (and the line after it where it gives the image url). Then go to the bit where it renders the image (in the draw() function). Replace the line where it draws playerImg with "context2D.strokeRect(playerPosX, playerPosY, 80, 80);"

    That should then work fine, I believe.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete