Let us Game!

This is the fifth article of an ongoing series about game development for the Web. The intention behind the two previous articles is to provide you with the minimum (but solid) tools to start developing two-dimensional games using the Canvas 2D Context API.

Up until now, we have learnt about key functions to draw simple shapes on the canvas element as well as functions to transform those shapes without having to update, manually, their location, orientation or scaling factors. It is time for us to dive deeper into the game development world and it is time to put to use what we have learned so far about the Canvas 2D Context API.


What is a game?

We all know at heart what a game is, but when it comes to putting it into words we may find it difficult since the term encompasses a wide range of activities that fall under our intuitive notion of what game is.

In his book, A theory of Fun for Game Design, Raph Koster defines a game to be an interactive experience that provides the player with an increasingly challenging sequence of patterns which he or she learns and eventually masters. On the other hand, Jason Gregory, in his book, Game Engine Architecture, reflects on this assertion adding that the activities of learning and mastering are at the heart of what we call fun, just as a joke becomes funny at the moment we get it by recognizing the pattern.

The definition of the word game is very broad and many other authors have different assertions with other types of validity. For the purposes of this series of posts, we will focus on the subset comprised by two-dimensional and three-dimensional virtual worlds were the player or players control a character or characters under a pre-determined set of rules using a computer. If you would like to expand on this topic, Gerard.IGN has a nice article about the term video game and its inadequacy.

Now that we got that out of the way, it is time for us to get into the technical details of how to create video games.


The Game Loop

Games, unlike other applications, need to be updating and processing data even if there is no user input. Real-time rendering is concerned with making images rapidly on the computer. An image appears on the screen, the viewer acts or reacts, and this feedback affects what is generated next. This cycle of reaction and rendering, happens at a rapid enough rate that the viewer does not see individual images, but rather becomes immersed in a dynamic process.

In order to achieve this, a series of operations need to run cyclically in order to interpret input, update and render the game to the player. The game loop is the heartbeat of the game, and like a real heart, it commonly runs at a fixed time lapse. The rate at which images are displayed is measured in frames per second (fps) or Hertz (Hz). There is a lot of discussion about what is the most suitable rate for the sense of interactivity. Displaying images at a rate of 15 fps should be enough to transmit that sense of interactivity. Obviously the higher the rate at which the images are displayed provides a more smooth and immersion, but the truth is that, after approximately 70 fps the difference is unperceivable. That said; games usually display images at a rate of 30 fps or 60 fps.

As described above, a naive implementation of the game loop processes player input, simulates the game world to react to that input, and then renders the scene. In a more advanced implementation of the main loop many sub-systems service periodically and independently, such as Artificial Intelligence, Sound, Networking, Collision Detection, etc. Since the time per iteration is fixed, all these systems must run their routines extremely fast. Hence, the endless interest of optimizing the way code runs from game developers. Faster executing code within, your main loop, is equivalent to smoother gameplay, period.


Implementing the main loop

        // Naive implementation of the main loop
        //
        while( true ) {
                 // Input
                 //  Update Simulation
                 //  Render
        }

        // The main loop is commonly expressed in code as an infinite loop.

The above stated implementation of the main loop executes sequentially the minimum routines to have a game. There is a problem with it though, it will execute as fast as it can. Taking into account that in an iteration of the loop an image is generated, the amount of images generated in a second will vary per hardware platform. This issue can be solved by using time and constraining the amount of iterations per second. This will allow us to control the amount of frames that are rendered in a second. In addition, it will guarantee that the user experience will be consistent across different hardware platforms.

Example:

    // Test Management
    //
    var noFrameLimitTestCounter   = 0;
    var AMOUNT_OF_TESTS           = 10;

    // Let's add a spinlock to wait until its time to update!
    //
    var DESIRED_FRAME_RATE        = 60.0;
    var DESIRED_SECONDS_PER_FRAME = ( 1.0 / DESIRED_FRAME_RATE );
    var DESIRED_MILLISECONDS_PER_FRAME = DESIRED_SECONDS_PER_FRAME * 1000;

    console.log("Desired Frame Rate:" + DESIRED_FRAME_RATE);

    // Time Management
    //
    var nextUpdate = new Date().getTime();
    var initTime   = new Date().getTime();

    // Test Management
    //
    var frameLimitTestCounter = 0;

    while(frameLimitTestCounter < AMOUNT_OF_TESTS)
    {
        while(new Date().getTime() >= nextUpdate) // let's wait to update!
        {       
            nextUpdate = new Date().getTime() + DESIRED_MILLISECONDS_PER_FRAME;
            amountOfUpdates++;
        }

        elapsedTime = new Date().getTime() - initTime;

        if( elapsedTime >= 1000 )
        {       
            frameLimitTestCounter++;
            console.log("Test #" + frameLimitTestCounter + " " + (amountOfUpdates)
                + " updates in a second.");

            // Reset variables for next test
            elapsedTime = 0;        
            amountOfUpdates = 0;
            initTime = new Date().getTime();
        }
    }

Since in this series we are targeting the Web, as a platform, some considerations need to be made about how we build our main loop. Browsers, themselves, implement a main loop to render Web pages and repaint their windows at a fixed frame rate.

Implementing infinite loops in JavaScript, like the ones stated above, will block the browser’s UI thread. This will make your browser unresponsive since it cannot repaint the window; therefore, unless we are using an independent platform like node.js to execute our JavaScript code, we should avoid them.Because of this, the browser provides window interfaces to call functions at a fixed amount of time from JavaScript. These tools allow us to create a main loop with a fixed frame rate without having to implement the time handling ourselves.

window.SetInterval

The purpose of this API method is to call a function or execute a code snippet repeatedly, with a fixed time delay between each call to that function. You can see the official documentation here.

Example:

    // Test Management
    //
    var noFrameLimitTestCounter    = 0;
    var AMOUNT_OF_TESTS            = 10;

    // Time Management
    //
    var amountOfUpdates            = 0;
    var setIntervalTestCounter     = 0;
    var time                       = undefined;
    var lastFireTime               = undefined;        
    var initTime                   = new Date().getTime();

    var myInterval = setInterval(function(){ 

        time = Date.now();

        if (lastFireTime !== undefined)
        {
            // Returns time it taken between function calls
            //console.log(time - lastFireTime);         
        }

        lastFireTime = time;        
        amountOfUpdates++;                
        elapsedTime = new Date().getTime() - initTime;

        if(elapsedTime >= 1000)
        {               
            setIntervalTestCounter++;
            console.log("Test #" + setIntervalTestCounter + " " 
                + (amountOfUpdates) + " updates in a second.");

            // Reset variables for next test
            elapsedTime = 0;        
            amountOfUpdates = 0;
            initTime = new Date().getTime();
        }

        if(setIntervalTestCounter >= AMOUNT_OF_TESTS)
        {
            window.clearInterval(myInterval);
        }
    }, DESIRED_MILLISECONDS_PER_FRAME);

window.requestAnimationFrame

This is one of the new additions as a consequence of the interactivity that HTML5 provides. The purpose of this API method is to tell the browser that you wish to perform an animation. Then, it requests the browser to call a specified function to update an animation before the next repaint. Here is a link to the official documentation.

Example:

(function() {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
            window.cancelRequestAnimationFrame = window[vendors[x]+
              'CancelRequestAnimationFrame'];
        }

        if (!window.requestAnimationFrame)
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
                  timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };

        if (!window.cancelAnimationFrame)
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
    }());

    var myReq                       = undefined;
    var requestAnimationTestCounter = 0;

    // Test Management
    //
    var noFrameLimitTestCounter = 0;
    var AMOUNT_OF_TESTS         = 10;

    // Time Management
    //
    var initTime        = new Date().getTime();
    var elapsedTime     = 0;
    var amountOfUpdates = 0;

    function step() 
    {
        amountOfUpdates++;
        elapsedTime = new Date().getTime() - initTime;  
        if(elapsedTime >= 1000)
        {               
            requestAnimationTestCounter++;
            console.log("Test #" + requestAnimationTestCounter + " " 
                + (amountOfUpdates) + " updates in a second.");

            // Reset variables for next test
            //
            elapsedTime = 0;        
            amountOfUpdates = 0;
            initTime = new Date().getTime();
        }

        if(requestAnimationTestCounter >= AMOUNT_OF_TESTS)
        {
            window.cancelAnimationFrame(myReq);
        }
        else
        {
            requestAnimationFrame(step);
        }
    }

    myReq = requestAnimationFrame(step);

Working with Real-Time Rendering is a little bit different when targeting the Web. We cannot control how HTML elements are rendered by the browser and in some cases we hand over the responsibility to the canvas element itself using the context API. It is very important that we remember this, since some of our decisions moving forward might feel odd when compared to traditional game development.


Double Buffering

Double buffering is a technique of drawing an image to a memory buffer before displaying it to the screen in order to avoid flickering, tearing, and other artifacts.

The pixels that comprise the image are written to an array located in the RAM called the framebuffer. Regardless of how, the ultimate purpose of our game code is to produce the pixel values that ultimately end up being placed in the framebuffer. Similar to the problems related to modifying an array while iterating through its contents, modifying the framebuffer while the pixel values are being rendered to the screen might result in undesired images or behaviors while drawing them. Therefore, a common thing is to drawing to another memory location called the back buffer and swapping it the framebuffer.

Demonstration of front & back buffer swaps:

BACK BUFFER

FRONT BUFFER


Double buffering is a very common technique for real-time rendering of games, simulations and animated movies. The canvas element implements double buffering by default as long as we are using the 2D Context API to render primitives and images. When we implement animations, it's ideal to take the matter into our hands, resulting in a back buffer clear at the beginning of every rendering call and a swap of the buffers at the end. Many other graphics libraries implement double buffering as well and provide methods and interfaces to perform the swap for us.


Conclusion

In this article, we discussed the concept of a game; we talked about the heart of games and a common technique to display images on screen. Further, in this series, we will implement the concepts in a full-fledged example.


References

J. Gregory, Game Engine Architecture, 1st ed., Boca Raton: A K Peters/CRC Press, 2009.

R. Koster, A Theory of Fun for Game Design, 2nd ed., O’Reilly Media, 2013.

M. McShaffry and D. Graham, Game Coding Complete, 4th ed., Cengage Learning PTR, 2012.

T. Akenine-Moller, E. Haines and N. Hoffman, Real-Time Rendering, 3rd ed., Boca Raton: CRC Press, 2008.