May 2015

Volume 30 Number 5


Game Development - 2D Game Engines for the Web

By Michael Oneppo

Imagine there’s a developer who wants to write a game. This developer looks around to see what tools are available, and is disappointed by the options. So, this developer then decides to create a custom engine that will fulfill all the needs for the current game concept. It will also fulfill all future game development requirements anyone will ever possibly have. And, so, the developer never actually writes a game.

It’s a tragedy that happens frequently. Fortunately, this story has spawned dozens of game development engines for all possible platforms. These encompass a wide range of methodologies, tools and licensing models. For example, JavaScripting.com currently lists 14 dedicated open source game libraries. That doesn’t even include physics engines, audio libraries and specialized input systems for games. With that kind of selection, there’s bound to be a game engine that will work well for any game project you have in mind.

This article will walk through three popular open source 2D game engines for the Web: Crafty, Pixi and Phaser. To compare and contrast these libraries, I’ve ported the Ping game from my first article (msdn.microsoft.com/magazine/dn913185) to each one to see how they feel. Keep in mind by restricting things to 2D, I’m leaving out a number of 3D game engines for the Web. I’ll cover those in the future. For now, I’ll focus on 2D and the opportunities there.

Crafty

Crafty (craftyjs.com) is meant primarily for tile-based games, although it works well for a wide variety of 2D games, including my Ping game. The foundation of Crafty is an object model/dependency system it calls components.

Components are like classes you define with specific attributes and actions. Each instance of a component is called an entity. Components are meant to be heavily mixed to make complex, feature-rich entities. For example, if you want to make a sprite sheet in Crafty, use the sprite function to generate components that represent your sprites. Here’s how I define the sprite types from an image:

Crafty.sprite("sprites.png", {
  PlayerSprite: [0, 128, 128, 128],
  OpponentSprite: [0, 0, 128, 128],
  BallSprite: [128, 128, 64, 64],
  UpSprite: [128,0,64,64],
  DownSprite: [128,64,64,64],
  LeftSprite: [192,0,64,64],
  RightSprite: [192,64,64,64]});

Now, if you want to make a ball entity that uses the image of the ball on the sprite sheet, include the BallSprite component when you create the entity, using the “e” function, as follows:

Crafty.e("Ball, BallSprite");

This won’t draw it to the screen yet. You have to tell Crafty you want this entity to live in 2D (so it will have an “x” and “y” position), and you want it to draw using DOM elements:

Crafty.e("Ball, 2D, DOM, BallSprite");

If you want to draw your elements in a canvas, it’s as simple as replacing the Document Object Model (DOM) component with Canvas.

Entities act like independent objects on your screen by reacting to events. Like many JavaScript libraries, Crafty entities have a bind function for reacting to events. It’s important to remember these are Crafty-specific events. For example, if you want an entity to do something in every frame, have it react to the EnterFrame event. This is exactly what’s needed to move the ball around based on who’s holding it and the appropriate physics, if necessary. Figure 1 shows ball entity initialization with an EnterFrame event function that covers the motion of the ball.

Figure 1 The Ball Entity Definition

Crafty.e("Ball, 2D, DOM, BallSprite, Collision")
  .attr({ x: width/2, y: height/2, velocity: [0,0], owner: 'User' })
  .bind('EnterFrame', function () {
    // If there's an owner, have the ball follow the owner.
    // Nudge the ball to the left or right of the player
    // so it doesn't overlap.
    if (this.owner) {
      var owner = Crafty(this.owner);
      switch(this.owner) {
        case 'User':
          this.x = owner.x + 128;
          break;
        case 'Opponent':
          this.x = owner.x - 64;
            }
            this.y = owner.y + 32;
            return;
    }
    // Bounce the ball off the ceiling or floor.
    if (this.y <= 0 || this.y >= (height - 64))
      this.velocity[1] = -this.velocity[1];
    // Move the ball based on its velocity, which is defined in
    // pixels per millisecond.
    var millis = 1000 / Crafty.timer.FPS();
    this.x += this.velocity[0] * millis;
    this.y += this.velocity[1] * millis;
  })

If you look closely, you’ll see “this” used frequently to refer to entity properties. The built-in Crafty “this.x” and “this.y” define the ball position. When you create the ball with the 2D component, this functionality is added. The statement, “this.velocity,” is custom to my code. You can see it defined as a property using the attr function. The same is true with this.owner, which I use to figure out if either player is holding the ball.

Crafty has several built-in components, but I don’t use them a huge amount in the Ping example. Here are just a few:

  • Gravity: Crafty has a simple gravity engine that automatically pulls an entity downward. You can define any number of element types to stop its motion.
  • TwoWay/FourWay/MultiWay Motion: With these components, you get a variety of arcade-style motion input methods. TwoWay is for platformers, with left, right and jump options bindable to any keys you want. FourWay allows top-down motion in the cardinal directions. MultiWay allows custom-defined inputs, each with an arbitrary direction.
  • Particles: Crafty includes a fast particle system, which you can only use with Canvas drawing. As soft, round, single-color images, the particles are particularly suited for smoke, fire and explosions.

The code for Ping, implemented in Crafty, is available in the code download accompanying this article. Take a look to get a full picture of how I ported things, particularly the AI for the opponent.

Pixi.js

Pixi.js (pixijs.com) is the bare-bones, close-to-the-metal 2D Web renderer. Pixi.js can forego the 2D canvas and dive straight into WebGL, which gives it a significant performance gain. A Pixi.js game has two key pieces: a stage that describes your game layout in a scene graph, and a renderer, which actually draws the elements in the stage to the screen using canvas or WebGL. By default, Pixi.js will use WebGL if it’s available:

$(document).ready(function() {
  // The stage to play your game.
  stage = new PIXI.Stage(0xFFFFFFFF);
  lastUpdate = 0;
  // autoDetectRenderer() finds the fastest renderer you've got.
  renderer = PIXI.autoDetectRenderer(innerWidth, innerHeight);
  document.body.appendChild(renderer.view);
});

Pixi.js has a scene graph and transformations, but it’s important to note you need to render the scene yourself. So, for my Ping game, I keep the rendering loop going using requestAnimationFrame, as follows:

function update(time) {
  var t = time - lastUpdate;
  lastUpdate = time;
  ball.update(t);
  ai.update();
  // New: You actually have to render the scene
  renderer.render(stage);
  requestAnimationFrame(update);
}

Unlike Crafty, Pixi.js doesn’t dictate much about how to structure your game, so I can pretty much use the same code from the original Ping game, with slight modifications.

The big difference you’ll see is that Pixi.js either lets you load sprites as images from files one by one, or as a tiled image using a special tool called TexturePacker. This tool will automatically merge a group of individual images into a single, optimized file with an accompanying JSON file that describes the image locations. I used TexturePacker to make the sprite sheet. Figure 2 shows the updated ready function, loading the sprite images.

Figure 2 Loading a Sprite Sheet in Pixi.js

$(document).ready(function() {
  // Create an new instance of a Pixi stage.
  stage = new PIXI.Stage(0xFFFFFFFF);
  lastUpdate = 0;
  // Create a renderer instance.
  renderer = PIXI.autoDetectRenderer(innerWidth, innerHeight);
  document.body.appendChild(renderer.view);
  var images = ["sprites.json"];
  var loader = new PIXI.AssetLoader(images);
  loader.onComplete = startGame;
  loader.load();
});

The loader is asynchronous, as it requires a function to call for when it’s done loading. The remaining game initializations, like creating the player, opponent and ball, are in the new startGame function. To use these newly initialized sprites, use the PIXI.Sprite.from-­Frame method, which takes an index into the array of sprites in the JSON file.

One last difference Pixi.js provides is a touch and mouse input system. To make any sprite input-ready, I set the interactive property to “true” and directly add some event handlers to the sprite. For instance, for the up/down buttons, I bound events to move the player up and down, as shown in Figure 3.

Figure 3 Bind Events to Move Game Player Up and Down

up = new PIXI.Sprite.fromFrame(3);
up.interactive = true;
stage.addChild(up);
up.touchstart = up.mousedown = function() {
  player.move(-distance);
}
down = new PIXI.Sprite.fromFrame(4);
down.interactive = true;
stage.addChild(down);
down.touchstart = down.mousedown = function() {
  player.move(distance);
}

It’s important to note I had to manually add each sprite to the scene to make it visible. You can check out my version of Ping built in Pixi.js in the code download accompanying this article.

Phaser

Phaser (phaser.io) is a full-featured game engine that uses Pixi under the hood to perform all rendering tasks. Phaser has a long list of features, including frame-based sprite animation, music and audio, a game state system and physics with three different contributing libraries.

Because I already ported Ping onto Pixi.js, porting to Phaser was straightforward. Phaser streamlines object creation based on Pixi.js:

ball = game.add.sprite(x, y, 'sprites');

While Phaser can be more concise, there are places where things are more complex. For instance, the last parameter in the previous code, “sprite’” refers to an image loaded at startup:

game.load.image('sprites', 'sprites.png');

Phaser has a different sprite sheet system for images from Pixi.js. You can break an image into tiles. To use it, call something like this:

game.load.spritesheet('sprites', 'mySprites.png',
  spriteWidth, spriteHeight, totalSprites);

For my example, I don’t actually use this function. The code I just showed you assumes all sprites are the same size, but the Ping sprite sheet has sprites of 64 pixels and 128 pixels. As such, I had to crop each sprite image manually. To crop down the image for the ball, I set a cropping rectangle on the sprite:

ball.cropRect = new Phaser.Rectangle(0, 64, 64, 64);
ball.updateCrop();

Hopefully this shows some of the flexibility of Phaser. That flexibility manifests itself in other ways, as well. You can make your game event-based, or check for events and respond to them sequentially in an update function. For instance, to handle a key input, you can respond to an event:

game.input.keyboard.onDownCallback = function(key) {
  console.log(key + " was pressed.");
}

or check if the key is pressed in your update loop:

function update() {
  if (game.input.keyboard.isDown('a'.charCodeAt(0)) {
    console.log("'A' key was pressed");
  }
}

This goes for a lot of events in Phaser, using sequential or asynchronous coding depending on your preference or need. On occasion, Phaser provides only the latter form of event handling. A good example is the Arcade physics system, where you must explicitly make a function call for every update to check for collisions between objects:

game.physics.arcade.collide(player, ball, function() {
  ballOwner = player;
});

This code determines if the ball has come in contact with the player sprite, and gives control of the ball to the player when that happens. Check out my implementation of Ping on Phaser with the code download for this article.

Wrapping Up

If you’re looking for a dedicated 2D JavaScript game engine, there are a lot of options to suit your needs. Here are two important factors to keep in mind when picking a framework:

Use Only the Features You Need: Don’t be dazzled by lots of features, all-encompassing monolithic frameworks, and other bells and whistles. Unless the bells and whistles address specific aspects of the game you want to make, they’ll probably get in the way. Also consider how the features are componentized. Will the whole system still work if you remove the physics system? Does the framework rely on the physics system to provide other functionality?

Understand How Each Engine Works: Try to understand how you can customize or extend the framework. If you’re making something slightly out of the ordinary, it’s likely you’ll need to write custom code that relies heavily on the framework you’ve chosen. At that point, you can either write a new, independent feature or extend the framework functionality. If you extend the framework itself instead of adding to it, you’ll end up with more maintainable code.

The frameworks covered here address many of these issues, so they’re definitely worth checking out. There are dozens of engines, libraries and frameworks out there, so do some research before embarking on your next game development adventure.


Michael Oneppo *is a creative technologist and former program manager at Microsoft on the Direct3D team. His recent endeavors include working as CTO at the technology nonprofit Library for All and exploring a master’s degree at the NYU Interactive Telecommunications Program.*​

Thanks to the following Microsoft technical expert for reviewing this article: Justin Garrett