HTML5 Games

Starting With The Basics

Matt Wood / @doowttam

Background

  • Web Developer by trade
  • Games are an interesting problem

Goal of this talk

Review aspects of a game and how you might deal with those aspects in JavaScript


A Note on Game Engines

I'm interested in how game engines work, but to really make games you should at least check out the available game engines.

List of Game Engines

Aspects of a Game

  • Initialization / asset loading
  • Game loop
  • Rendering graphics
  • Audio (Not discussed)
  • Controls
  • Non-core features (Not discussed)
    • Game saves
    • Mulitplayer net code

Game Loop

  • The central structure to a game
  • Can be metaphorical in a simple game (wait for input, do action, wait for input...)
  • Usually implemented as a running loop with requestAnimationFrame (or setInterval/setTimeout, but not recommended)

Benefits of rAF

  • Typically only runs when tab is visible
  • Browser can do some optimization, like syncing with CSS animations

In the Loop

  • update entities based on previous state, time, and user input
  • render entities (could do as part of update, but probably want to render in a different order than update)

Example Using rAF

var drawFrame = function() {
    var now = Date.now();
    var dt  = (now - lastTime) / 1000.0;

    // dt can be used to have entities do "actions per second"
    update(dt);
    draw();

    lastTime = now;
    requestAnimationFrame(drawFrame);
};

Need a polyfill for this to work.

Canvas

The canvas element provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics, art, or other visual images on the fly.

W3C HTML5 specification

Basic Drawing

var canvas  = doc.getElementById('basic');
var context = canvas.getContext('2d');
 
// Draw the line
context.strokeStyle = "red";
context.lineWidth = 5;
 
context.beginPath();
context.moveTo(20, 20);
context.lineTo(175, 260);
context.lineTo(240, 60);
context.closePath();

context.stroke();
 
// Draw the rectangle
context.fillStyle = "blue";
context.fillRect( 30, 160, 60, 20 );
 
// Draw the circle
context.fillStyle   = "orange";
context.strokeStyle = "purple";
 
context.beginPath();
context.arc(170, 60, 45, 0, Math.PI * 2, false);

context.stroke();
context.fill();	
Click for demo!

Drawing Images

// Basic draw (not shown)
context.drawImage( img, 0, 0 );

// Slice from the image
context.drawImage( img, 0, 0, 50, 90, 20, 100, 50, 90);

// Slice and scale, not generally helpful
context.drawImage( img, 0, 0, 50, 90, 135, 75, 100, 180);
});
Drawing sprites from the detective sprite sheet
Click for demo!

Animating

var width       = 25;
var totalFrames = 8;
var frame       = 0;

rAF(function() {
    var offset = width * (frame % totalFrames);
    context.drawImage( img, offset, 0, 50, 90, 25, 30, 50, 90);
 
    frame++;
}, 150 );
Click for demo!

Animating: Second Try

var width       = 25;
var totalFrames = 8;
var frame       = 0;

rAF(function() {
    // Reset canvas
    canvas.clearRect(0, 0, canvas.width, canvas.height);

    var offset = width * (frame % totalFrames);
    context.drawImage( img, offset, 0, 25, 45, 140, 20, 25, 45);
	
    frame++;
}, 150 );
Details on Canvas reset performance
Click for demo!

Initialization / asset loading

  • Initilize things like the map, character, etc.
  • Preload all of your art and sound assets

One Technique

  1. List assests
  2. Loop through them, creating new objects
  3. Have load event listeners increment loaded count
  4. When loaded (+ errors) === total, you're done!
  5. Keep track of objects made for later use

Example

var imageCount = 0;
var images     = [ 'map.png', 'avatar.png' ];

// Some imaginary function that updates a progress bar
showLoading( imageCount, images.length );		  

// The callback each image will call when fully loaded
var resourceOnLoad = function() {
    imageCount++;
    showLoading( imageCount, images.length );

    // We're finished
    if ( imageCount == images.length ) {
        // Some imaginary function that starts the game
        startGame();
    }
};

for ( var i = 0; i < images.length; i++ ) {
    var img = new Image();
    img.addEventListener('load', resourceOnLoad);
    img.src = images[i];

    // Some imaginary function that makes the asset
    // available in a global data structure
    addAsset( images[i], img );
}

Input Can Be Tricky

User input in browsers isn't optimized for games, it's optimized for filling out forms.

Example

Hold a key down in each box

Normal:
Async:

Asynchronously track input

  • Smooths out keyboard events
  • Can be important for touch event performance

Simplest Code

var keyPressed = false;

// Event listeners do something very simple
asyncInput.addEventListener('keydown', function() {
    keyPressed = true;
});
asyncInput.addEventListener('keyup', function() {
    keyPressed = false;
});

// Read it when we want it
if ( keyPressed ) {
    EXAMPLES.drawCircle();
}

More Complete Code

KEY = {
    pressed: {},
    codes: {
        "LEFT":  37,
        "UP":    38,
        "RIGHT": 39,
        "DOWN":  40
    },
    isDown: function(keyCode) {
        return pressed[keyCode];
    },
    onKeyDown: function(event) {
        pressed[event.keyCode] = true;
    },
    onKeyUp: function(event) {
        delete pressed[event.keyCode];
    }
};

// Listeners set up in init
window.addEventListener('keyup', function(e) {
    KEY.onKeyUp(e);
});

window.addEventListener('keydown', function(e) {
    KEY.onKeyDown(e);
});

// Read state when you want it
if ( KEY.isDown(KEY.codes.LEFT) ) {
  character.moveLeft(); // Take some action
}
Credit to Arthur Schreiber

Other Interesting HTML5 Features

  • WebSockets
  • Web Audio API
  • localStorage

But, Actually...

Would you write a web framework everytime you wrote a new web app?

All of this stuff would be taken care of by a game engine.

Slides available at
doowttam.com/html5-games-talk