Architecture

If you are planning to extend or modify the Gamma framework – or are, perhaps, simply curious – it is desirable to understand the various patterns that were used whilst constructing the framework.

Reading this section, you will learn:

  • how to use RequireJS;
  • how each module is put in the gma namespace;
  • how we’ve constructed the building blocks in Gamma to allow inheritance;
  • how we’ve allowed private variables through closures; and
  • the limitations we imposed on the game engine for simplicity.

Namespace and RequireJS

Eveything in Gamma exists under the gma namespace, which has been achieved using RequireJS.

Whether defining a new module with require.def or simply loading dependencies with require, you need to supply a list of dependencies that will be loaded before executing the supplied callback. RequireJS will then pass in all these dependencies into callback function when it loads. When defining a module, this callback function is expected to return the module and is called the Module’s function.

require.def('newModule',
    [
        'dependency1',
        'dependency2'
    ],
    function(dependency1, dependency2) {
        // Module's function

        return the_module;
    }
);

Gamma uses the convention that each module has gma/base as the first dependency, then the gma namespace will be the first argument to the module's function. Then, each module will append it's contents straight onto the gma namespace. Therefore only the gma base object will return a module.

For example, gma.character is added to the gma namespace with the following code:

require.def('gma/entities/character',
    [ 'gma/base',              // Provides the gma namespace which
      'gma/entities/moveable', //  is passed into the function
      'gma/utils/collisions'   //  below.
    ],
    function(gma) {
        // Add gma.character
        gma.character = function(spec) {
            // character class goes here
        };
    }
);

Factory Pattern

Gamma was designed so that classes behave like building blocks, allowing objects to be created by applying many such "building blocks" to a JavaScript object.

This was achieved using the factory pattern. In this pattern, an object is created by a factory function that accepts an object and simply add more properties/methods to it.

We have designed all our building blocks to "respect" properties/methods already existing on any input object, by only setting such properties and methods if the object doesn't already have such a property. The building blocks ensure that a default set of properties/methods exist on objects, so that they may be "complete".

For example,

var myClass = function(spec) {
    var self = spec || {};

    // add properties and methods to self
    self.y = self.y || 6

    self.someFunction = self.someFunction || function() {
        return 'hi there';
    };

    return self;
};

// Defaults to a new object if one isn't passed in
var myNewObject = myClass();

// Or we can apply the building block with some object
var myOtherNewObject = myClass({x:5});
myOtherNewObject.y; // <= 6

// Or just by calling the function on an existing object
var yetAnotherObject = {y:2};
myClass(yetAnotherObject);
yetAnotherObject.y; // <= 2

This allows the Gamma framework to imitate inheritance -- in the previous example yetAnotherObject inherits from myClass -- but it is possible to create an object that is built using a combination of unrelated blocks.

For example,

var myObject = {};
// There are no building blocks as shape, animateable or
//  armed, but for example's sake
shape(myObject);
animateable(myObject);
armed(myObject);

Note

For all these examples to work, the factory must return the object it creates/edits at the bottom

The factory pattern also chosen because it allows our classes to have a closure, which allows us to imitate private variables.

var myClass = function(spec) {
    var self = spec || {};

    // Anything else that is var'd is private
    var secret = "Lalala";

    // Private variable is only exposed through an accessor
    self.someFunction = self.someFunction || function() {
        return 'My secret is ' + secret;
    };

    return self;
};

Factories in collision detection

We have also used factories for our collision detection for caching purposes, as you can see on the page that explains our collision detection

Separating Concerns

The Gamma framework aims to disconnect the game logic from the visual logic. We have achieved this by making an interface that sits between Gamma and an arbitary rendering engine. This interface is collectively referred to as the Render Helpers. Theoretically we can define rendering helpers for many rendering engines, and perhaps even non-webgl engines. At this point we only have Render Helpers that use GLGE. You can find more about these here.

Limitations for Simplicity

Gamma was designed to be with two main limitations to keep the code simple

  • Entities can only move in 2D space
  • Everything is a square

Below is the list of places in the Gamma codebase that makes these assumptions

Functionality through tags

Gamma uses a very simple tags system to provide the ability to specify particular functionality on entities. This is explained in further detail on this page.

Fork me on GitHub