Andro.js
Today, I am releasing Andro.js, a JavaScript library that helps you compose objects from fragments of behaviour.
The problem
Over the last few months, I have been writing a new JavaScript game. It is a 2D platformer that involves leaping about, stabbing, shooting and blowing stuff up - that much is certain. But it also has a puzzle element that is almost totally undefined.
When I began working on this puzzle element, I experimented with objects with different capabilities: objects that would light up, or play a sound, or leap into the air. Soon, it became clear that I needed a way to give a single object multiple capabilities. Then, I needed a way for these capabilities to interact, like: make this cube light up and leap into the air when it is touched by the player.
A common approach to defining the behaviour of game objects is to use components. These are chunks of code that are installed in the object, each to handle a separate task: artificial intelligence, collision detection and so forth. When the game loop ticks, each game object gets an update call and, in turn, calls an update function on each of its components, so that they can do their work. This is nice because you separate concerns, which means your code is easier to read, easier to reuse, and easier to reason about. Further, the components have total control over their game objects, so you can still do powerful stuff.
I thought about using a component-based approach, but it seemed like it didn’t really fit the way the game objects actually worked. If I were to program a leap into the air capability, what type of component would it be? Logic? And if I wanted my object to only leap when it was touched by the player, how would I organise the code then? Could I shove the leaping and touch detection into some sort of vague Collision Detection component? Or would components somehow talk to one another? I wanted to organise the code by its function, rather than its approach.
A solution
I needed to be able to write very malleable, tightly focused fragments of code that could be reused and recombined as I changed my mind, and I was willing to sacrifice some code clarity to get this.
It seemed that mixins were a promising approach. A mixin is a bundle of attributes and functions that are written onto an object, thus conferring a set of capabilities. A traditional mixin puts these attributes and functions into the namespace of the object. Any clashes: boom. That seemed too dangerous to me. I decided to put each mixin in its own namespace.
Another potential danger was how these mixins would interact. Their reusability would be limited if they had to call each other’s functions by name. Further, direct interaction would make things into a wild free-for-all that was impossible to reason about, with behaviours walking into rooms and issuing orders and walking out to cause chaos elsewhere. I quelled the potential chaos (somewhat) by deciding that the behaviours should only interact via events.
Finally, I had a plan: mixins in their own namespaces communicating via an event emitter. I wrote Andro.js to enact this plan.
An example of Andro.js in use
I require the andro.js
file. I instantiate Andro
. I define the game object as a constructor called Cube
. It has a touch
function that, when called, emits a touch
event to all behaviours (mixins) attached to the cube.
var Andro = require('andro').Andro;
var andro = new Andro();
function Cube {
this.touch = function(contact) {
andro.eventer(this).emit("touch", contact);
};
};
I define the firstTouchBehaviour
mixin. This binds to the touch
events emitted by its owner and keeps track of the number of things currently in contact. When the owner goes from being untouched to being touched, firstTouchBehaviour
emits a FirstTouch:newlyBeingTouched
event.
var firstTouchBehaviour = {
touchCount: 0,
setup: function(owner) {
andro.eventer(owner).bind(this, "touch", function(contact) {
if(contact === "added") {
if(this.touchCount === 0) {
andro.eventer(owner).emit("FirstTouch:newlyBeingTouched");
}
this.touchCount++;
} else if(contact === "removed") {
this.touchCount--;
}
});
}
};
I define soundBehaviour
. This binds to the FirstTouch:newlyBeingTouched
event. Each time this event occurs, soundBehaviour
makes a noise: “Rarrrrrwwwwwwwwwwwwwwww”.
var soundBehaviour = {
setup: function(owner) {
andro.eventer(owner).bind(this, "FirstTouch:newlyBeingTouched", function() {
console.log("Rarrrrrwwwwwwwwwwwwwwww");
});
}
};
I now put everything together. I instantiate cube
, set it up for use with Andro.js and augment it with firstTouchBehaviour
and soundBehaviour
. Finally, I simulate two touches upon the cube. On the first, it roars. On the second, it does not.
var cube = new Cube();
andro.setup(cube);
andro.augment(cube, firstTouchBehaviour);
andro.augment(cube, soundBehaviour);
cube.touch("added"); // rarrrww
cube.touch("added"); // silence
Limiting mentalness
The approach Andro.js takes exchanges a measure of one’s ability to reason about the code for a measure of malleability. Though the modes of expression are constrained to events and mixins, you can still express yourself into one hell of a mess.
As I’ve worked on my new game, I’ve discovered some guidelines that avoid most of the mess. First, all my behaviours have only one responsibility. Second, most either emit information about state, or act upon information received. Third, behaviours do not meddle with the state of their owner objects.
Was this all a mistake, my beloved?
It’s hard to express something at the same time as you’re figuring out what you’re trying to say. Andro.js has enabled me to design some complex game objects. For example, one type of object can simultaneously flash, be a note in a step sequencer, and play a sound when struck that is conferred to it by a nearby object. The code for the behaviours is clean and readable. But, I have rewritten it four times. So, I wonder if there are superior approaches.
I keep on thinking about Arc. Paul Graham has said it is a language designed for exploratory programming. And I keep on thinking about GOOL, the dialect of LISP that Andy Gavin invented to script the game objects in Crash Bandicoot.
These are both languages designed to solve the problem I have been grappling with. Is the logic of game objects in fact a microcosm of all of programming, as complex a problem as any that might be solved by a programming language? Or is it usually a limited problem that is easier to solve when you limit the tools at your disposal? And if that is so, is mixins+events a good approach, or is there a better one?
Subscribe to my newsletter to hear about my latest work