Object-oriented design in JavaScript games

Use OOP and design patterns to make your code smarter

Much of JavaScript is procedurally based loops and gigantic if/else statements. In this article, learn about a smarter approach by using object-oriented design in JavaScript games. Get an overview of prototypal inheritance and basic object-oriented programming (OOP) with JavaScript. Learn how to gain more benefits from OOP in JavaScript by using a classical inheritance-based library. This article also reviews architectural design patterns that demonstrate how to write cleaner code with examples of a game loop, state machine, and event bubbling.

Dan Zdrazil, Software Developer, The Nerdery

Photo of Dan ZdrazilDan Zdrazil began working in PHP in 2008, then added Flex/AS3, Zend, JavaScript, and HTML to his portfolio. He works full-time as a software engineer at The Nerdery and frequently shares his expertise by participating in the company's JavaScript committee for those passionate about the technology. Zdrazil is a graduate of the University of Minnesota with a Bachelor of Arts in Asian language and literature.



16 October 2012

Also available in Chinese Russian Japanese

Introduction

In this article, learn about OOP in JavaScript, and explore the prototypical and classical inheritance models. Examples explain common patterns in games that can greatly benefit from the structure and maintainability of OOP design. The ultimate goal is that each piece of code is human-readable and represents an idea and a single purpose, altogether transcending a set of instructions and algorithms to become a finely-tuned work of art.


Overview of OOP in JavaScript

Frequently used abbreviations

  • DOM: Document Object Model
  • DRY: Don't Repeat Yourself
  • OOP: Object-oriented programming

The goals of OOP are to provide data abstraction, modularity, encapsulation, polymorphism, and inheritance. With OOP, you can abstract the concept of the code from the authoring of code, thereby providing elegance, reusability, and readability at the cost of file count, line count, and (if poorly managed) performance.

Traditionally, game developers shied away from pure OOP approaches so they could squeeze every bit of performance possible from the CPU cycle. Many JavaScript game tutorials use non-OOP approaches for a quick demo rather than providing a solid foundation. JavaScript developers have different problems than developers of other gaming environments: memory is not manually managed, and JavaScript files are executed in a global context that encourages maintainability nightmares with spaghetti code, namespace collisions, and mazes of if/else statements. To get the most out of developing a JavaScript game, follow OOP best practices to significantly enhance the future maintainability, pace of development, and expressiveness of your game.

Prototypical inheritance

Unlike languages that use classical inheritance, in JavaScript there is no built-in class construct. Functions are first-class citizens of the JavaScript world and, like all user-defined objects, they have prototypes. Calling a function with the new keyword actually creates a copy of the function's prototype object and uses that object as the context for the keyword this inside of the function. Listing 1 shows an example.

Listing 1. Constructing an object with prototypes
// constructor function
function MyExample() {
  // property of an instance when used with the 'new' keyword
  this.isTrue = true;
};

MyExample.prototype.getTrue = function() {
  return this.isTrue;
}

MyExample();
// here, MyExample was called in the global context, 
// so the window object now has an isTrue property—this is NOT a good practice

MyExample.getTrue;
// this is undefined—the getTrue method is a part of the MyExample prototype, 
// not the function itself

var example = new MyExample();
// example is now an object whose prototype is MyExample.prototype

example.getTrue; // evaluates to a function
example.getTrue(); // evaluates to true because isTrue is a property of the 
                   // example instance

Conventionally, functions that represent a class should start with a capital letter to signify that it is meant to be a constructor. The name should represent the data structure it creates.

The magic of creating instances of classes is a combination of the new keyword and prototype objects. Prototype objects can have both methods and properties, as shown in Listing 2.

Listing 2. Simple inheritance through prototyping
// Base class
function Character() {};

Character.prototype.health = 100;

Character.prototype.getHealth = function() {
  return this.health;
}

// Inherited classes

function Player() {
  this.health = 200;
}

Player.prototype = new Character;

function Monster() {}

Monster.prototype = new Character;

var player1 = new Player();

var monster1 = new Monster();

player1.getHealth(); // 200- assigned in constructor

monster1.getHealth(); // 100- inherited from the prototype object

Assigning a parent class to a child class requires that you call new and assign the result to the child class' prototype property, as shown in Listing 3. Therefore, it's advisable to keep constructors as lean and free of side effects as possible, unless you're willing to pass default values in your class definitions.

If you've started trying to define classes and inheritance in JavaScript, you might have realized a major difference from classical OOP languages: there are no super or parent properties to access a parent object's methods if you've overridden them. There's a simple solution to this, but it violates the Don't Repeat Yourself (DRY) principles and is probably the biggest reason there are so many libraries that try to simulate classical inheritance.

Listing 3. Calling parent methods from child classes
function ParentClass() {
  this.color = 'red';
  this.shape = 'square';
}

function ChildClass() {
  ParentClass.call(this);  // use 'call' or 'apply' and pass in the child 
                           // class's context
  this.shape = 'circle';
}

ChildClass.prototype = new ParentClass(); // ChildClass inherits from ParentClass

ChildClass.prototype.getColor = function() {
  return this.color; // returns "red" from the inherited property
};

In Listing 3, the color and shape attribute values aren't in the prototype—they are assigned in the ParentClass constructor function. New instances of ChildClass will have their shape property assigned twice—once as "squre" in the ParentClass constructor, and once as "circle" in the ChildClass constructor. Moving logic such as these assignments to the prototype will reduce such side effects and make the code easier to maintain.

In prototypical-inheritance models, you can use JavaScript's call or apply methods to run a function with a different context. Although this works well to replace other languages' use of super or parent, it creates a different problem. If you need to refactor a class by changing its name, its parent, or its parent's name, you now have the token ParentClass in your text file in many more places. This problem grows as your classes become more complex. A better solution is to have your classes extend a base class, which allows your code to repeat itself less, typically in the form of recreating classical inheritance.

Classical inheritance

Though prototypical inheritance is perfectly capable for OOP, it does not meet a number of the goals of good programming. Consider the following issues:

  • It isn't DRY. Class names and prototypes are repeated everywhere, making reading and refactoring more difficult.
  • Constructors are called during prototyping. You're unable to use certain logic in constructors once you start subclassing.
  • There's no real support for strong encapsulation.
  • There's no real support for static class members.

Many JavaScript libraries attempt to impose more classical OOP syntax to overcome the issues above. One of the easier-to-use libraries is Dean Edward's Base.js (see Resources), which provides the following useful features:

  • All prototyping is done with object mix-ins (classes and subclasses can be defined in one statement).
  • A special constructor function is used to provide a safe place for logic to be run when creating new instances of the class.
  • It provides static class member support.
  • Its contribution towards strong encapsulation stops at keeping your class definition down to one statement (mental encapsulation, not code encapsulation).

While other libraries may offer stricter support for public and private methods and properties (encapsulation), Base.js provides a concise syntax that's easy to use and remember.

Listing 4 gives a brief introduction to Base.js and classical inheritance. The example extends the features of an abstract Enemy class with a more specific RobotEnemy class.

Listing 4. Brief introduction to Base.js and classical inheritance
// create an abstract, basic class for all enemies
// the object used in the .extend() method is the prototype
var Enemy = Base.extend({
    health: 0,
    damage: 0,
    isEnemy: true,

    constructor: function() {
        // this is called every time you use "new"
    },

    attack: function(player) {
        player.hit(this.damage); // "this" is your enemy!
    }
});

// create a robot class that uses Enemy as its parent
// 
var RobotEnemy = Enemy.extend({
    health: 100,
    damage: 10,

    // because a constructor isn't listed here, 
    // Base.js automatically uses the Enemy constructor for us

    attack: function(player) {
        // you can call methods from the parent class using this.base
        // by not having to refer to the parent class
        // or use call / apply, refactoring is easier
        // in this example, the player will be hit
        this.base(player); 
       
        // even though you used the parent class's "attack" 
        // method, you can still have logic specific to your robot class
        this.health += 10;
    }
});

OOP patterns in game design

The basic game engine invariably relies on two functions: update and render. The render method will typically rely on either setInterval or a polyfill for requestAnimationFrame, such as the one by Paul Irish (see Resources). The benefit of using requestAnimationFrame is that it won't be called any more than necessary. It will only run at the client monitor's refresh rate (typically 60 times a second for desktops), and, in most browsers, it won't run at all unless the tab your game is in is active. Benefits include:

  • Reducing the amount of work the client computer does when the user isn't looking at the game.
  • Saving battery life on mobile devices.
  • Effectively pausing the game if your update loop is tied to the render loop.

For these reasons, requestAnimationFrame has been called "client-friendly" and a "better citizen" in comparison to setInterval.

Tying the update loop to the render loop poses a different problem: keeping the pace of the game actions and animations the same regardless of whether the render loop is running at 15 or 60 frames per second. The trick is to establish a unit of time, called a tick, within your game and pass the amount of time elapsed since the last call to update. This amount of time can then be translated into a number of ticks, and your models, physics engines, and other time-dependent game logic can adjust accordingly. For example, a player who is poisoned may take 10 damage per tick for 10 ticks. If the render loop is running fast, they may take no damage on a particular call to update. But, if garbage collection kicked in on the last render loop causing one-and-a-half ticks to pass, your logic may cause 15 damage instead.

Another school of thought is to completely decouple the timing of your model updates from the view render loop. In games that have many animations or objects, or are otherwise highly resource-intensive to draw, coupling the update loop to the render loop will cause the game to slow down completely. In this case, your update method can run on a set interval (using setInterval) regardless of when and how often the requestAnimationFrame handler fires. Most of the time spent in these loops is actually spent in the render step, so the game will continue to run at a set pace even if only 25 frames are drawn to the screen. In both cases, you'll still want to calculate a difference in time between update cycles; if you update 60 times a second, your update function has at most 16ms to finish. If it runs longer (or if the browser's garbage collection runs) the game will still slow down. Listing 5 shows an example.

Listing 5. Basic application class with render and update loops
// requestAnim shim layer by Paul Irish
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function(/* function */ callback, /* DOMElement */ element){
                window.setTimeout(callback, 1000 / 60);
              };
    })();

var Engine = Base.extend({
    stateMachine: null,  // state machine that handles state transitions
    viewStack: null,     // array collection of view layers, 
                         // perhaps including sub-view classes
    entities: null,      // array collection of active entities within the system
                         // characters, 
    constructor: function() {
        this.viewStack = []; // don't forget that arrays shouldn't be prototype 
		                     // properties as they're copied by reference
        this.entities = [];

        // set up your state machine here, along with the current state
        // this will be expanded upon in the next section

        // start rendering your views
        this.render();
       // start updating any entities that may exist
       setInterval(this.update.bind(this), Engine.UPDATE_INTERVAL);
    },

    render: function() {
        requestAnimFrame(this.render.bind(this));
        for (var i = 0, len = this.viewStack.length; i < len; i++) {
            // delegate rendering logic to each view layer
            (this.viewStack[i]).render();
        }
    },

    update: function() {
        for (var i = 0, len = this.entities.length; i < len; i++) {
            // delegate update logic to each entity
            (this.entities[i]).update();
        }
    }
}, 

// Syntax for Class "Static" properties in Base.js. Pass in as an optional
// second argument to.extend()
{
    UPDATE_INTERVAL: 1000 / 16
});

In case you're not familiar with the context of this in JavaScript, note that .bind(this) is used twice—once on the anonymous function inside the setInterval call, and once on this.render.bind() inside the requestAnimFrame call. setInterval and requestAnimFrame are functions, not methods; they belong to the global window object and not to any particular class or identity. As a result, in order for this inside of the engine's render and update methods to reference our instance of the Engine class, calling .bind(object) will force this inside of the function to act differently than it would normally. If you're supporting Internet Explorer 8 or earlier, you'll need to add a polyfill for bind.


State machine

The state machine pattern is widely implemented but poorly recognized. It is an extension of the principles behind OOP (abstract the concept of the code from the execution). For example, a game may have the following states:

  • Preloading
  • Start screen
  • Active game
  • Options menu
  • Game over (win, lose, or continue)

None of these states should have executable code that is concerned about the other states. Your preloading code shouldn't know anything about when to open the Options menu. Imperative (procedural) programming might suggest monolithic if or switch conditional statements to properly order application logic, but they fail to represent the concept of the code, making them more difficult to maintain. Adding additional states, such as in-game menus, transitions between levels, or more features, makes your conditional statements harder to maintain.

Instead, consider the example in Listing 6.

Listing 6. Simplified state machine
// State Machine
var StateMachine = Base.extend({
    states: null, // this will be an array, but avoid arrays on prototypes.
                  // as they're shared across all instances!
    currentState: null, // may or may not be set in constructor
    constructor: function(options) {
        options = options || {}; // optionally include states or contextual awareness

        this.currentState = null;
        this.states = {};

        if (options.states) {
            this.states = options.states;
        }

        if (options.currentState) {
            this.transition(options.currentState);
        }
    },

    addState: function(name, stateInstance) {
        this.states[name] = stateInstance;
    },

    // This is the most important function—it allows programmatically driven
    // changes in state, such as calling myStateMachine.transition("gameOver")
    transition: function(nextState) {
        if (this.currentState) {
            // leave the current state—transition out, unload assets, views, so on
            this.currentState.onLeave();
        }
        // change the reference to the desired state
        this.currentState = this.states[nextState];
        // enter the new state, swap in views, 
        // setup event handlers, animated transitions
        this.currentState.onEnter();
    }
});

// Abstract single state
var State = Base.extend({
    name: '',       // unique identifier used for transitions
    context: null,  // state identity context- determining state transition logic

    constructor: function(context) {
        this.context = context;
    },

    onEnter: function() {
        // abstract

        // use for transition effects
    },

    onLeave: function() {
        // abstract

        // use for transition effects and/or
        // memory management- call a destructor method to clean up object
        // references that the garbage collector might not think are ready, 
        // such as cyclical references between objects and arrays that 
        // contain the objects
    }
});

You might not need to create a specific subclass of the state machine for your application, but you'll definitely want to create subclasses of State for each of your application states. By separating your transition logic into different objects, you could:

  • Use the constructors as an opportunity to start immediately preloading assets.
  • Add new states to your game, such as a continue screen that appears before a game over screen, without trying to figure out which global variables are affected in which conditionals in some monolithic if/else or switch structure.
  • Dynamically define transition logic if you create states based on data loaded from a server.

Your main application class shouldn't be concerned about the logic inside of your states, and your states shouldn't be too concerned about anything inside of the main application class. A preloading state, for example, might be responsible for instantiating a view based on assets built into the markup of the page and queueing the minimum game assets (movie clips, images, and sounds) in a singleton asset manager. Although the state instantiated the preloading view class, it doesn't need to be concerned with the behavior of the view. In this case, the idea (the object that the state represents) is limited in responsibility to defining what it means to the application to be in a state of preloading data.

Keep in mind that the state machine pattern isn't limited to the states of your game logic. Individual views will also benefit by removing the state logic from their representation logic, especially when managing subviews or when combined with the Chain of Responsibility pattern to handle user interaction events.


Chain of Responsibility: Emulating event bubbling on a canvas

Think of the HTML5 canvas element as an image element that lets you manipulate individual pixels. If you have an area where you've drawn grass, some loot, and a character standing on top of it all, the canvas doesn't have a clue what the user has clicked on. If you draw a menu, the canvas doesn't know that a particular area represents a button, and the only DOM element to attach an event to is the canvas itself. To make a game playable, the game engine needs to be able to translate what can happen when a user clicks on the canvas.

The Chain of Responsibility design pattern is intended to decouple the sender (the DOM element) of an event from the receiver (your code) so that more than one object has a chance to claim responsibility for handling the event (your views and models). Classical implementations, such as web pages, may have your views or models implement a handler interface then delegate all mouse events to a scene graph, which would find the relevant "things" that were clicked on and let each one have a chance at intercepting. A simpler approach is to simply have the canvas itself host a chain of handlers defined at runtime, as shown in Listing 7.

Listing 7. Handing event bubbling using the Chain of Responsibility pattern
var ChainOfResponsibility = Base.extend({
        context: null,      // relevant context- view, application state, so on
        handlers: null,     // array of responsibility handlers
        canPropagate: true, // whether or not 

        constructor: function(context, arrHandlers) {
            this.context = context;
            if (arrHandlers) {
                this.handlers = arrHandlers;
            } else {
                this.handlers = [];
            }
        },

        execute: function(data) {
            for (var i = 0, len = this.handlers.length; i < len; i++) {
                if (this.canPropagate) {
                    // give a handler a chance to claim responsibility
                    (this.handlers[i]).execute(this, data);
                } else {
                    // an event has claimed responsibility, no need to continue
                    break;
                } 
            }
            // reset state after event has been handled
            this.canPropagate = true;
        },

        // this is the method a handler can call to claim responsibility
        // and prevent other handlers from acting on the event
        stopPropagation: function() {
            this.canPropagate = false;
        },

        addHandler: function(handler) {
            this.handlers.push(handler);
        }
});

var ResponsibilityHandler = Base.extend({
    execute: function(chain, data) {

        // use chain to call chain.stopPropegation() if this handler claims
        // responsibility, or to get access to the chain's context member property
        // if this event handler doesn't need to claim responsibility, simply
        // return; and the next handler will execute
    }
});

The ChainOfResponsibility class will work just fine without subclassing because all of your application-specific logic will be contained inside of the subclasses of ResponsibilityHandler. The only thing that would change between implementations would be passing in an appropriate context, such as the view that it's representing. Consider an options menu that, while open, still shows the paused game around it, as shown in Listing 8. If the user clicks on one of the buttons in the menu, the characters in the background shouldn't react to the click.

Listing 8. Options menu close handler
var OptionsMenuCloseHandler = ResponsibilityHandler.extend({
    execute: function(chain, eventData) {
        if (chain.context.isPointInBackground(eventData)) {
            // the user clicked the transparent background of our menu
            chain.context.close(); // delegate changing state to the view
            chain.stopPropegation(); // the view has closed, the event has been handled
        }
    }
});

// OptionMenuState
// Our main view class has its own states, each of which handles
// which chains of responsibility are active at any time as well
// as visual transitions

// Class definition...
constructor: function() {
    // ...
    this.chain = new ChainOfResponsibility(
        this.optionsMenuView, // the chain's context for handling responsibility
        [
            new OptionsMenuCloseHandler(), // concrete implementation of 
			                               // a ResponsibilityHandler
            // ...other responsibility handlers...
        ]
    );
}

// ...
onEnter: function() {
    // change the view's chain of responsibility
    // guarantees only the relevant code can execute
    // other states will have different chains to handle clicks on the same view
    this.context.setClickHandlerChain(this.chain);
}
// ...

In Listing 8, a view class has a reference to a set of states, and each state determines which objects will be responsible for how a click event is handled. In this way, the view's logic is limited to only what the view's identity represents: displaying the options menu. If the game is updated to include more buttons, fancier effects, or transitions to new views, there is a discrete object capable of handling each new feature without any need to change, break, or rewrite the existing logic. Through a clever combination of chains for mousedown, mousemove, mouseup, and click events, managing everything from menus to characters, to drag and drop inventory screens can be handled in a highly structured and organized manner without increasing the complexity of your code.


Conclusion

Design patterns and OOP are inherently neutral concepts; blind usage of them can cause problems, instead of solving problems. This article provided an overview of OOP in JavaScript and explored the prototypical and classical inheritance models. You learned about common patterns in games that can greatly benefit from the structure and maintainability of OOP design (a basic game loop, state machine, and event bubbling). This article has only skimmed the surface of common solutions to common problems. With a bit of practice, you should become adept at writing expressive code—and consequently spend less time writing and more time creating.


Download

DescriptionNameSize
Article source codeObject-OrientedDesignSource.zip5KB

Resources

Learn

Get products and technologies

  • OpenGL: Get the latest drivers.
  • jQuery: Download the popular JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development.
  • Modernizr: Get this open-source JavaScript library that helps you build the next generation of HTML5 and CSS3-powered websites.
  • Kibo: Download the popular library specifically written for speedy cross-browser keyboard event handling.
  • IBM product evaluation versions: Download or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2, Lotus, Rational, Tivoli, and WebSphere.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=840125
ArticleTitle=Object-oriented design in JavaScript games
publish-date=10162012