a blog about things that I've been thinking hard about

Notes on Writing a 2-D Video Game in Javascript

28 August, 2006
animated Javascript 2D video game

PrimeShooterTM is a game where you shoot numbers with their prime factors.

All the game elements are HTML <div> elements.

Animation is done via CSS positional properties ("left" and "top").

The game loop is implemented using the Javascript function setinterval.

Some collision detection is required.

PrimeShooterTM

This article is based on my experience of writing the PrimeShooterTM game in Javascript.

The following notes discuss the code which includes PrimeShooter.html (as a text file), PrimeShooter.css and PrimeShooter.js.

Should You Write a 2-D Video Game in Javascript/HTML/CSS?

By itself, browser Javascript (i.e. Javascript + HTML + CSS) is not the ideal platform for writing games with real-time animation and user interaction. In Javascript there is no control of graphical buffering or updating. Basically you update the DOM elements, and hope that the browser does something reasonable to redraw them. As a result, your game display may be jumpy and/or flickery.

Nevertheless, there are some good reasons to try writing a simple animated game in Javascript:

The most popular web-enabled video game platform is Flash, and there are some open-source languages that can be used to write Flash applications, although I have not tried using any of these yet. (Porting PrimeShooterTM would be a good way for me to learn this.)

Cross-Browser Compatibility

If you are writing a Fortune 100 website, you want your website to work with every browser under the sun, otherwise people will think your giant company is too lame to make a website work properly. For the purposes of my game, I decided that it would be sufficient to make it work with the latest versions of Internet Explorer, Firefox and, perhaps if I had time and it wasn't too hard, Opera.

There are various cross-browser compatibility Javascript libraries out there. However I do not know if any of them are optimised for animated 2D games (since that isn't a very common type of Javascript application) and I wanted to learn stuff, so it seemed easier just to program from scratch and see how I got along.

Developing and Debugging

In theory writing Javascript code is as easy as writing it in your text-editor (which for me is Emacs in Java mode), and then reloading the HTML page in the browser.

Personally I am a fan of so-called "printf" debugging, as opposed to, say, using a debugger. Unfortunately Javascript's default "printf" is the dreaded and annoying alert function. In the end I concocted my own simple console facility which I implemented in a file console.js.

Update (July, 2013): console.js is now obsolete, because these days web browsers come with their own Javascript consoles.

For a default development environment I chose Mozilla Firefox, because it is somewhat more developer friendly than Internet Explorer. The FireBug extension is essential for identifying Javascript errors and their location in source code.

However, if you develop code to run in one browser, it will fail in the other browser, because you will write code which is not cross-browser compatible, so you do have to occasionally test the application in the other browser. And if something doesn't work as expected, "printf" debugging with console.js is usually sufficient to find out what the problem is.

Javascript is Object-Oriented (But It Does Classes In a Funny Way)

If you're used to the object systems of Java, Python, Ruby or even C++, you will be slightly confused to find that Javascript pretends not to have classes. Everything is just objects, and all attributes and methods are simple named properties held in a hash map. However, every function has a special prototype property, which is shared between all objects that are constructed using the new operator applied to that function.

Here is a simple example:

function Cat (name) {
  this.name = name;
}

Cat.prototype = {

  numLegs: 4,

  sayHello: function() {
    alert ("Meow!! from an animal with " + this.numLegs + " legs.");
  }
}

The function Cat is actually a constructor, which you call as follows:

var myCat = new Cat("Tiddles");

This call creates a new empty object whose prototype is the Cat prototype, and then invokes the Cat function with the implicit dynamic this parameter set to the new object.

The properties of the Cat.prototype object are "inherited" by any instance of the Cat class, such as myCat. So the value of myCat.numLegs will be 4. And you can invoke the "method" sayHello as follows:

myCat.sayHello();

If this code is executed, you will get an alert box saying "Meow!! from an animal with 4 legs". Similar to the new call, the method invocation on an object sets the dynamic this parameter to be the object that the method is being invoked on.

Unfortunately this somewhat roundabout way of defining classes and methods doesn't provide any easy way of doing inheritance, so if you want inheritance you might want to use one of the special Javascript libraries written to support inheritance. As it happened, PrimeShooterTM wasn't complicated enough to need inheritance, so I didn't bother with this.

Strict XHTML

As I was writing my game, and comparing it in Firefox and IE, I noticed that my "gun" was a different size in IE. I traced this to IE's "quirks" mode. The official cure for this is to use XHTML "strict" mode, i.e. :

<!DOCTYPE html 
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

Of course I then found that setting XHTML to strict broke several things in my existing Javascript code, in particular you cannot assign numerical values to position attributes; instead you have to write code like this:

/** Set "left" attribute of style to "pos" pixels. */
function setLeft (elt, pos) {
  elt.style.left = pos + "px";
}

This probably counts as another reason why Javascript is not the optimal gaming platform: you have to convert all numerical co-ordinates into attribute strings just so that the browser can parse the strings and convert them back into numbers.

Update (2013): Now HTML5 is the preferred HTML version. (And just like XHTML, HTML5 enforces the "px" requirement for positional attributes.)

Models, Views and Controllers

The "sprites" of your Javascript game are going to be DOM elements. To support the logic of the game these sprites will need to have attribute values maintained and updated. One way to do this is to take advantage of the free-form nature of Javascript and just define these attributes directly on the DOM elements. (For examples of older code where I do this, see the Javascript in the source of the pages factorizer.html and sieve.html.)

However, this approach is a bit untidy, as it involves "polluting" an existing namespace (i.e. DOM element attributes). Also there is no way for the relevant DOM elements to belong to newly defined classes.

I decided to follow a more structured approach and create explicit "model" classes representing the state of the game sprites; these are the classes Bullet, Gun, SpareGuns and Number. Each "model" object is assigned a view attribute which is the corresponding DOM element (or elements), passed in at construction time. One can define a corresponding model attribute in each DOM element (limiting the "pollution" to a single attribute value), although this wasn't strictly necessary for the PrimeShooterTM game, since all user interaction is driven from the keyboard, so there is never any need to follow references from DOM elements to model objects.

I also defined a Game class which represents the state of the game as a whole, and a GameController class which is responsible for the general mechanics of starting the game loop and handling key events.

Key Event Quirks

Handling key events is one place where code needs to deal with a difference between Firefox and IE, as in the following code excerpt:

    this.document.onkeypress = function(event) { // set up keypress handler
      if (!event) event = window.event;  // IE event

Another issue I discovered had to do with Opera. When I tried playing the game in Opera, all sorts of funny things happened to the browser window. I thought at first that Opera was just buggy, but then I realised that keys like "5" and "7" are keyboard shortcuts in Opera (where they aren't in IE or Firefox), and the problem was my fault because the game wasn't consuming the keystrokes that it was handling. The fix is to make sure that the onkeyup, onkeydown and onkeypress handler functions return false when you don't want the browser itself to execute keystrokes. (But don't consume keystrokes unnecessarily, for example it is annoying if you discard the normal keystroke shortcuts for the forward and back buttons.)

CSS relative and absolute

The CSS position property value of relative has two distinct and simultaneous meanings:

  1. The location of the styled element is relative to its normal location in the flow. (But by default the relative positioning is (0,0), so the default relative position is just the same as the default position.)
  2. The positions of elements styled with position property absolute are positioned relative to the innermost containing element styled position: relative.

Given that the moving "sprites" in your Javascript game will consist of DOM elements controlled by setting their left and top CSS properties, you will want to define the outer frame of your game as a <div> element with the relative position property, and assign the value absolute to the position property of the sprites.

The Game Loop

The Javascript function for calling a function at repeated intervals is setInterval, and you can see the call to this function in my code as a method of my GameController class:

  /** Start the game loop */
  startLoop: function() {
    var game = this.game;
    var controller = this;
    this.loopHandle = setInterval (function(){game.update();}, this.loopInterval);
  },

Note: You might be wondering why I have to set the local variable controller to have the value of this. It's because this is always a dynamic variable in Javascript, and is not lexically bound into closures. So if you want to create a closure which refers to the current value of this, you have to create a local variable (which does get bound into the closure) and assign the value of this to that variable.

Collision Detection

Given two objects X and Y with position vectors rx and ry, you want to be able to test whether or not the objects are collided as a function of rx - ry.

The solution is that objects X and Y overlap if the set

\[\{ (\mathbf{x}, \mathbf{y}) \mid \mathbf{x} \in \mathbf{X} \land \mathbf{y} \in \mathbf{Y} \land \mathbf{x} + \mathbf{r}_x = \mathbf{y} + \mathbf{r}_y \}\]

is non-empty. This is equivalent to the condition

\[\mathbf{r}_x - \mathbf{r}_y \in \mathbf{Z}\]

where Z is the set

\[\{ \mathbf{y} - \mathbf{x} \mid \mathbf{x} \in \mathbf{X} \land \mathbf{y} \in \mathbf{Y} \}\]

If X and Y are arbitrary shapes, then the shape Z should be pre-computed. However there is no easy way to efficiently determine membership of a point in an arbitrary shape in Javascript (unless perhaps you separately encode the shape Z as a suitable array of arrays).

One can avoid this problem by making all sprites a rectangular shape, in which case if X is a rectangle with width wx and height hx and Y is a rectangle with width wy and height hy, then Z will be rectangle with width wx + wy and height hx + hy. Which explains the following code:

//================================================================================
/** Do rectangular elements element1 & element2 overlap?
 * Assumes that they are positioned relative to the same containing element.
 **/
function collided (element1, element2) {
  var x1 = element1.offsetLeft;
  var x2 = element2.offsetLeft - element1.offsetWidth;
  var x3 = element2.offsetLeft + element2.offsetWidth;
  if (x2 <= x1 && x1 <= x3) {
    var y1 = element1.offsetTop;
    var y2 = element2.offsetTop - element1.offsetHeight;
    var y3 = element2.offsetTop + element2.offsetHeight;
    return y2 <= y1 && y1 <= y3;
  }
  else {
    return false;
  }
}

(In the above code, element1 is X, element2 is Y, and the variables x2, x3, y2 and y3 represent the boundaries of element2 as if it had a size of the hypothetical Z object.)

The other thing to watch with collision detection is that if you are checking it once per game loop, then you want to make sure that the objects aren't moving towards each other so fast that they can skip past each other, i.e. their total distance travelled relative to each other per game loop should not exceed the size of the vector corresponding to their combined width and height.

CSS z-index property

The CSS z-index property lets you specify which thing goes on top of what. For a game this is important where objects may be passing over or under each other.

CSS overflow property

It is very common in video games for objects to enter from off-screen, and sometimes exit off-screen and reappear (perhaps on the opposite side). To make this entering and exiting look smooth, you want to set your main game <div> element to have the overflow: hidden property.

<noscript>

Don't forget that there is some percentage of users who will come to your Javascript game with Javascript turned off. The <noscript> element is where you tell them to turn it on again. (And I indulge in big bright letters for this because it's an important thing to tell them.) For example:

<noscript><div class="noscript">If you can see this message it is because you have
disabled Javascript in your browser. You must turn Javascript on in order to use 
this interactive page.</div></noscript>

and (in CSS):

.noscript { /* for message if Javascript is disabled */
  font-size: 1.4em;
  font-family: Helvetica, sans-serif;
  background-color: #e02020;
  color: #ffffff;
  padding: 0.3em;
  margin: 0.2em;
  border: solid 3pt black;
}

Things I Haven't Done Yet But Which I Will When I Get Around To It

One thing I should be able to do, but haven't done so far, is add sound effects to the game which would run using the sound-playing capabilities of the web browser (via plugins such as Quicktime or RealPlayer, or maybe via Flash using something like the Javascript Sound Kit).

Another thing I need to do some time is port the game to alternative open source object-oriented graphical scripting environments such as OpenLaszlo, Javascript+SVG and PyGame.

Vote for or comment on this article on Reddit or Hacker News ...