Game Career Guide is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Get the latest Education e-news
 
  • Build a Web Game!

    [06.05.14]
    - James Long
  • The web is everywhere, and offers a very powerful and non-traditional environment for creating and distributing apps. If you want to make a change to your program, you can simply edit the code and then try it out live in your browser without waiting for your whole program to compile. Additionally, it's relatively painless to distribute your app across a huge number of platforms. It's exciting that in the past few years, developing games using HTML5, the technology behind the web, has become a reality.

    The "canvas element" (https://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html) was introduced with HTML5 (https://en.wikipedia.org/wiki/HTML5), and it provides an API for rendering on the web. The API is simple, but if you've never done graphics work before it might take some getting used to. However, it has great cross-browser support (https://caniuse.com/#feat=canvas) at this point, and it makes the web a viable platform for games.

    Using canvas is simple: just create a `` tag, create a rendering context from it in javascript, and use methods like `fillRect` and `drawImage` on the context to render shapes and images. The [API](https://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html) has a lot of methods for rendering arbitrary paths, applying transformations, and more.

    In this article, we're going to create a 2d game with canvas; a real game with sprites, animations, collision detection, and of course, explosions! What's a game without explosions?

    You can play the game we're going to make here: https://jlongster.github.com/canvas-game-bootstrap/ . I wrapped this up into a "game bootstrap project" that you can use to quickly get started (https://github.com/jlongster/canvas-game-bootstrap). I recomend checking out the source and running it locally by opening "index.html" in a text editor.

    Gearing Up

    The game might look complex, but it really just boils down to a few technical components. I've always been amazed how far you can go with canvas, simple collision detection, some sprites, and a game loop.

    However, in order to focus on the game components, I'm not going to fully explain every single line of code and API call. This tutorial is going to be somewhat advanced, but I hope that it's clear enough so that people of all skill levels can follow along. It's meant to explain basic game concepts, with a few more advanced techniques like sprite animations mixed in. This article assumes that you are familiar with JavaScript and basic HTML APIs. It also skims over the canvas API and some basic game concepts like the game loop.

    For a more basic tutorial, check out these two articles: "How to make a simple HTML5 Canvas Game" (https://www.lostdecadegames.com/how-to-make-a-simple-html5-canvas-game/) and "HTML5 Snake source code walkthrough" (https://jdstraughan.com/2013/03/05/html5-snake-with-source-code-walkthrough/).

    Using Free Art Assets

    Lots of would-be game devs get stalled out during their first few times making games because there is so much to learn, and you don't really have the time or energy to learn how to make good-looking artwork and make a game. That's why I highly recommend using a free set of graphics until you have time to make your own. I personally like browsing around HasGraphics (https://hasgraphics.com/) for free art assets when I'm working on practice games like this. I'm using the "Hard Vacuum" (https://www.lostgarden.com/2005/03/game-post-mortem-hard-vacuum.html) set for this example game. This way, you'll also learn how real graphics are stored and how to work with them.

    Creating the Canvas

    Let's start by digging into the code. Most of the game is in app.js (https://github.com/jlongster/canvas-game-bootstrap/blob/master/js/app.js), which is in the `js` directory.

    The very first thing we do is create the canvas tag, set the width and height, and add it to the "body" tag. We do this dynamically to keep everything in Javascript, but you could add a "canvas" tag in the HTML file and use something like "getElementById" to get it too. There's no difference between these two methods-it's just a matter of preference where the canvas element is created.

    // Create the canvas

    var canvas = document.createElement("canvas");

    var ctx = canvas.getContext("2d");

    canvas.width = 512;

    canvas.height = 480;

    document.body.appendChild(canvas);

    The `canvas` element has a `getContext` method which is what you use to get the rendering context. The context is the object on which you call all of the rendering APIs. You can also pass `webgl` if you want a WebGL(https://developer.mozilla.org/en-US/docs/WebGL) context for 3D scenes, though we won't need that for this project. From here on, we will be using the `ctx` variable to render everything.

    Game Loop

    You need a game loop that continually updates and renders the game. You can see what it looks like in Listing 1.

    // The main game loop

    var lastTime;

    function main() {

        var now = Date.now();

        var dt = (now - lastTime) / 1000.0;

        update(dt);

        render();

        lastTime = now;

        requestAnimFrame(main);

    };

    Listing 1: The main game loop.

    You update and render the scene, and then use requestAnimationFrame to queue up the next loop. It's basically a smarter way of saying "setTimeout(main, 1000 / 60)", which attempts to render a 60 frames/second. At the very top of app.js we shim rAF as the "requestAnimFrame" function, since not all browsers support it yet.

    Note: Never, ever use "setTimeout(main, 1000 / 60)", as it's less accurate and also wastes a lot of cycles by rendering when unnecessary.

    The "update" function takes the time that has changed since the last update. Never update your scene with constant values per frame (like "x += 5;"). Your game will run wildly different on various computers and platforms, so you need to update your scene independently of framerate.

    This is achieved by calculating the time since last update (in seconds), and expressing all movements in pixels/second units. Movement then becomes "x += 50 * dt", or "50 pixels per second".

    Loading Resources and Starting the Game

    The next section of code initializes the game and loads all resources. This uses one of the few separate utility classes that I wrote called "resources.js" (https://github.com/jlongster/canvas-game-bootstrap/blob/master/js/resources.js). It's a very simple library that loads images and fires an event when they are all loaded.

    Games require a lot of assets, like images, scene data, and so on. For 2D games, most or all of the assets are images. You need to load all your assets before starting the game so that they can be immediately used. It's easy to load an image in Javascript and do something when it's available:

    var img = new Image();

    img.onload = function() {

        startGame();

    };

    img.src = url;

    If you have several images to load, though, this gets really tedious; You need to make a bunch of global variables, and in each "onload" check if all of them are loaded. That's why I wrote a basic resource loader to handle all of this automatically. Get ready for some code in Listing 2!

    (function() {

        var resourceCache = {};

        var loading = [];

        var readyCallbacks = [];

        // Load an image url or an array of image urls

        function load(urlOrArr) {

            if(urlOrArr instanceof Array) {

                urlOrArr.forEach(function(url) {

                    _load(url);

                });

            }

            else {

                _load(urlOrArr);

            }

        }

        function _load(url) {

            if(resourceCache[url]) {

                return resourceCache[url];

            }

            else {

                var img = new Image();

                img.onload = function() {

                    resourceCache[url] = img;

                   

                    if(isReady()) {

                        readyCallbacks.forEach(function(func) { func(); });

                    }

                };

                resourceCache[url] = false;

                img.src = url;

            }

        }

        function get(url) {

            return resourceCache[url];

        }

        function isReady() {

            var ready = true;

            for(var k in resourceCache) {

                if(resourceCache.hasOwnProperty(k) &&

                   !resourceCache[k]) {

                    ready = false;

                }

            }

            return ready;

        }

        function onReady(func) {

            readyCallbacks.push(func);

        }

        window.resources = {

            load: load,

            get: get,

            onReady: onReady,

            isReady: isReady

        };

    })();

    Listing 2: Our sample resource loader.

    The way this works is your game calls `resources.load` with all the images to load, and then calls `resources.onReady` to register a callback for when everything is loaded. This assumes that you won't call `resources.load` later in the game; it only works at startup.

    It keeps a cache of images in `resourceCache`, and when the image loads it checks to see if all the requested images have loaded, and if so calls all the registered callbacks. Now we can just do this in our game:

    resources.load([

        'img/sprites.png',

        'img/terrain.png'

    ]);

    resources.onReady(init);

    To get an image once the game starts, we just do `resources.get('img/sprites.png')`. Easy!

    In the above code, `init` (Listing 3) is called when all the images are loaded, which creates the background pattern, hooks up the "Play Again" button, resets the game state, and starts the game.

    function init() {

        terrainPattern = ctx.createPattern(resources.get('img/terrain.png'), 'repeat');

        document.getElementById('play-again').addEventListener('click', function() {

            reset();

        });

        reset();

        lastTime = Date.now();

        main();

    }

    Listing 3: Our 'init' function sets the stage once all the images are loaded.

Comments

comments powered by Disqus