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. .. _requireJSNS: 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`. .. code-block:: javascript 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, :api:`gma.character` is added to the gma namespace with the following code: .. code-block:: javascript 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, .. code-block:: javascript 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, .. code-block:: javascript 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 :term:`closure`, which allows us to imitate private variables. .. code-block:: javascript 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 :api:`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 :ref:`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 square * :metho:`gma.collisions.detectCollisions`, :metho:`gma.collisions.factories.findCollisions`, :metho:`gma.collisions.factories.findGround`, :metho:`gma.collisions.factories.findBlockers` * We only supply directional :api:`constants ` that represent 2D directions, (:constant:`LEFT`, :constant:`RIGHT`, :constant:`FALLING`, :constant:`JUMPING`) * :metho:`gma.renderHelper.setLocation` assumes entities don't have a ``z`` property. * :api:`gma.moveable` assumes it only needs to keep track of vertical and horizontal state (:prop:`gma.moveable.xState` and :prop:`gma.moveable.yState`) * :api:`gma.moveable` also has functions that change the position of an entity, and they all assume we only need to update it's :prop:`gma.moveable.x` and :prop:`gma.moveable.y` properties * :metho:`gma.moveable.updatePositions`, :metho:`gma.moveable.animate`, :metho:`gma.moveable.getMovement` * This also means everything that inherits from :api:`gma.moveable` have the same limitations. * :api:`gma.shapes.rectangle` has a couple of functions that don't care about the ``z`` axis. * :metho:`gma.shapes.rectangle.setBottomLeft` and :metho:`gma.shapes.rectangle.setPointsAndEdges` * :api:`gma.shapes.rectangle` is the only object that provides any "shape" functionality. 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 :ref:`this page `.