Introduction to ES6 Classes for Game Programmers

If you’ve used JavaScript for game programming, you probably already know some of its shortcomings. One of those is its object system. Where most languages that have objects are class-based (think C++, Java, etc.), JavaScript is unusual in that it’s prototype-based. If you have experience with the language, you know this already, of course. And you know that the syntax can leave a lot to be desired, especially if you come from a background in, say, any other language. (Well, any class-based language, at least. If you’re used to something like Lisp or Haskell, then nothing will surprise you.)

With the newest standard, ES6, that’s going to change. It’s supposed to come out by the end of this month, and maybe that’s true. I’m writing this on June 15th, so it might even be released before this post goes up. If that’s the case, great! (I still remember when C++11 was codenamed C++0x, so I remain skeptical.) Anyway, some of the new features of ES6 are incredibly useful for all developers, but we’ll start with classes, because they’re simple yet powerful, and game development has always been one of the main uses of object-oriented programming. (EDIT 6/18: ES6 is now out! I’m amazed, and I’m happy to admit that I was wrong. Even though the release happened between writing this and posting it, I won’t remove what I already said.)

(By the way, most browsers don’t support much of ES6 yet, and even Node.js support is spotty. You’ll likely need to use a transformer like Babel to turn your ES6 code into something usable under the current standard, ES5.)

Basic Syntax

Let’s take a look at a little class that we can use to represent an enemy in a game. In ES6, it might look something like this:

class Enemy {
    constructor(id, name) {
        // Some properties that are passed into the constructor
        this.id = id;
        this.name = name;

        // A property we can define ourselves
        this.health = 100;
    }

    doAI() {
        /* Do some AI work here */
    }

    kill() {
        /* Play a death animation or something */
    }

    hit(damage) {
        this.health -= damage;
        if (this.health <= 0) {
            this.kill();
        }
    }
}

If you’ve ever worked with Java, C++, C#, ActionScript, or any other language like that, then the syntax will be familiar. Inside the class definition, you can define methods, both instance methods (like all of those here) and static methods. Instance methods are basically the same as the prototype methods already in JavaScript. You call them on an instance of an object, and they can use that object as this. Static methods don’t require an instance of the class; they’re called on the class itself, like the functions of the Math object.

Using this class is as easy as any object constructor. var myEnemy = new Enemy(0, 'myEnemy'); is exactly the same as what you’d use if we defined Enemy in the traditional JS way, defining methods on the prototype and so on. That’s the beauty (if you want to call it that) of ES6 classes: deep down, they’re the same thing as before, but prettier, like CoffeeScript and Typescript claim to be.

constructor is a special method. (I wonder what it does…), but you can define any other method you like. You can also use get and set before method names, just like with the object literal syntax. Static methods are prefixed with static. And that’s pretty much it.

Inheritance

If ES6 classes could just do that, they’d be a pretty good bit of syntactic sugar, but they’d definitely be missing something. Subclassing (AKA inheritance) is that something. Like their counterparts in other languages, ES6 classes can derive from another class, adding or redefining methods and properties. Taking our Enemy class from above, we can make a couple of different enemies using subclasses:

class ToughEnemy extends Enemy {
    constructor(id, name) {
        super(id, name);

        this.health = 200;
    }
}

class BossEnemy extends ToughEnemy {
    constructor(id, name) {
        super(id, name);

        this.lives = 3;
    }

    kill() {
        if (this.lives > 0) {
            this.lives--;
            this.health = 200;

            /* say something cheesy
            e.g., "Ha! You thought I would go down that easy?"
            and play a regeneration animation */
        } else {
            // All lives are gone, so he's really dead
            super.kill();
    }
}

Now we have two new classes. ToughEnemy is a generic bad guy with double health. There’s not much to change for him, but it shows the super keyword that we can use to call methods of the superclass. In the constructor, you can use it by itself to call the superclass constructor. Actually, you have to. Otherwise, you can’t use this anywhere in the derived class’ constructor. Since we want to change the this.health property, we do call super, forwarding the constructor parameters to it, which also means we don’t have to deal with them.

For BossEnemy, we further subclass ToughEnemy, but with a little added logic. First, we give him a property this.lives, set to 3. Then, we change his kill() method as you can see above. If he goes down to 0 health, he loses a life, but he goes back to full health. That continues until he’s out of lives, when we call the superclass kill() method. Since we didn’t define one for ToughEnemy, the call goes up another level, to Enemy, which does have kill().

So, to make a subclass, define a class as usual, but add extends and the name of the base class after your derived class’ name. Everything else is the same, except for the caveat about super in the constructor. You can change methods to your heart’s content, and any that you leave out will stay the same as they were in the base class. Just like any other OO language. Like it should be.

The only downside (and a lot of people wouldn’t see it as such) is that you only get single inheritance, meaning you can only derive from one base class. That’s okay for JS, since we didn’t have anything else to begin with. And Java doesn’t have multiple inheritance, nor does C#. C++ and Python do, but it takes a lot of extra work on the programmer’s part to use it well. Basically, once you truly need multiple inheritance, you’ll know how to fake it, no matter what language you’re using.

For Games

Since ES6 classes are just a fancier way of writing JS prototypes, you can use them wherever you’d use those: pretty much anywhere. And, due to the way they’re defined, you can subclass “traditional” JS objects by extending a constructor. This includes builtins like Array (but not Math, as it’s not a constructor). If you’re using a game library like Phaser, you can extend its objects with classes, too. Basically, wherever you’d already use objects and prototypes, classes fit right in.

Compatibility

ES6 is new, and not everything supports it. According to this compatibility table, nothing actually has full support for classes yet. Firefox 39 will have just about everything, and Chrome 42 allows classes, but not completely. Internet Explorer, as usual, is hopeless, but Edge has quite a bit of support if you enable “experimental” JavaScript. Safari and iOS are out of the question right now. Realistically, if you want to use ES6 classes at the moment, you’ll need a transformer. But that will change, and the next generation of programmers might wonder why we ever bothered with prototype at all.

One thought on “Introduction to ES6 Classes for Game Programmers”

Leave a Reply

Your email address will not be published. Required fields are marked *