Machine.js lets you use a hierarchical state machine to control a JavaScript object.
1. Define a behaviour tree as JSON.
{
identifier: "idle", strategy: "prioritised",
children: [
{
identifier: "photosynthesise", strategy: "sequential",
children: [
{ identifier: "makeEnergy" },
{ identifier: "grow" },
{ identifier: "emitOxygen" },
]
},
{ identifier: "gatherSun" },
{ identifier: "gatherWater" },
]
};
2. For each leaf state, define a function that enacts the behaviour for that state.
So, for the gatherSun
state, define a function that gathers sun.
3. For each state, define a can function that returns true if the
actor may move to that state. So, for the gatherSun
state,
define a function canGatherSun
that returns true if the sun is out.
The code is open source, under the MIT licence. It uses Base.js by Dean Edwards.
A tiny ecosystem is living right now in your browser debug console. Rain falls, the sun shines. An oak tree absorbs water and sunlight, makes energy and grows. Open your console and see. View source to see the code.
A behaviour tree is a normal state machine, except the states are connected in a hierarchical structure. At fork states, the actor controlled by the tree does nothing. A fork state simply leads into one of a set of subsequent states (its children). At leaf states - states with no children - action is taken by the actor.
Movement from state to state is controlled by two mechanisms: strategies and can functions. Each fork state has a strategy that determines which child state to move to. Each state has a can function that returns true if the actor is allowed to move to that state.
You will need to import machine.js and base.js. If you are running your behaviour tree in a web page, you might do that like this:
This is the object that will be controlled by the behaviour tree. Any normal JavaScript object will do.
Write some JSON that defines your actor's behaviour tree.
For each leaf state in your behaviour tree, define a synonymous function.
This will be run once when the actor moves to the corresponding state. For each state
(leaf or fork),
also define a function called canNameOfState
. This should return true if
the actor is allowed to move to the corresponding state.
For example, if your leaf state is called kiss
, you would define a
function
called kiss
that does the kissing, and a function called
canKiss
that returns true if kissing is allowed. Put all these functions into an object:
Instantiate your actor. Make an instance of Machine. Use the instance of machine
to generate a behaviour tree, passing in the tree JSON, your actor and the object with all the state functions. Repeatedly
call tick()
on the current state to get the next state.
States are defined as JSON hashes. All must have an identifier
property that is
set to the name of the state. Fork states also have a strategy
property and a children
property that is set to a list of subsequent states.
Each state in the machine has a corresponding can function
called canNameOfState
.
This returns true if the actor can move to this state. You can omit
this function if the state is always permissable.
Fork states are those that have children. A fork state has a strategy assigned to it that determines which state the machine should subsequently move to. Machine.js supports two strategies:
Call the can function of the first child. If it returns true, move to that child state. If it does not, try the next child, and so on. Once a child has been run, or the final child has been tested and returns false, go back to the parent fork state.
Go through all the child states, executing each one for which the corresponding can function returns true. Once all the children have been processed, move back to the parent fork state.
Sometimes, you want the actor being controlled by the behaviour tree to respond to events. For example, the player might give a soldier orders to return to base.
You can do this with warp()
. First, define your
behaviour tree so that it has a special return to base branch that
is a direct descendent of the root node:
Next, define a function on the actor that can be called to invoke the
return to base order.
In it, instead of using the tick()
function to get the next
state, you use warp()
and pass in the identifier of the
return to base state:
A word of warning. The warp()
function is a violation
of the deterministic beauty of a behaviour tree. Use it sparingly.