Please note: this blog has been migrated to a new location at https://jakesgordon.com. All new writing will be published over there, existing content has been left here for reference, but will no longer be updated (as of Nov 2023)

Javascript Game Foundations - Player Input

Thu, Dec 5, 2013

Ten Essential Foundations of Javascript Game Development

  1. A Web Server and a Module Strategy
  2. Loading Assets
  3. The Game Loop
  4. Player Input
  5. Math
  6. DOM
  7. Rendering
  8. Sound
  9. State Management
  10. Juiciness

Player Input

Games are interactive. Without input from a player, they would be stories.

Input via the keyboard and mouse is the most common method for games that run on desktop or laptop computers, ← ↑ → ↓, W,A,S,D,<ENTER>,<SPACE>, mouse-look, etc.

Input via a touch screen is added for games that run on mobile devices.

Event Handlers

In order to get input from the player we need to attach event handlers to the browser DOM. This was more complex back when we had to support older browsers, but modern browsers support the standard addEventListener method:

Most games run standalone within a single web page, so we typically want to attach our keyboard event handlers to the top-level document object in order to handle all keyboard events within the page, independent of focus.

For mouse and touch events, it usually makes more sense to anchor those handlers to the HTML5 <canvas> element to make calculating relative coordinates simpler.

document.addEventListener('keydown',    onkeydown,    false);
document.addEventListener('keyup',      onkeyup,      false);

  canvas.addEventListener('click',      onclick,      false);
  canvas.addEventListener('mousemove'   onmousemove,  false);
  canvas.addEventListener('touchstart', ontouchstart, false);
  canvas.addEventListener('touchmove',  ontouchmove,  false);


function onkeydown(event) {
  ...
}

function onkeyup(event) {
  ...
}

function onclick(event) {
  ...
}

function onmousemove(event) {
  ...
}

function ontouchstart(event) {
  ...
}

function ontouchmove(event) {
  ...
}

If the event was handled by our game, we should also prevent the browser’s default action (e.g. to stop the up/down keys from scrolling the page)

function onkeydown(event) {
  ...
  event.preventDefault();
}

Keyboard Input

Once we have attached to the keydown and keyup events, we can use the event.keyCode attribute to check which key has been pressed.

If we are already using a 3rd party library such as jQuery, they may provide an enumeration of the possible keyCode values for us, but if not, we can easily define our own.

Here are some common values:

var KEY = {
    BACKSPACE: 8,
    TAB:       9,
    RETURN:   13,
    ESC:      27,
    SPACE:    32,
    PAGEUP:   33,
    PAGEDOWN: 34,
    END:      35,
    HOME:     36,
    LEFT:     37,
    UP:       38,
    RIGHT:    39,
    DOWN:     40,
    INSERT:   45,
    DELETE:   46,
    ZERO:     48, ONE: 49, TWO: 50, THREE: 51, FOUR: 52, FIVE: 53, SIX: 54, SEVEN: 55, EIGHT: 56, NINE: 57,
    A:        65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90,
    TILDA:    192
  };

Most of the time, the keyboard event handlers should simply record the current state of the key, pressed vs not pressed. It should still be the responsibility of the update() method in the game loop to actually update the game world.

Since we only need to record the state (key pressed vs not pressed) we can use a common onkey function for both keydown and keyup events and record the state in a player.input object:

var player = {
  ...
  input: { left: false, right: false, jump: false }
}

document.addEventListener('keydown', function(ev) { return onkey(ev, ev.keyCode, true);  }, false);
document.addEventListener('keyup',   function(ev) { return onkey(ev, ev.keyCode, false); }, false);

function onkey(ev, key, pressed) {
  switch(key) {
    case KEY.LEFT:  player.input.left  = pressed; ev.preventDefault(); break;
    case KEY.RIGHT: player.input.right = pressed; ev.preventDefault(); break;
    case KEY.SPACE: player.input.jump  = pressed; ev.preventDefault(); break;
  }
}

Now our update() method can modify the game state based on player.input.

NOTE: I haven’t used it yet, but have recently discovered a small micro-library called keypress that looks like a promising, lightweight library for dealing with more complex keyboard handling scenarios (such as modifier-keys, combo-keys, etc) - check it out!

Touch Input

To detect a touch enabled device, you can use the Modernizr library, or you can treat Modernizr more like a mentor and just learn from it:

var hasTouch = ('ontouchstart' in window);

The touch events follow a similar pattern to mouse events:

canvas.addEventListener('touchmove', ontouchmove, false);

Touch events include the targetTouches attribute which is a list of fingers touching the current DOM element. For the simple case, you can assume a single finger touch:

function ontouchmove(ev) {
  var x = ev.targetTouches[0].pageX;
  ...
}

Others have written about touch events in much more detail.

An important note about the touch events and coordinates. Supposedly, they should provide 3 sets of coordinates: client, page and screen. It makes sense in a <canvas> game to respond to the touch events on the <canvas> element and use the client coordinates to know where on the canvas was touched.

This works great in iOS, but on some android devices the clientX and clientY coordinates are incorrect, they actually contain the same values as the pageX and pageY coordinates.

Ugh! This means you can’t rely on clientX and clientY, so you have to use pageX/Y instead and convert it to canvas relative coordinates yourself - be wary.

Mouse Input

I haven’t actually had the need for mouse input in any of my games so far, so I’m going to cheat a little here…

… blah blah blah mousedown, mouseup, mouseover, mouseout, and mousemove, blah blah blah blah blah click and dblclick blah blah blah…

… and simply refer you to others who have written more extensively about this subject than I:

Hmmm, I bet I could post articles a lot more frequently if I could just write ‘blah blah blah’ :-)