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
The entire collision detection assumes we don’t need to look at the
z
axis , and that everything is a squareWe only supply directional
constants
that represent 2D directions, (LEFT
,RIGHT
,FALLING
,JUMPING
)gma.renderHelper.setLocation
assumes entities don’t have az
property.gma.moveable
assumes it only needs to keep track of vertical and horizontal state (gma.moveable.xState
andgma.moveable.yState
)gma.moveable
also has functions that change the position of an entity, and they all assume we only need to update it’sgma.moveable.x
andgma.moveable.y
propertiesgma.moveable.updatePositions
,gma.moveable.animate
,gma.moveable.getMovement
This also means everything that inherits from
gma.moveable
have the same limitations.
gma.shapes.rectangle
has a couple of functions that don’t care about thez
axis.gma.shapes.rectangle
is the only object that provides any “shape” functionality.