HTML5 Canvas & Backbone

An elementary HTML5 Canvas game engine built on Backbone. Specialized for 2D platformers, and optimized for mobile.

Examples

These examples are on Github.

CocoonJS Example

Backbone Game Engine was written to run inside of CocoonJS Canvas+, so you can turn your HTML5 game into a native application on iOS or Android. If you have the CocoonJS launcher loaded on your iOS or Android device, you can load Super Mario Bros level 1-1 via this URL:

http://martindrapeau.github.io/cocoon-mario/cocoon-mario.zip

Features:

  • Built on Backbone. Events, models, collections, inheritance and RESTful persistence. Why reinvent the wheel?
  • HTML5 canvas only. No jQuery, as little DOM manipulations as possible.
  • Mobile optimized. Build to run on mobile devices with transparent touch and viewport support. Everything is optimized for maxium frames per seconds (FPS).
  • Go Native with CocoonJS.. Built to run in Ludei's CocoonJS canvas+. Deploy native on iOS and Android.
  • 2D platformer. Built with side-scrollers in mind. Built-in classes for sprites, sprite sheets, characters, hero, quad-tree collision detection, world and editor.
  • No compilation. You don't need to install node, grunt or whatever else. Just code and press F5 to run.
  • No server required. Fork this repo and your Github site is up and going. Create your own game and point your friends to it. Rebase to pull in latest engine updates.
  • Built for mobile. Conceived to run on tablets. Share your URL with Mom so she can add it to the home screen of her iPad.
  • Take if offline. With HTML5 Application Cache, your game runs offline. Perfect for taking it on the road or on a fishing trip.
  • Save state. With HTML5 Local Storage, save where you are.
  • World editor. Conceived for tile-based games, comes with a world editor. Place your tiles and characters, then hit play to try it out. Hit save to save your world.

Dependencies

All included in the 3rd folder. That and nothing else.

Why Backbone?

Backbone implements events, models, collections, inheritance and persistence. Models implement getters and setters for object attributes. Models and Collections have an extend function to easily do inheritance. They implement methods for persistence (RESTful JSON by default). They can also trigger events and bind to them. Everything you need to build a great extensible game engine. Plus, Backbone is now widely used and provides these features in a standard fashion with a huge community to support them. I hope this project can make game programming accessible to developers already familiar with Backbone.

Using and Contributing

Backbone Game Engine was built to get you going fast. Fork this repository, and your own Github page will be ready in minutes. You can then create your own games by simply creating a new directory, and putting files in it.

The default branch is gh-pages so that any changes get published automatically to the Github page. Changes typically take only a few seconds to get published. This allows you to develop, test, document and deploy rapidly. It is a double-edge sword though. Any untested code you push to your fork on that branch will be felt by your users. It is therefore a good practice to create and work on another branch, and fast-forward merge to the gh-pages branch when done.

To report a bug, use Github issues. To contribute improvements, bug fixes or new examples, make changes to your fork and do a pull request. For anyone looking to help, here is a short to-do list:

  • Implement vertical panning in Backbone.Camera.
  • Revamp collision detection: optimize lookup and better functions.
  • Implement sound.
  • Add missing behaviors in Super Mario: character death, break brick, etc.

Getting Started

Backbone

The engine is based on Backbone so it is essential to understand its core structure: a Backbone.Model. A model has hash of attributes that are changed via getter and setter methods get and set. These attributes should only contain state information as it is those attributes which get saved and restored. A model is a Javascript object. Hence behaviour can be stored as properties and methods directly on the object. For example a sprite sheet points to an image. Attribute img contains the Image object or DOM selector (by id) for retrieving the Image object. Property img contains reference to the Image object. It is automatically set when the sprite sheet model is initialized.

var spriteSheet = new Backbone.SpriteSheet({
  id: "mario",
  img: "#mario",
  tileWidth: 32,
  tileHeight: 64,
  tileColumns: 21,
  tileRows: 6
});
spriteSheet.get("img"); // attribute
// #mario

spriteSheet.img; // property
// <img id=​"mario" src=​"../​super-mario-bros/​super-mario-2x.png" style=​"display:​none;​">

spriteSheet.img.width
//672

spriteSheet.img.height
//384

In fact, a model stores its attributes in the object property attributes. Methods get, set and toJSON operate on that property. Just remember that state date like sprite coordinates, velocity, etc go in there. However references to other objects do not.

Note on nomenclature: The word attribute is used to define state data (get and set stored in obejct property attributes) while the word property is used to define behavioural data stored directly on the object.

Living without jQuery

Backbone Game Engine does not use jQuery. Instead, it depends on backbone.native which re-creates the ubiquitous $ function/object with only essentials required by Backbone. As such, you are limited to simple DOM selectors enabled by querySelectorAll and events. Since the framework is for canvas-based games, avoid HTML manipulations. Canvas-based only allows for easier wrapping with CocoonJS. Consult the documentation of backbone.native to understand limitations.

Directory structure

To create a new example (or game), create an empty directory at the same level as super-mario-bros. Then, add some files. Here is the recommended file structure:

3rd/
src/
...
super-mario-bros/
my-game/
        index.html
        main.js

index.html is the HTML document which loads your assets, and defines your canvas. File main.js is where you instantiate the game engine and sprites. It must get loaded last.

index.html

The HTML document (usually named index.html in its own folder) declares the assets to be loaded and the canvas element. Javascript assets are declared in the HEAD element. Images are loaded via IMG elements. They must be hidden with inline CSS. Everything is bootstrap-loaded as declared in the HTML file. There is no asynchronous loading.

Here is an example HTML file taken from the example Mario.

<!doctype html>
<html style="touch-action: none;">
    <head>
        <title>Mario - Backbone Game Engine</title>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <link href="../favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <link href="../apple_touch_icon.png" rel="apple-touch-icon" />

        <meta name="viewport" content="width=960, user-scalable=no"/>
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>

        <script src="../3rd/underscore.js" type="text/javascript"></script>
        <script src="../3rd/backbone.native.js" type="text/javascript"></script>
        <script src="../3rd/backbone.js" type="text/javascript"></script>

        <script src="../src/shapes.js" type="text/javascript"></script>
        <script src="../src/core.js" type="text/javascript"></script>
        <script src="../src/input.js" type="text/javascript"></script>
        <script src="../src/hero.js" type="text/javascript"></script>
        <script src="../src/world.js" type="text/javascript"></script>

        <script src="main.js" type="text/javascript"></script>

        <style>
            body {
                margin: 0;
                background-color: #000;
            }
            canvas {
                position: fixed;
                top: 0;
                left: 0;
            }
        </style>

    </head>

    <body>
        <img id="mario" src="../super-mario-bros/super-mario-2x.png" style="display:none;" />
        <canvas id="foreground" width="960" height="700">
            Your browser does not support canvas element.
        </canvas>
    </body>

</html>

Some important notes:

  • HTML meta tags viewport, apple-mobile-web-app-capable, mobile-web-app-capable and apple-mobile-web-app-status-bar-style ensure the canvas is properly sized and styled on iPads and other mobile devices. The width specified there should be the same as the canvas elements. On mobile devices, an orientation change will recalculate and change the width to fit the canvas in the viewport.
  • Style touch-action: none; on the HTML tag disables touch behaviors, like pan and zoom for IE10.
  • Elements must all have ids since we use the native getElementById Javascript function to retrieve the Canvas and Image objects from their respective DOM elements.

main.js

File main.js is where you declare and initialize your objects. It is recommended that you wrap that code in a function which gets executed once the document and all assets are loaded (i.e. window.onload event). You can use for example $(window).on("load", function() {});.

Other files use a simple anonymous function instead since they contain class declarations mainly. However in main.js is where Canvas and Image objects are retrieved from the DOM elements. So they must first be loaded. This keeps things simple. Assets are as a consequence always loaded synchronously.

Here is a sample main.js file, taken from the Mario example.

$(window).on("load", function() {

  // Mario alone in an empty world. Control him with the touchpad.

  Backbone.Mario = Backbone.Hero.extend({
    defaults: _.extend({}, Backbone.Hero.prototype.defaults, {
      name: "mario",
      spriteSheet: "mario"
    })
  });

  var canvas = document.getElementById("foreground");

  var spriteSheets = new Backbone.SpriteSheetCollection([{
    id: "mario",
    img: "#mario",
    tileWidth: 32,
    tileHeight: 64,
    tileColumns: 21,
    tileRows: 6
  }]).attachToSpriteClasses();

  var debugPanel = new Backbone.DebugPanel();

  var input = new Backbone.Input({
    drawTouchpad: true,
    drawPause: true
  });

  var mario = new Backbone.Mario({
    x: 400, y: 400, floor: 500
  }, {
    input: input
  });

  var world = new Backbone.World({
    width: 30, height: 18,
    tileWidth: 32, tileHeight: 32,
    backgroundColor: "rgba(66, 66, 255, 1)",
    viewportBottom: 156
  });
  world.add(mario);

  var engine = new Backbone.Engine({}, {
    canvas: canvas,
    debugPanel: this.debugPanel,
    input: input
  });
  engine.add([
    world,
    input,
    debugPanel
  ]);

  // Expose things as globals - easier to debug
  _.extend(window, {
    canvas: canvas,
    engine: engine
  });

  // Ensure the canvas is always visible and centered
  adjustViewport(canvas, canvas.width, canvas.height);

});

Reference

Backbone Game Engine defines classes in the Backbone namespace. Most are sub-classed from Backbone.Model or Backbone.Collection.

Backbone.Engine

new Backbone.Engine([attributes], [options])

Backbone.Engine is a Backbone model that holds a Backbone collection of sprite models. It uses HTML5's requestAnimationFrame to provide a 60 frames per second game loop.

The sprite collection is stored in property sprites. You may directly access it however for convenience, methods add, remove and reset exist as proxy the engine.

Attributes

  • clearOnDraw: Optional. Boolean to ask for a clear of the canvas before redraw. Defaults to false. Note that this is an expensive call. Better to only clear the area that changed.
  • tapDetectionDelay: Optional. The delay in ms before a tap gestured is detected. Defaults to 50ms
  • tapMoveTolerance: Optional. The amount of pixel move tolerated to detect a tap gesture. Defaults to +/-5 pixels. Beyond that, a drag gesture will be trigerred.

Options

Upon instantiation, these options can be passed. They will be stored as properties on the Backbone.Engine model instance.

  • canvas: The canvas to draw upon. Drawing is on its 2d context.
  • input: Optional. The user control input instance. If passed and the pause button is enabled, will stop/start then engine when pressed.
  • debugPanel: Optional. A Backbone.DebugPanel instance. If passed fps and cycleTime are output.

Methods

  • add(): Adds one or multiple models delegating to the sprite collection's add method.
  • remove(): Removes one or multiple models delegating to the sprite collection's remove method.
  • reset(): Clears or sets the sprites collection delegating to the reset method.
  • isRunning(): Returns true if the engine is running, or false if not.
  • start(), stop(): Starts or stops the engine.
  • toggle(): Toggle start/stop the engine.

Events

  • tap: Trigerred when the user clicks or taps on the canvas. A tap is defined when the user presses/clicks on a position without moving for more than tapDetectionDelay ms. The event callback function is passed the DOM event object, with these extra properties attached: canvas, canvasX and canvasY. In addition, property canvasHandled is provided as a mechanism to stop propagation (see below).
  • key: Trigerred when the user types in a key. The event callback function is passed the DOM event object, with additional property canvas.
  • dragstart, dragmove and dragend: Trigerred when a drag gesture occurs. This happens when the user presses/clicks and holds and moves. When these events are trigerred, the tap event does not get trigerred.
Note: The tap and drag* events are broadcasted to whomever is listening. Event property canvasHandled is used to to prevent propagation to many overlapping objects. At first it is set to false. The first object to intercept and handle the event should set it to true. Subsequent objects intercepting the event should look at this property and return without action when true. Backbone.Button and Backbone.WorldEditor implement this behavior.

How it works

During every animation frame, the engine performs these things:

  • Loop through models (in order), and calls their update method. Passing dt, the time in milliseconds since the last call to update. The update method must return true to ask for a redraw, or false not to.
  • Loop through all models that requested a redraw, and call their draw method passing context, the canvas 2d context. Perform whatever magic you like in the draw method.
  • Call itself again upon the next animation frame.
Note: By default the engine does not clear the canvas before redraw. You can set the clearOnDraw option to do so however it is an expensive call. Better to do it only when required. See class Backbone.World for an example.

The update method is used to update the model position, animation, detect collisions, or whatever you like. If it requests a redraw, the engine will then call its draw method. The engine ensures that models are updated and drawn in the order they are sorted in the collection. You can define the sort order by defining a comparator.

Models added to the collection receive an attach event and have property engine set as backreference. When removed, they receive a detach event.

To measure performance, two properties are set: fps and cycleTime. If you passed option debugPanel, they will be drawn on screen.

The engine can be started and stopped. When running, will perform an update/draw sequence 60 times per second. Use methods start, stop or toggle. Use method isRunning to determine if the engine is running. If you passed option input, the engine will bind to the pause button (or the p key) to toggle start/stop.

Usage

  var canvas = document.getElementById("foreground");

  var debugPanel = new Backbone.DebugPanel();

  var ball = new Backbone.Ball({
    x: 100, y: 100, color: "blue"
  });

  var engine = new Backbone.Engine({
    clearOnDraw: true
  }, {
    canvas: canvas,
    debugPanel: debugPanel
  });
  engine.add([
    ball,
    debugPanel
  ]);

Taken from the Bouncing Ball example. Draws two models: the debug panel and a bouncing ball.

Backbone.SpriteSheet

new Backbone.SpriteSheet([attributes], [options]);

Backbone.SpriteSheet is a Backbone model which breaks an image into frames used for animation.

Attributes

  • img: The Image object or element id selector of the image to find in the DOM (i.e. #icons). A pointer to the Image object is then stored in property img.
  • imgUrl: Optional. The url to the image to load dynamically. If specified, and the image element does not exist, will try to load the image.
  • tileWidth, tileHeight: Size of tiles in pixels.
  • tileColumns, tileRows: Number of tiles in the image.

Properties

  • frames: Array of animation frames. Automatically set when model is instantiated.
  • img: Pointer to Image object. Automatically set when model is instantiated.

Events

  • spawnImg: Trigerred when the image is fully loaded.
  • destroyImg: Trigerred when the image is unloaded.

When a sprite sheet is instantiated, an array of frames is built and stored in property frames. A frame object contains the coordinates of the frame. It consists of {x, y, width, height} representing the pixel position and size of the frame. These will be passed to the HTML5 canvas drawImage function as arguments sx, sy, sw, sh by the draw method.

Sprite sheets are not generally created on their own, but rather in a Backbone.SpriteSheetCollection. See below for usage.

Backbone.SpriteSheetCollection

new Backbone.SpriteSheetCollection([models], [options]);

Backbone.SpriteSheetCollection is a Backbone collection of Backbone.SpriteSheet models.

Methods

  • attachToSpriteClasses(): Attaches sprite sheets to sprite class prototypes. Does so by finding all defined sprite classes (Backbone.*) with default attribute spriteSheet matching a sprite sheet id in the collection. Then sets their spriteSheet property to point to the correct sprite sheet in the collection.

Events

  • allSpawnImg: Trigerred when all images are fully loaded.

Usage

Define your sprite sheets by creating a collection as such:

this.spriteSheets = new Backbone.SpriteSheetCollection([{
  id: "mario",
  img: "#mario",
  tileWidth: 32,
  tileHeight: 64,
  tileColumns: 21,
  tileRows: 2
}, {
  id: "tiles",
  img: "#tiles",
  tileWidth: 32,
  tileHeight: 32,
  tileColumns: 29,
  tileRows: 28
}]).attachToSpriteClasses();

Backbone.Input

Here two sprite sheets are created mario and tiles. Their graphics are in Image objects found in the DOM. The attribute img is the selector to retrieve them.

Calling method attachToSpriteClasses will attach the sprite sheets to each sprite class found in the Backbone namespace.

Backbone.Sprite

new Backbone.Sprite([attributes], [options]);

Backbone.Sprite is a Backbone Model which implements the required update and draw methods to animate a sprite, frame by frame.

Attributes

  • x, y: The coordinates in pixels.
  • width, height: Size of the sprite in pixels.
  • paddingLeft, paddingRight, paddingTop, paddingBottom: Optional. Internal padding to account for empty space inside the tile of a sprite. Useful to specify empty zones for collision detection.
  • sequenceIndex: The current animation sequence frame. Automatically set.
  • state: The current animation.
  • spriteSheet: Sprite sheet id.
  • collision: Optional. For use with Backbone.World.
  • static: Optional. For use with Backbone.World.
  • visible: Optional boolean. If true, the sprite is not drawn. Default is false.
  • zIndex: Optional. Specifies the drawing order. Higher value is drawn above sprites with lower values. Default is 0.
Note: zIndex is only used in Backbone.World. Currently, it is partially implemented - only 0 and 1 values are recognized.

Properties

  • animations: Hash of animations of the sprite. Described further below.
  • spriteSheet: Instance of the Backbone.SpriteSheet holding the images to animate. This is automatically set when you define your sprite sheet collection. See Backbone.SpriteSheetCollection for details.
  • saveAttributes: Attributes serialized for persistence. Defaults to ["name", "state", "sequenceIndex", "x", "y"].

Methods

  • toSave(): Serializes attributes for persistence. Attributes to be serialized are specified in the saveAttributes property.
  • update(dt): Called by then engine 60 times a second. Updates sprite attribues and implements behavior. Returns true to ask for a redraw, or false for none.
  • onUpdate(dt): Not defined by default. If you define it, it is called at the end of update. Useful for extending the behavior of a sprite without having to overload metod update. Note that draw is called based on the Boolean return value of this method to decide whether to draw or not. It must therefore return true to perform a draw, or false not to.
  • draw(context, options): Called by the engine after update, if a redraw was asked. Takes care of rendering the sprite, and its proper animation at the correct position. Argument options can be used by a model serving as proxy. For example it is passed when drawn by a Backbone.Worldinstance. It will contain offsetX and offsetY to transform x and y from world coordinates to canvas coordinates.
  • onDraw(context, options): Not defined by default. If defined, it is called at the end of draw. Useful for extending the rendering of a sprite without having to overload metod draw.
  • getAnimation([state]): Returns the current animation based on argument state. If argument state is omitted, the attribute is used instead.
  • overlaps(x, y):: Checks to see if the sprite overlaps with the passed coordinates. Returns a Boolean.
  • getLeft(withPadding), getRight(withPadding), getTop(withPadding), getBotttom(withPadding): Returns the left, right, top or bottom-most position of a sprite. Argument withPadding is a boolean specifying whether to include the padding or not. Defaults to false.
  • bbox(withPadding): Returns the bounding box of the sprite as an object {x1, y1, x2, y2}. Argument withPadding is a boolean specifying whether to include the padding or not. Defaults to false.
  • getCenterX(withPadding): Returns the center x of the sprite. Argument withPadding is a boolean specifying whether to include the padding or not. Defaults to false.
  • getCenterX(withPadding): Returns the center y of the sprite. Argument withPadding is a boolean specifying whether to include the padding or not. Defaults to false.

Events

  • attach: Triggered when the sprite is attached to the engine.
  • detach: Triggered when the sprite is detached to the engine.

The attach and detach events can be used to start/stop listening to events. For example, the Backbone.Hero sprite starts listening to user input when attached, and stops when detached.

Sprite and sprite sheets

Graphics are obtained from a Backbone.SpriteSheet model. In attribute spriteSheet, specify the sprite sheet id you previously defined in a Backbone.SpriteSheetCollection instance. The collection will automatically attach it to the sprite by setting property spriteSheet as back reference. Building on the example above:

var spriteSheets = new Backbone.SpriteSheetCollection([{
  id: "mario",
  img: "#mario",
  tileWidth: 32,
  tileHeight: 64,
  tileColumns: 21,
  tileRows: 2
}]).attachToSpriteClasses();

var mario = new Backbone.Mario({
  spriteSheet: "mario"
});

mario.get("spriteSheet");
// mario

spriteSheets.get("mario");
// child {cid: "c2", attributes: Object, collection: child, _changing: false, _previousAttributes: Object…}

mario.spriteSheet;
// child {cid: "c2", attributes: Object, collection: child, _changing: false, _previousAttributes: Object…}

Inheritance

Instantiating a Backbone.Sprite model is not very useful by itself. You must first extend the Backbone.Sprite class to provide your own animations and a pointer to the sprite sheet. For example this defines a sprite with 3 animations idle, walk-left and walk-right. It points to the sprite sheet id mario.

Backbone.MySprite = Backbone.Sprite.extend({
  defaults: _.extend(_.deepClone(Backbone.Sprite.prototype.defaults), {
    x: 400,
    y: 400,
    spriteSheet: "mario",
    state: "idle",
    sequenceIndex: 0,
    static: false,
    collision: true
  }),
  animations: {
    idle: {
      sequences: [0]
    },
    "walk-right": {
      sequences: [1, 2, 3, 2],
      delay: 200
    },
    "walk-left": {
      sequences: [1, 2, 3, 2],
      scaleX: -1,
      delay: 200
    }
  }
});

Above, the Backbone.Sprite was sub-classed using extend method. defaults are the default attributes to give any new Backbone.MySprite instance. They extend the Sprite class' defaults.

If you want to reuse parts of defaults or animations from a parent class, make sure to make a copy. You can use the helper function _.deepClone for that purpose (_.clone only goes one level deep). Otherwise you may change the parent class behavior. For example the Backbone.PennieUg class reuses the Backbone.Pennie class' properties defaults and animations by first creating copies.


  Backbone.PennieUg = Backbone.Pennie.extend({
    defaults: _.extend(_.deepClone(Backbone.Pennie.prototype.defaults), {
      name: "pennie-ug"
    }),
    animations: _.deepClone(Backbone.Pennie.prototype.animations)
  });
  Backbone.PennieUg.prototype.animations.idle.sequences = [168, 168, 169, 170, 169, 168];
Helper function deepClone was created as a mixin of underscore. When it makes sense, make general functions available that way. Look at the end of src/core.js for all mixins.

Animations

Sprite property animations contains a hash of animations. Each animation contains a sequence of frames and a delay between frames for animation. For example:

animations: {
  idle: {
    sequences: [0, 1],
    delay: 200
  }
}

This defines an animation of two frames, alternating at an interval of 200ms. Values 0 and 1 in array sequences are frame indices defined in the sprite sheet. Sprite attributes state and sequenceIndex control which animation and sequence are currently used. The sequenceIndex is automatically incremented (and reset to 0) by the sprite's draw function. Attribute state determines the current animation. It must be set to idle in the above example (as there is only one).

Extra animation options are available. Here is a complete list:

  • sequences: Array of frame indices, or squence objects. A sequence object looks like this: {frame: 52, x: 0, y: -32, scaleX: 1.00, scaleY: 1}. It allows you to specify an offset to apply when the sprite is drawn, and a scaling factor.
  • scaleX, scaleY: Optional. Scaling factors. Set scaleX to -1 to flip horizontally. Defaults to 1 if omitted.
  • delay: Optional. The time to change to the next sequence. No need to specify if there is only one frame (as there is no animation). You can also define a sprite method sequenceDelay to programmatically return the delay. It will be passed the current animation.

For detailed examples of animations, look at file artifacts.js in the super-mario-bros folder. Class Backbone.Pennie implements a basic animation sequence using frame indices, while Backbone.FlyingPennie implements a more complex animation with sequence objects.

Backbone.Pennie = Backbone.AnimatedTile.extend({
  ...
  animations: {
    idle: {
      sequences: [52, 52, 53, 54, 53, 52],
      delay: 50
    }
  },
  ...

Backbone.FlyingPennie = Backbone.Sprite.extend({
  ...
  animations: {
    anim: {
      sequences: [
        {frame: 52, x: 0, y: -32, scaleX: 1.00, scaleY: 1},
        {frame: 52, x: 0, y: -64, scaleX: 0.50, scaleY: 1},
        {frame: 53, x: 0, y: -90, scaleX: 0.50, scaleY: 1},
        {frame: 53, x: 0, y: -128, scaleX: 1.00, scaleY: 1},
        {frame: 53, x: 0, y: -128, scaleX: 0.50, scaleY: 1},
        {frame: 52, x: 0, y: -112, scaleX: 0.50, scaleY: 1},
        {frame: 52, x: 0, y: -90, scaleX: 1.00, scaleY: 1},
        {frame: 52, x: 0, y: -80, scaleX: 0.50, scaleY: 1},
        {frame: 53, x: 0, y: -80, scaleX: 0.50, scaleY: 1}
      ],
      delay: 50
    }
  },
  ...

Backbone.Input

new Backbone.Input([attributes], [options]);

Backbone.Input class is a model which captures user input events and stores them as model attributes. For example pressing the left arrow, sets the left attribute to true. Depressing sets it to false. Bind to on the attribute change event to be notified.

Backbone.Input supports keyboard, mouse and touch events. It can draw a touchpad on screen with left and right arrow keys, an A (red) button and a B (blue) button.

Backbone.Input

Note: The Backbone.Input model only captures input when attached to a Backbone.Engine.

Attributes

This attribute can be passed when creating the model, to configure the input.

  • drawTouchpad: Optional. Boolean or string "auto" to indicate whether to draw the touchpad. When "auto", the touchpad will be drawn only on touch devies. Defaults to "auto".

The following model attributes are set by the model. They should not be set externally.

  • touchEnabled: Boolean set to true if the device is touch enabled.
  • left: Boolean set to true when the left touchpad arrow or left keyboard arrow key is pressed.
  • right: Boolean set to true when the right touchpad arrow or right keyboard arrow key is pressed.
  • buttonA: Boolean set to true when the A touchpad button is pressed, or when the z keyboard key is pressed.
  • buttonB: Boolean set to true when the B touchpad button is pressed, or when the x keyboard key is pressed.

Methods

  • hasTouchpad(): Returns true if the touchpad is drawn.
  • leftPressed(): Returns true if the left button is pressed.
  • rightPressed(): Returns true if the right button is pressed.
  • buttonAPressed(): Returns true if button A is pressed.
  • buttonBPressed(): Returns true if button B is pressed.

Events

  • attach: Triggered when the input is attached to the engine. Will start listening to user input.
  • detach: Triggered when the input is detached to the engine. Will stop listening to user input.

You can add or remove a Backbone.Input model from the engine on the fly. In the Super Mario Bros example, the Backbone.Input and the Backbone.WorldEditor are swapped when moving from play to edit modes.

Usage

var input = new Backbone.Input();
var engine = new Backbone.Engine();
engine.add(input);
input.bind("change:left", function(input) {
  if (input.leftPressed())
    console.log("left pressed:)");
  else
    console.log("left depressed:(");
});

Backbone.World

new Backbone.World([attributes], [options])

Backbone.World is model which contains a collection of sprites that interact with each other. A world is an environment composed of tiles and characters. The world extends beyond the canvas however the viewport, the visible portion, is constrained within its prescribed limits. Backbone.World is similar to a sprite; it implements the update and draw methods required by the Backbone.Engine collection.

Attributes

  • x, y: Origin of top-left corner in pixels.
  • width, height: Size of world in tiles.
  • tileWidth, tileHeight: Size of a tile in pixels.
  • viewportLeft, viewportRight, viewportTop, viewportBottom: Defines an area in the canvas the world is constrained to. Each value provides gutter regions in pixels. Anything drawn by the world will be clipped in the area. Useful for drawing a menu bar or buttons on the same canvas. In provided demos for example, viewportBottom is set to 156 pixels to make room the the touchpad.
  • sprites: Array of sprite models for persistence.
  • backgroundColor: Background color of the world.
  • backgroundImage: Id attribute of an image element in the DOM to show as background.
  • state: Persisted state either play or pause.
Note: Setting a viewport different than the canvas size uses canvas clipping. Avoid using if you can as clipping the is expensive and can introduce performance issues.

Options

  • backgroundImage: Optional. Pass to use a background image instead of a background color. Anchored to the origin.
  • input: Input instance to control the hero.
  • camera: Camera instance to keep the hero in the viewport.
  • debugPanel: Optional.

Methods

A Backbone.World is a model that wraps a collection stored in property sprites. To prevent outside direct access to this collection, it provides these two methods:

  • add(models, [options]): Add one or many models. Adds world to options and delegates to the sprites collection's add method. Then sets the world property as back-reference on the new model(s). Returns the new model(s).
  • remove(models, [options]): Removes one or many models. Delegates to the sprites collection's remove method. Deletes the world back-reference and returns the model(s).

In addition to standard Backbone.Model methods, it also exposes these:

  • spawnSprites(): Resets the sprites collection by retrieving the sprites attribute from the model. This is called after the world is loaded.
  • update(dt): Update function called by the engine. Will in turn call the update method of all sprites.
  • draw(context): Draw function called by the engine. Will in turn call the draw method of all sprites.
  • cloneAtPosition(sprite, x, y): Clones the sprite model and places the new instance at the specified coordinates. Will pass world to the options payload when created. Will also set property world as back-reference. If the sprite name matches that of the world attribute hero it will also pass option input, and if a Backbone.Camera exists, it will be tied to it. This function also acts as a toggle when placing a sprite over a tile where another exists. The existing one is removed. This ensures only one tile (static sprite) exists at one location.
  • width(), height(): Return the size of the world in pixels.
  • getWorldIndex(object): Calculates the index position of a tile based on coordinates. Argument object must contain x and y, or be a model with those attributes. The index is calculated with formula height * x/tileWidth + y/tileHeight. This method is used to set the id of tile sprites.
  • getWorldCol(x), getWorldRow(y): Returns the tile position in columns or rows of a coordinate.
  • findAt(x, y, [type], [exclude], [collision]): Finds the first sprite at the specified coordinate. Use for collision detection. Optional arguments allow you to filter what to look for. Set type to character to find moving sprites, or tile to find tiles. Set exclude to the id of the sprite you want to exclude from the search. In a collision detection scheme, this is usually the id of the sprite you are checking against. Set collision to true to find only tiles that have their collision flag set.
  • filterAt(x, y, [type], [exclude], [collision]): Same as findAt but returns the list of all matching sprites instead of the first.
  • findCollidingAt(x, y): Finds a colliding tile. Just like calling findAt(x, y, "tile", null, true).
  • findCollisions(): Finds collisions for a given set of collision map.

Events

  • attach: Triggered when the sprite is attached to the engine. Will trigger the attach method of all sprites in the world.
  • detach: Triggered when the sprite is detached to the engine. Will trigger the detach event of all sprites in the world.
  • tap: Trigerred when the user taps or clicks on the canvas. The callback will be passed the DOM event extended with these properties: canvas, canvasX, canvasY, world, worldX and worldY.
  • key: Trigerred on a keyup event. The event callback will be passed the DOM event as argument.

How it works

Sprites can be added and removed via methods add and remove. Sprites are automatically attached to the Backbone.Engine the world is attached to. Sprites then have properties engine and world set as back-reference. In each request frame, the same mechanics apply as for sprites attached directly to an engine; methods update and draw are called for each sprite. The exception is for static sprites which are only updated/redrawn when required (see below).

Internally, the world keeps sprites into a collection stored in property sprites. It further splits sprites into 2 collections for faster lookup:

  • staticSprites: Background sprites that have no animation. These are usually same-sized tiles. Sprites that have their static attribute set to true will be put in this collection. Sprites are given an id determined by their position (column and row). The collection is ordered and indexed on id allowing for fast lookup on a pair of x/y coordinates. In addition, these sprites are drawn on a background canvas only drawn when required (i.e. world is panned).
  • dynamicSprites: Animated tiles and characters. Their static attribute must be set to false to fall in this collection. These sprites are given unique id attributes based on their name (i.e. mario.1). They are not indexed therefore lookup has an order of N. Keep the number of sprites here to a minimum.

A world is measured in tiles via attributes width, height, tileWidth and tileHeight. Call methods width and height to get the size in pixels. Attributes x and y determine the origin in pixels (top-left corner) and allow the world to be panned consequently changing the viewport.

Note: If you define an animated tile, make sure its static attribute is set to false to prevent redraws every animation frame. Also ensure the width of tile sprites match that of world attributes tileWidth and tileHeight.

Sprites

When the world is created, sprites are instantiated in method spawnSprites. Each sprite instance is attached to the engine. Sprites then have properties engine and world set pointing to those respective objects.

Sprites can be categorized with attribute type as to identify one another when they interact with each other. Sprites of type tile are usually static and obstacles. Sprites of type character are usually dynamic and moving sprites.

You can add sprites with method add which delegates to the sprites collection's add method. It takes care of passing the world as option.

You can also use method cloneAtPosition(sprite, x, y). It takes as argument an existing sprite, and coordinates. It will clone the sprite and place it at the specified coordinates passing world and input as options. If the sprite has its hero attribute set to ture, and if a Backbone.Camera exists, it will be tied to it. Backbone.WorldEditor uses this function for instance.

Methods getWorldIndex, getWorldCol and getWorldRow can be used to find the position of a sprite. A sprite's x and y attributes determine their position relative to the world origin.

Background and Tiles

The background of a world is composed of same-size tiles defined by attributes tileWidth and tileHeight. A tile is usually a non-animated sprite with its static attribute set to true. The model id is the position of the sprite on screen (column and row). As such, there can only be one tile per location.

Character Sprites

Characters are sprites that interact with their environment. Backbone.Character and Backbone.Hero are character sprites. Character sprite models usually have teir type attribute set to character. You are free to use attribute type to classify your sprites.

Collision detection

Internally sprite positions are stored in a QuadTree. As a sprite moves, its position in the QuadTree is updated.

Three methods exist to detect collisions.

  • findAt(x, y, [type], [exclude], [collision]): Find the first sprite touching the given point or null if none are found. Optional arguments can be passed for limiting the lookup:
    • type: Optional. If set, will only lookup sprites matching that type. Typical values are character or tile.
    • exlcude: Optional. The sprite model's id to exclude in lookup.
    • collision: Optional. Boolean indicating whether to only include sprites that have the collision attribute explicitly set to true.
  • filterAt(x, y, [type], [exclude], [collision]): Same as a above but finds all sprites touching the given point. Returns an array of sprites.
  • findCollisions(map, [type], [exclude], [collision]): Detects collisions on sprites for a set of named coordinates. Optional arguments are the same as above. Argument map is an array of objects that is passed in, and passed out. A map object must contain:
    • x and y: Coordinates to detect the collision.
    • dir: The lookout direction: top, right, bottom or left.
    • sprites: Array of detected colliding sprites. Reset/initialized to [] every call.
    • sprite: The closest sprite based on the lookout direction.

Look at the code in Backbone.Character and Backbone.Hero classes for examples on how to perform collision detection.

Persistence

The world model attributes contain all that is necessary to persist the state of the world to disk, or in the cloud. Calling the save method first serializes the sprite collection from the sprites property into the sprites attribute. It calls the toSave method on each sprite to save their position and state. It then delegates to the backbone model's save method to save to a server or local storage in JSON format. See the Backbone documentation for details.

The world can be restored by calling spawnSprites passing as argument a saved world in JSON format. For example, file super-mario-world/level-1-1.js contains the level in JSON format. It is set in global variable _world. The world can be restarted anytime with this line of code:

world.set(window._world).spawnSprites();

Usage

  var mario = new Backbone.Mario({
    x: 400, y: 400, floor: 500
  });

  var world = new Backbone.World({
    width: 30, height: 18,
    tileWidth: 32, tileHeight: 32,
    backgroundColor: "rgba(66, 66, 255, 1)"
  });
  world.add(mario);

  var engine = new Backbone.Engine();
  engine.add(world);

A sprite (mario) is first created and added it the world. The world is then added to the engine. This is taken from file mario/main.js for the Mario example.

Backbone.WorldEditor

new Backbone.WorldEditor([attributes], [options])

Backbone.WorldEditor is a model which displays a palette of sprites and allows the user to edit a Backbone.World instance by placing and removing sprites. Also allows the user to pan the world. In the Super Mario Bros demo, an editor is drawn at the bottom of the world replacing the Backbone.Input. Like sprites and world, it must be added to the Backbone.Engine to render. The palette will page automatically if there are too many sprites.

Backbone.Input

Attributes

  • x, y: Top-left placement position in pixels of the editor on the canvas.
  • width, height: Size of editor in pixels.
  • tileWidth, tileHeight: Size of sprites drawn in the palette.
  • padding: Padding in pixels around sprites.
  • backgroundColor: Background color of the editor.
  • selectColor: Background and outline color of a selected sprite.
  • selected: The name of the cuurrently selected sprite.
  • spriteNames: Ordered array of sprite names in the palette.

Options

  • world: The Backbone.World to edit.
  • debugPanel: Optional. If passed will output mouse coordinates.

Events

  • attach: Triggered when the sprite is attached to the engine. Will start listening to user input.
  • detach: Triggered when the sprite is detached to the engine. Will stop listening to user input.

How it works

The palette is drawn as a series of tiles left to right, top to bottom. Each tile is a sprite. The sprite may be bigger than the specified tile size, in which case it will be resized to fit the tileWidth and tileHeight attributes.

Backbone.Character

new Backbone.Character([attributes], [options])

The Backbone.Character is a sub-classed Backbone.Sprite which implements an animated character with elementary AI (artificial intelligence) and physics. AI is limited to moving left, right, jumping/gravity and dying. Direction changes occur with collisions against an obstacle; either a tile or another character. You are free to extend the AI by subclassing it.

A Backbone.Character must live inside a Backbone.World.

Attributes

  • x, y: Position of the sprite.
  • floor: Set this value to prevent the character to fall outisde of the world due to gravity. By default it is null meaning the character will be removed from the world if it falls below the bottom of the world.
  • ceiling: Analogous to floor but for exiting the world from above.
  • width, height: Size of the sprite in pixels. Defaults to 32 x 32.
  • paddingLeft, paddingRight, paddingTop, paddingBottom: Optional. Internal padding to account for empty space inside the tile of a sprite. Useful to specify empty zones for collision detection.
  • sequenceIndex: The current animation sequence frame. Automatically set.
  • name: Name of the sprite.
  • type: Type of sprite. Defaults to character.
  • spriteSheet: Id of sprite sheet.
  • state: Current animation. Starts with idle-right.
  • collision: Boolean indicating whether it can be detected by other sprites. Defaults to true.
  • static: Set to false for the World to draw every animation frame.
  • visible: Set to true for the sprite to be drawn.
  • zIndex: Optional. Specifies the drawing order. Higher value is drawn above sprites with lower values. Default is 0.
  • velocity, acceleration: Current horizontal movement. Controlled by the AI in update.
  • yVelocity, yAcceleration: Current vertical movement. Controlled by the AI in update.
  • health: Amount of health the character has. When health is 0, the is "knocked-out" and dies. Defaults to 1.
  • attackDamage: This is the amount of damage the character does to an opponent. Will reduce their health by this value. Defaults to 1.
  • aiDelay: Time in ms between AI calls.

Methods

Backbone.Character inherits all methods from Backbone.Sprite. Here are additional methods, and ones with a different signature.

  • getStateInfo([state]): Decomposes state into move and direction components. For example walk-left would be decomposed into {mov:"walk", mov2: null, dir:"left", opo:"right"}. If argument state is omitted, the state attribute is used instead. See Backbone.Hero for a more detailed description.
  • buildState(mov, [mov2], [dir]): Helper to construct and return a dash-separated state string. Ignores falsy values. For instance buildState("walk", "left") and buildState("walk", null, "left") both return walk-left.
  • update(dt): Handles the character movements and collision detection. Also calls the ai function.
  • ai(dt): Implements the AI. Is called at an interval defined by attribute aiDelay.
  • updateSequenceIndex(dt): Returns the new sequenceIndex (animation frame). Called by update.
  • toggleDirection(dirIntent): Invokes a left or right direction change.
  • startNewAnimation(state, attrs, done): Changes the state of the character starting a new animation. Pass hash attrs to set attributes at the same time. Callback done is called once the animation is complete. Useful for performing only one animation sequence.
  • hit(sprite, [dir], [dir2]): Function invoked usually by a hit event. Implement this to determine what happens when another sprite hits the character. Argument dir is the primary direction of the hit (left, right, top or bottom). Argument dir2 is the secondary direction. In top-left for example, it would be left. dir2 can also be attack in which case health is reduced by attackDamage amount.
  • hurt(sprite, [dir], [dir2]): Function invoked when the character gets attacked and has remaining health. The character bounces back.
  • knockout(sprite, [dir], [dir2]): Function invoked when the sprite gets knocked out of the world. The sprite is litterally knocked out updside down. This function is called when health is 0.

Events

  • attach: Triggered when the sprite is attached to the world. Turns on the AI.
  • detach: Triggered when the sprite is detached to the world. Turns off the AI meaning the character will not move.
  • hit: Trigger this event on the Backbone.Character to indicate a hit with another character. Delegates to the hit method above. For example, Backbone.Hero executes this when he lands on a character: sprite.trigger("hit", this, "top", "left");.
  • change:health: Whenever the character health decreases method hurt gets called. If health reaches 0, method knockout gets called instead.

How it works

During an animation frame, it is important to understand the method and event sequence. First, the update method is called. It may then call the ai method if the aiDelay internal is reached. Afterwhich collision detection is performed. At that point, the character may trigger hit events on other characters. Those may in turn trigger a hit event on the character. The character position and velocities are updated, and finally, the character model is set. The update method then returns true to tell the engine to draw the sprite, or false to prevent a redraw.

These events are synchroneous, and will occur before the update method is completely executed. Therefore methods ai, hit, hurt and knockout may set the character model and abort the update. To abort the update, those methods set property cancelUpdate to true.

Example

In the Super Mario Bros example, look at file enemies.js. There are implementations for basic characters in that game. The hit method implements collision detection outcome, notably when Mario lands on a character. In the case of a Mushroom, it gets squished. When a turtle shell is sliding, it would cause a knockout.

...
hit: function(sprite, dir, dir2) {
  if (this._handlingSpriteHit) return this;
  this._handlingSpriteHit = sprite;

  var cur = this.getStateInfo(),
      opo = dir == "left" ? "right" : (dir == "right" ? "left" : (dir == "top" ? "bottom" : "top"));

  if (sprite.get("hero")) {
    if (dir == "top")
      this.squish.apply(this, arguments);
  } else if (sprite.get("state").indexOf("slide") != -1 ||
            sprite.get("type") == "tile" && dir == "bottom" && sprite.get("state") == "bounce") {
    this.knockout.apply(this, arguments);
  }
  sprite.trigger("hit", this, opo);

  this._handlingSpriteHit = undefined;
  return this;
}
...

The hit method will in turn trigger a hit event on the sprite which caused the collision. That sprite may in turn trigger a hit event. To avoid the possibility of inifite callback loops, private property _handleSpriteHit is used.

Backbone.Hero

new Backbone.Hero([attributes], [options])

The Backbone.Hero is a sub-classed Backbone.Character which implements a controlable character with input and physics. Its playability is based on that of Mario in the Nintendo classic Super Mario Bros. Currently, only the small Mario is implemented.

A Backbone.Hero must live inside a Backbone.World.

Attributes

  • x, y: Position of the sprite.
  • floor: Set this value to prevent the character to fall outisde of the world due to gravity. By default it is null meaning the character will be removed from the world if it falls below the bottom of the world.
  • ceiling: Analogous to floor but for exiting the world from above.
  • width, height: Size of the sprite in pixels. Defaults to 32 x 64.
  • paddingLeft, paddingRight, paddingTop, paddingBottom: Optional. Internal padding to account for empty space inside the tile of a sprite. Useful to specify empty zones for collision detection. paddingTop is set to 32 to start as Mario is small.
  • sequenceIndex: The current animation sequence frame. Automatically set.
  • name: Name of the sprite. Defaults to hero.
  • hero: Boolean set to true indicating this character sprite is the hero. Backbone.World will detect this and pass the Backbone.Input instance for taking user input.
  • type: Type of sprite. Defaults to character.
  • spriteSheet: Id of sprite sheet.
  • state: Current animation. Starts with idle-right.
  • collision: Boolean indicating whether it can be detected by other sprites. Defaults to true.
  • static: Set to false for the World to draw every animation frame.
  • visible: Set to true for the sprite to be drawn.
  • zIndex: Optional. Specifies the drawing order. Higher value is drawn above sprites with lower values. Default is 0.
  • velocity, acceleration: Current horizontal movement. Do not set.
  • yVelocity, yAcceleration: Current vertical movement. Do not set.
  • health: Amount of health as an integer. When set to 0, the character is knocked out and dies. Defaults to 1.
  • healthMax: The maximum allowed health. Defaults to 2.
  • dead: Boolean flag indicating if the sprite is dead.
  • attackDamage: This is the amount of damage the hero does to an opponent. Will reduce their health by this value. Defaults to 1.
  • ignoreInput: Flag to prevent the hero from accepting user input. Defaults to false.
  • canAttack: Flag indicating whether the B button will result in an attack. Defaults to false.
  • canTurnInJump: By default the hero cannot turn in air (jumping or falling). Set this to tru to enable it.

Options

  • input: Optional. A Backbone.Input instance to allow control of the character with the keyboard (left and right arrows, z and x) or via touch events on the on-screen drawn gamepad.

Methods

Backbone.Hero inherits all methods from Backbone.Sprite and Backbone.Character. In addition, it provides these methods you can override:

  • dirToggled(dirIntent): Called when the user presses the left or right buttons. Argument dirIntent maye be left or right. The default implementation handles left/right movements.
  • buttonAToggled: Called when the user presses the A button (rigt-most blue on pad, or x on keyboard). The default implementation handles jumping.
  • buttonBToggled: Called when the user presses the B button (left-most red on pad, or z on keyboard). The default implementation handles running and attacking (if canAttack is set to true).

Events

Backbone.Hero has the same events as Backbone.Character.

How it works

Attribute state determines the current animation. A state is a pair of move and direction. For example idle-right, walk-left and jump-right. Method getStateInfo will decompose the state for you into those components.

With attribute nextState, we are able to implement tweens, or in-between animations. For example, holding the right arrow button puts the hero in a walk-right state (and slide-right next state). Releasing the right arrow button transitions to the slide-right state (and idle-right next state) for quick decelration until velocity reaches 0 to fall to the idle-right state. If instead the left arrow button is pressed, we transition to the skid-left state (and walk-left next state) for quicker decelartion until velocity reaches 0 to then accelerate in the opposite direction to fall in the walk-left state (and slide-left next state).

Holding an arrow button and the A button (z on keyboard) allows the character to run. Its state becomes run-right (or run-left). Pressing the B button allows the character to jump (jump-left or jump-right). Hold the B button to jump higher. You can control the direction and distance jumped with the arrow buttons. And like in the real game, a jump while running will go higher to cover more distance.

Physics are implemented with attributes velocity, acceleration, yVelocity and yAcceleration measured in pixels per seconds. The two first control horizontal translations over time where a positive velocity indicates a move to the right, and negative to the left. The latter two control vertical translations where a positive yVelocity indicates a move downwards, and negative upwards.

Each animation have targets of these properties when required. They are omitted otherwise (i.e. walk-right has no yVelocity or yAcceleration). The update method updates the model attributes according to the targets configured on the current animation. When state changes to another animation, so do the targets, and so follow the actual velocities over time to reach their targets. This gives the character momentum.

Gravity is implemented with a positive yAcceleration. Unless atop a tile, our character will fall. However it is constrained by the extent of the Backbone.World it is contained in. And will therefore stop falling when the bottom is reached.

Collisions

Our character detects collisions either from tiles or other characters to constrain its movements. It does so using collision detection method findCollidings from Backbone.World. Every update, collisions with other sprites are detected on the outline of the sprite:

Hero Collisions

Collisions are only handled when necessary. For instance, when jumping collisions are handled top and right only. The decision is based on looking at velocity and yVelocity. For gravity, a check is performed every time at the bottom of the sprite to land or to fall.

var bottomWorld = this.world.height() + tileHeight,
    floor = this.get("floor") || bottomWorld,
    bottomY = Math.min(floor, bottomWorld);

for (i = 0; i < this.collisionMap.bottom.sprites.length; i++) {
  sprite = this.collisionMap.bottom.sprites[i];
  bottomY = Math.min(bottomY, sprite.getTop(true));
  if (sprite.get("type") == "platform") bottomPlatform = sprite;
}

...

if (yVelocity > 0 && heroBottomY >= bottomY) {
  // Stop falling
  land(bottomY);
  for (i = 0; i < this.collisionMap.bottom.sprites.length; i++)
    this.collisionMap.bottom.sprites[i].trigger("hit", this, "top", cur.dir);
  if (this.cancelUpdate) return true;
} else if (cur.mov != "jump" && yVelocity == 0 && heroBottomY < bottomY) {
  // Start falling if no obstacle below
  attrs.nextState = state;
  attrs.state = this.buildState("jump", cur.mov2, cur.dir);
} else if (yVelocity == 0 && heroBottomY == bottomY) {
  // On a floating platform - same vertical velocity
  if (bottomPlatform)
    relativeVelocity = bottomPlatform.get("velocity");
}

When an obstacle is hit, the character stops moving. Its x is anchored, and velocity set to 0.

if (velocity >= 0) {
  // Stop if obstacle to the right
  var rightX = this.world.width();
  for (i = 0; i < this.collisionMap.right.sprites.length; i++)
    if (heroTopY > 0 )
      rightX = Math.min(rightX, this.collisionMap.right.sprites[i].getLeft(true));

  if (heroRightX >= rightX) {
    attrs.velocity = velocity = 0;
    attrs.x = x = rightX - heroWidth - paddingLeft;
    for (i = 0; i < this.collisionMap.right.sprites.length; i++)
      this.collisionMap.right.sprites[i].trigger("hit", this, "left", cur.mov2);
    if (this.cancelUpdate) return true;
  }
}

A collision with other sprites triggers an hit event on that sprite. The event hanlder will receive the colliding sprite (hero), and its relative position where the hit occured (left of the enemie sprite in this case).

Usage

This excerpt is taken from the Mario example.

  Backbone.Mario = Backbone.Hero.extend({
    defaults: _.extend({}, Backbone.Hero.prototype.defaults, {
      name: "mario",
      spriteSheet: "mario"
    })
  });

  var canvas = document.getElementById("foreground");

  var spriteSheets = new Backbone.SpriteSheetCollection([{
    id: "mario",
    img: "#mario",
    tileWidth: 32,
    tileHeight: 64,
    tileColumns: 21,
    tileRows: 6
  }]).attachToSpriteClasses();

  var input = new Backbone.Input({
    drawTouchpad: true,
    drawPause: true
  });

  var mario = new Backbone.Mario({
    x: 400, y: 400
  }, {
    input: input
  });

  var world = new Backbone.World({
    width: 30, height: 18,
    tileWidth: 32, tileHeight: 32,
    backgroundColor: "rgba(66, 66, 255, 1)"
  });
  world.add(mario);

  var engine = new Backbone.Engine({}, {
    canvas: canvas,
    input: input
  });
  engine.add([
    world,
    input
  ]);

Backbone.Camera

new Backbone.Camera([attributes], [options])

Backbone.Camera is a model which ensures a sprite, the subject, is always in the viewport (the canvas). It pans the Backbone.World when the character steps out of a window in the viewport. Backbone.Camera must be added to the engine (not the world).

Attributes

  • left, right, top, bottom: Coordinates of the window with respect to the drawn canvas.

Options

  • subject: The sprite to keep in the viewport.
  • world: The Backbone.World.

Methods

  • update(dt): No-op. Simply returns false for no redraw.
  • draw(context): No-op.
  • maybePan(): Called when the x or y position of the subject changes. Ensures the subject is within the specified window, otherwise pans the world.

Events

  • attach: Triggered when the sprite is attached to the engine. Starts monitoring the subject and pans the Backbone.World when necessary.
  • detach: Triggered when the sprite is detached to the engine. Stops monitoring the subject.

Usage

var spriteSheets = new Backbone.SpriteSheetCollection({
  id: "mario",
  img: "#mario",
  tileWidth: 32,
  tileHeight: 64,
  tileColumns: 21,
  tileRows: 6
});
var mario = new Backbone.Mario();
var world = new Backbone.World();
var camera = new Backbone.Camera(
  {left: 200, right: 600, top: 100, bottom: 50},
  {subject: mario, world: world}
);
world.add(mario);
var engine = new Backbone.Engine();
engine.add([world, camera]);

Backbone.Clock

Backbone.Clock is a model which ticks at a set time interval. Attribute ticks stores an integer tick value that gets incremented every interval. Useful for synchronizing sprite animations.

Attributes

  • ticks: Initial tick value. An integer that gets incremented every interval.
  • delay: Interval between ticks.

Methods

  • update(dt): Updates the ticks.
  • draw(context): No-op. Simply returns the model.

Events

  • attach: Triggered when the sprite is attached to the engine. Starts ticking.
  • detach: Triggered when the sprite is detached to the engine. Stops ticking.

Usage

var clock = new Backbone.Clock({delay: 2000});
clock.on("change:ticks", function() {
  console.log("tick", clock.get("ticks");
});

As an example, look at the Super Mario Bros example, question blocks and pennies are all synced on the same clock. These sprites are sub-classes of Backbone.AnimatedTile. Instances create a Backbone.Clock if one does not exist. Otherwise, they find the existing one by that name and reuse it.

Backbone.AnimatedTile = Backbone.Tile.extend({
  initialize: function(attributes, options) {
    Backbone.Tile.prototype.initialize.apply(this, arguments);
    this.on("attach", this.onAttach, this);
    this.on("detach", this.onDetach, this);
  },
  onAttach: function() {
    if (!this.engine) return;
    this.onDetach();

    this.clock = this.engine.findWhere({name: "animatedTileClock"});

    if (!this.clock)
      this.clock = this.engine.add(new Backbone.Clock({name: "animatedTileClock", delay: 200}));

    this.listenTo(this.clock, "change:ticks", this.updateAnimationIndex);
  },
  onDetach: function() {
    if (this.clock) this.stopListening(this.clock);
    this.clock = undefined;
  },
  update: function(dt) {
    return true;
  },
  updateAnimationIndex: function() {
    var animation = this.getAnimation(),
        sequenceIndex = this.get("sequenceIndex") || 0;
    if (!animation) return;
    this.set("sequenceIndex", sequenceIndex < animation.sequences.length-1 ? sequenceIndex + 1 : 0);
  }
});

Backbone.Element

Backbone.Element is a model that mimics a rudimentary DOM element. Currently, it supports these features:

  • Fixed position specified by top-left x and y.
  • Optional background color and rounded corners.
  • Optional image.
  • Optional text.
  • Can be animated with easing functions. Supported animations are: translation, fade in and fade out.

Attributes

  • x, y: Top left corner of button.
  • width, height: Size of button.
  • backgroundColor: Fill style of the button. Set to transparent to see thru.
  • borderRadius: Border radius in pixels for rounded corners.
  • img: The Image object or element id selector of the image to find in the DOM (i.e. #icons). A pointer to the Image object is then stored in property img
  • imgX, imgY, imgWidth, imgHeight: Bounding box of image location in source image.
  • imgMargin: Offset in pixels to apply when drawing the image inside the button.
  • text: Text to draw. If empty, not text is drawn.
  • textPadding, textLineHeight: Padding around the text and line height in pixels.
  • textContextAttributes: Canvas text styling properties. Will be set on the canvas context before calling fillText. Consult HTML5 fillText documentation for details.
  • easing: Easing function to use as defined in Backbone.EasingFunctions (see below).
  • easingTime: Duration of the animation in ms. Defaults to 1000ms.
  • opacity: Opacity of the element. 1 for fully visible, 0 for invisible and in-between for translucent. This value is changed by fadeIn and fadeOut animations.
  • scale: Zoom-in, zoom-out scale to apply to the element. Default is 1. Can be used to defined your own animation (see Backbone.Button's pressed animation below).

Methods

  • update(dt): Draws the element and handles animations.
  • onUpdate(dt): Not defined by default. If deinfed, called at the end of update. It must return true or false to determine whether to draw the element or not.
  • draw(context, options): Draws the element.
  • onDraw(context, options): Not defined by default. If deinfed, called at the end of draw.
  • drawText(b, context, options): Called by draw to draw text. Argument b is the JSONized model (this.toJSON()) defining the context. You can call this to draw extra text if you implemented method onDraw.
  • overlaps(x, y):: Checks to see if the sprite overlaps with the passed coordinates. Returns a Boolean.
  • moveTo(x, y, callback): Translation animation. Will move the element from the current position to the specified x and y position using the easing function and easingTime duration.
  • fadeIn(callback): Animates a fade in. Starts with an opacity of 0 going up to 1.
  • fadeOut(callback): Animates a fade out. Starts with an opacity of 1 going down to 0.

Events

  • attach: Triggered when the sprite is attached to the engine. Starts listening to user input.
  • detach: Triggered when the sprite is detached to the engine. Stops listening to user input.

Animations

Animations are driven by attributes easing and easingTime. The first defines the easing function to use and the second the duration in ms. These functions are found in Backbone.EasingFunctions and are:

  • linear
  • easeInQuad
  • easeOutQuad
  • easeInOutQuad
  • easeInCubic
  • easeOutCubic
  • easeInOutCubic
  • easeInQuart
  • easeOutQuart
  • easeInOutQuart
  • easeInQuint
  • easeOutQuint
  • easeInOutQuint
Easing functions return a value between 0 and 1.

You can create your own animations pretty easily. Look at the code for examples.

Usage

TO DO...

Backbone.Button

Backbone.Button is a Backbone.Element which listens to tap/click events and triggers a tap event when pressed. When pressed there is a grow-shrink animation to give the user feedback.

Usage

var button = new Backbone.Button({
  x: 4, y: 4, width: 52, height: 52, borderRadius: 5,
  img: "#icons", imgX: 0, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
});
button.on("tap", function() {
  console.log("button tapped!");
});

Backbone.DebugPanel

Backbone.DebugPanel is a Backbone model on which you set attributes to be dumped on screen. Upon draw, it will JSON.stringify attributes.

Events

  • update(dt): No-op. Simply returns true.
  • draw(context): Draws the debug information on screen.
  • attach: Triggered when the sprite is attached to the engine.
  • detach: Triggered when the sprite is detached to the engine.

Usage

var debugPanel = new Backbone.DebugPanel();
var engine = new Backbone.Engine({}, {
  debugPanel: debugPanel
});
engine.add(debugPanel);

debugPanel.set({hello: "Word"});
// Draws this on screen
// {"fps": 58, "ct": 7, "hello": "World"}

debugPanel.set({hello: "Dolly"});
// {"fps": 58, "ct": 7, "hello": "Dolly"}

debugPanel.unset("hello");
// {"fps": 58, "ct": 7}

In the above example, the debug panel is created. It is added to the engine as a model to draw. It is also passed as an option to the engine so it can output fps and ct (cycle time).

We manually add attribute hello to be tracked. Whenever it changes, so does the print out on screen. Use unset to remove a tracked attribute.

Conditional Usage

It is recommended that you support the non-existence of the debug panel with an if (this.debugPanel) statement before setting. For example, when you extend a class, pass in the debug panel as an option. Then, in your code, check to see if it exists. For example, this is done in the Backbone.Engine.draw method:

if (this.debugPanel) this.debugPanel.set({fps: this.fps, ct: this.cycleTime});

This supports the case where the debug panel is never created (debugPanel = undefined), such as in production.

Shape functions

File shapes.js contains helper functions to draw elementary shapes in the 2d drawing context of a canvas. You are free to use direct methods on the context to draw. These are provided as convenience. The functions are added to the global scope, under window. Supported functions are:

drawRect(ctx, x, y, width, height, fill, stroke)
drawCircle(ctx, x, y, radius, fill, stroke)
drawRoundRect(ctx, x, y, width, height, radius, fill, stroke)

I encourage you to add your own. If you do, respect these recommendations:

  • Functions take as first argument ctx the drawing context.
  • Second and third arguments should be x and y coordinates.
  • Last arguments should be fill the fill style, and stroke the stroke style. They should be optional if possible.

Mobile Devices

Backbone Game Engine was built for mobile first.

Touch Events

Backbone.Engine, Backbone.Input, Backbone.Button and Backbone.WorldEditor support touch and mouse events transparently. Works on Android, iOS and Windows.

Viewport resizing and canvas centering

On mobile devices, the meta tag viewport is set to 960 pixels wide. On iOS, Android and Windows mobile devices, this will ensure the canvas is full width. The HTML file contains the necessary header tags to ensure everything works. You can change the viewport width value to whatever you want.

<meta name="viewport" content="width=960, user-scalable=no"/>
<meta name="mobileoptimized" content="0" />

Not all screens have the same aspec ratio. To take care of the height, you can change the height of the canvas upon start by calling the global function adjustViewport() (see file adjust-viewport.js for details).

var canvas = document.getElementById("foreground");
adjustViewport(canvas);

If you want to maintain the aspect ratio, pass true. The canvas will be centered on screen.

var canvas = document.getElementById("foreground");
adjustViewport(canvas, true);

On desktop the viewport meta tag is ignored. adjustViewport will center the canvas, even handling resizes. It will try to reduce the height of the canvas if too tall unless you omit the keepRatio argument.

Web App

These meta tags are set to enable Web App support:

<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>

To suggest users to put add the home page to the home screen, checkout this great plugin: Cubiq's Add To Homescreen.

Going Offline

With HTML Application Cache, you can go offline with your game. Super Mario Bros level 1-1 uses the application cache. The first time your browser loads that page, it will save the web page, along with all assets in its application cache. Subsequent visits will load these from the application cache instead of the server.

Note: Application Cache only works when loaded from a server. It will not if you have forked the repo, and are loading the file from your disk (file:///). That's good because under development, we want to load the new code every refresh.

If you have Google Chrome, open the console and you will see this:

Creating Application Cache with manifest http://martindrapeau.github.io/backbone-game-engine/super-mario-bros/offline.appcache
Application Cache Checking event martindrapeau.github.io/:1
Application Cache Downloading event martindrapeau.github.io/:1
Application Cache Progress event (0 of 23) http://martindrapeau.github.io/backbone-game-engine/3rd/qtree.js
Application Cache Progress event (1 of 23) http://martindrapeau.github.io/backbone-game-engine/src/input.js
...
Application Cache Progress event (22 of 23) http://martindrapeau.github.io/backbone-game-engine/super-mario-bros/super-mario-enemies-2x.png
Application Cache Progress event (23 of 23)
Application Cache Cached event 

Subsequent times, you will see this:

Document was loaded from Application Cache with manifest http://martindrapeau.github.io/backbone-game-engine/super-mario-bros/offline.appcache
Application Cache Checking event
Application Cache NoUpdate event

Manifest File

Using an Application Cache is dead simple. First you must add the manifest attribute to your HTML tag. It points to the manifest file:

<!doctype html>
<html manifest="offline.appcache">
    <head>
Second, create the manifest file. It contains files that must be cached. For example here is the offline.appcache:

CACHE MANIFEST
# Version 0.11 (c) 2014-2015 Martin Drapeau

../3rd/qtree.js
../3rd/underscore.js
../3rd/backbone.native.js
../3rd/backbone.js

../src/input.js
../src/shapes.js
../src/core.js
../src/world.js
../src/local-storage.js
../src/camera.js
../src/editor.js
../src/hero.js

mario.js
tiles.js
artifacts.js
enemies.js
display.js
level_1-1.js
main.js

super-mario-2x.png
super-mario-tiles-2x.png
super-mario-enemies-2x.png
icons.png

If you have server requests, you can add a NETWORK section. Consult the docs for details.

Fianally, the comment with Version 0.11 is important. When a new version of Super Mario Bros level 1-1 is released, the version number is increased to force the browser to reload the files. It will also trigger an updateready event which gets captured to show a download button. That informs the user a new version is ready to be downloaded. Clicking on that button simply refreshes the browser to reload the new version.

Persistence

Backbone offers RESTful persistence via Backbone.sync. Models have methods fetch and save to retrieve/send the model/collection JSONified data to/from the server. As such, you can easily implement server-side persistence using well established RESTful standards.

In our Super Mario Bros example, we use local storage instead. This is done by overriding Backbone.World methods save and fetch. See file src/local-storage.js for details.

Performance and Debugging

HTML5 canvas has come a long way in terms on performance. Browser implementations of canvas now offer impressive performance. On mobile, Apple leads the pack being able to sustain a 60fps for the Super Mario Bros example. However on Android, frame rates drop fast to the 30s when the background needs to be redrawn. On Surface performance seems good on newer models, however on first generation RT models, as slow as on Android tablets.

This being said, there are things you can do to ensure the best performance.

Keep cycle time below 16ms

That is the time you have between redraws, 60 times a second. The Backbone.Engine will report the frame rate (fps), and cycle time (ct) if you add and attach a Backbone.DebugPanel. Make sure to use it. If you see fps go down while ct goes up, then your update and draw times must be too long. You can time the update time to pinpoint the issue.

Play well with Javascript Garbage Collection

You can't avoid it. You will leak memory. Every call made by requestAnimationFrame creates a function scope. It does so 60 times a second and it will need to be garbage collected. The browser will pause to collect garbage.

You can however control the leakage rate. Try to create objects upfront, and pool resources as much as possible. That's why sprite sheets are shared among sprites. If you ever see that your game jerks, at an even interval (i.e. every 30s), then you are probably being hit by the garbage collector recuperating large amounts of memory (>10MB).

You can use the Timeline tool in Chrome/Safari Developer Tools to identify this. Record a session and once done, you can apply a filter gc to filter on garbage collection events. You will notice they are at evenly spaced intervals. On my machine, for Super Mario Bros, 3.5MB is collected every 4s on average. There is no jerk. No jerk on a tablet means healthy memory management.

Some further references and good resources on performance:

Publishing your Game

On the Web

If you forked this repo, your game is already published on the web on your Github page under [username].github.io/backbone-game-engine.

If you own an iPad or iPhone, you can add it to the home screen as a Web app. It will open in full-screen and if you've implemented an Application Cache, it will work offline too.

On iOS and Android

Backbone Game Engine was built to run in CocoonJS canvas+. You can try out Super Mario Bros level 1-1 in the CocoonJS launcher by pointing to the zip file at this URL: http://martindrapeau.github.io/cocoon-mario/cocoon-mario.zip.

http://martindrapeau.github.io/cocoon-mario/cocoon-mario.zip

Checkout the Github repo cocoon-mario. It can be used as the basis for your own native game on iOS or Android.

Change Log

0.40 - TBD

  • Upcoming release to include bug fixes, improvements and new features to come following the release to iOS of Ludo's Quest.

0.30 - 2015-03-22

  • Backbone.Element - a rudimentary DOM element with image, text and animations.
  • Backbone.World now uses a QuadTree for collision detection.
  • Removed dependence on hammer.js. Backbone.Engine now triggers tap and key events.
  • Complete rewrite of Backbone.Input. Removed its pause button.
  • Complete rewrite of Backbone.Character.
  • Complete rewrite of Backbone.Hero.
  • Backbone.Editor now resizes sprites to fit in the specified tileWidth and tileHeight.
  • Rewrite of adjustViewport global function to work cross-device.
  • Official support of CocoonJS canvas+.

0.21 - 2015-02-06

  • Sprite padding
  • More efficient gamepad drawing
  • Editor: paging, shrink large sprites, highlight tiles
  • World: z-index, tap event,key event, fixed background image, improved sprite lookup, bug fixes

0.20 - 2014-12-31

Major improvements including:

  • Performance improvements.
  • Fast sprite lookup.
  • Faster dynamic and static drawing.
  • Efficient collision detection.
  • Character and hero knockout and dying.
  • Bug fixes.

0.11 - 2014-11-12

Adjust viewport on orientation change, and center canvas.

0.10 - 2014-05-19

Initial release.