Overview


Expect this Unit to take about 2-5 hours, depending on your comfortability with Object-Orientated Programming and with JavaScript. Further reading might be required to help solidify the concepts. To demonstrate what you should have at the end of this Unit, check out the Demo for Unit 4. Don't forget to use the Chrome DevTools to look at the console and check out the source code for my tests!

This Unit might be one of the heaviest in terms of architecturing our game engine. The decisions we make for our game object system shape how we'll write game systems and gameplay code. We have lots of choices, but I'm here to tell you that there is generally no right way, just a best way given our circumstances. What I chose to do here works for our project and helps teach common concepts used in many other games.

JavaScript's prototypical inheritance model enables us to use classical inheritance through some work. Why am I choosing to use classical inheritance? It's a lot easier to get a few key features through classical inheritance: inheriting constructors, calling a parent's function from a derived function, and I want it to be super easy to create derived object types. Following a classical approach isn't very natural in JavaScript, but it's a valuable pattern to be comfortable with.

We are going to talk about 3 files here:


class.js


The guy who created JQuery, John Resig, created another JavaScript library called Prototype.js. You can read about this open-source code from his blog: http://ejohn.org/blog/simple-javascript-inheritance/. His blog post does a fairly good job at explaining the code, but to give you a basic rundown the idea is that we want to allow new classes to be extended from existing ones through a methond called extend. Inherited objects can share the same instance methods as the parent objects and call parent methods using this._super() from the child method. There's a good bit of overhead in making this work: when creating the child class each method is checked against the parent to see if it is inherited and if so creates a wrapper function.

This code also supports an init() function to be used a constructor, which is chained through all inherited types.

You can grab the code from John's blog, but there are a few adjustments I made for this project: I also wanted member objects to extend any member object of the same from the parent's. This means a parent class type could have a member object called .defaults with a number of properties, and if a child class has a property of the same name the values from the child's .defaults will be combined with the values from the parent class. In order to help with debugging, I also generate a unique function name for the constuctor, so that when looking at the object in the watch window we get more useful type information. It is an unfortunately side effect of using this system that we have to remember to assign the .typename to a new class type, but the payoff is worth it.

Compare my version with the original, and see if you can better understand the changes!

class.js

Source Code:


Class Test Code


Here is some test code directly from John's blog to demonstrate how class.js should work.

Source Code:


Supporting Events through baseObject.js


Let's create our game engine's first class!

The BaseObject class is going to provide some basic behavior that we want all objects in our game engine to have, and that is sending and receiving events.

Events are also known as messages, and they allow us to keep parts of the engine from becoming too tightly coupled. We wants objects to be able to communicate with each other without having to actually know anything specific about each other, or about each other at all! This is especially handy when we get to components

First we should think about our API. We want the following features:

Let me explain that more clearly: Events are triggered on a specific object. Any other object (including itself) can bind a callback function to an event to be notified when this event occurs for the specific object.

One more thing, we also want to have a constructor and default properties. This class is what other classes will be deriving from, so we might as well start here with those features. Check out the constructor function and see that we are adding a property called .listeners.

We are also doing something that I think is pretty neat with the .defaults property and taking in other properties through the constructor. Using the Underscore library, we can concatenate these objects and assign the properties back onto the object. Everything in .defaults will get assigned on to the object, then anything in the props argument in the init() function will get assigned over it - adding new values or overriding anything from defaults.

A destroy function is meant to be called when the object is no longer needed. It handles cleaning up anything that needs cleaning up. In this case we will want to call .debindEvents() which we will explain soon.

baseObject.js

API Source Code:

bindEvent()

Bind a target listener to a specific event on this object and trigger the callback on the target. If no target is provided then we assume that it means the target is 'this' object itself. We store listeners and the callback in an array called listeners, keyed by the event name. We also add the flexibility of the user passing in the function by name instead of by value.

Implementation Source Code:

triggerEvent()

When an event has occured, we'll call this function which will check to see if there are any listeners wanting to know about this specific event. There's a neat bit of JavaScript here in how we actually call the callback function - functions have a .call method that we can use to pass in the context, which is known as 'this' in the scope of the function.

Implementation Source Code:

unbindEvent() and unbindAllEvents()

To unbind a specific listener from a specific event, we also need to know the callback. Deleting a single element from an array means that we need to walk over the array backwards, and splice the disjointed elements back together.

Implementation Source Code:

The second one is super easy. If there are any listeners registered for a specific event, then remove them all. It's as easy as using delete.

Source Code:

debindEvents()

Say this object has a lot of other objects registered to listen to events on it, and then this object is destroyed. While unbind is used to remove listeners from this object, debind is used to remove all this object as a listener from any other object(s). Otherwise, the other object might still try to trigger a callback on this guy after it's destroyed.

Implementation Source Code:


Event Test Code


Let us create a few new object types that derive from BaseObject.

One of them will trigger an event, and the other object will bind itself to that same event so it can respond to it.

Check out the code and see if you can follow along!

Source Code:


Object Heirarchy through drawNode.js


Now, we create our first derived class type DrawNode.

This object will be responsible for our rendering and updating heirarchy. Why we are doing this will make more sense once we get to game objects and sprites, but it is important to setup now as this is a base feature of our engine.

First let me point out the situation with the default properties and the constructor again. We know this is a derived class type, so we make sure to call this._super(props) passing in the properties so they can be combined with the .defaults. If you remember the feature I added to John's class library, the .defaults defined here were already added/concatenated with the parent's class, so everything will work out as you would expect!

Next let's figure out how we'll approach the API for managing the heirarchy. We'll refer to this internally as node management. We'll want to be able to attach other objects as children, as well as remove objects, so we need functions like: attachChild, removeChild, removeAllChildren. When adding a child node, the list of nodes should remain sorted so that they are stored according to the value of their .zOrder property. Since this is a heirarchy, we'll also want to execute some funtion on this node and then execute the same function on the children nodes. This is called the Visitor Pattern, a common computer science pattern for separating an algorithm from the object structure on which it is operating.

drawNode.js

API Source Code:

attachChild()

When adding a child node, we'll store it on the .nodes array. We'll also want to resort the array using the nodeSortFunc() we have defined earlier.

Let's also take this oportunity to trigger an event on both this object and the passed in object to notify anyone interested that this attachment as occured.

Implementation Source Code:

removeChild()

Removing a node from an array is simple, we just need to remember to splice the the disjointed elements back together.

Implementation Source Code:

removeAllChildren()

To remove all children nodes, wWalk over the array destroying each one and then clear the array by setting its length to zero.

Implementation Source Code:

visit()

There are two parameters, the name of the function to call on each node, and the parameter to pass into it. To visit a node is to call this function on it passing in the parameters.

An example of the types of functions we'll be using to visit onto nodes would be a draw() call to render it, or a step() call to update any gameplay logic.

If you look at the heirarchy of nodes as if it were a tree (a common data-structure concept in computer science) then you might see that there's a interesting problem in how we might visit every child node of this node, and then all the childrens' children, etc. We are going to use recursion to solve this problem. Any time we visit a child node, it will recursively visit all of it's children nodes, etc.

  1. First, let's visit each of the nodes that have a .zOrder value that is less than this node.
  2. Then, we visit this node. This means calling the specified function if it exists.
  3. Finally, we visit all of the children nodes that have a great .zOrder value than this node.

Implementation Source Code:

Test Code

Here we will create a new object type that might behave as if it were a sprite. We will create our heirarchy by assigning each new instance a unique zOrder value and attaching them to each other as we might want them to be managed.

Implementation Source Code:


Homework


Part 1. Create and add the three files to your project: class.js, baseObject.js, drawNode.js

Part 2. Fill out class.js.

Part 3. Fill out baseObject.js

Part 4. Fill out drawNode.js