I want to share a technique that I came up with while developing my Cockpit compo entry. I first intended it as a simple way to program enemy behaviors, but I soon realized that it's quite a powerful concept and subsequently also used it to script other sequences like the endgame. Basically, this is a variation on the component-based architectures that I have been quite interested in recently, but on a smaller scale. I'm certainly not the first one to come up with this, but I thought someone might find it interesting. Above all, I just think it's a beautiful concept.
As this turned out pretty long, I'll briefly summarize what I'm doing: we have a dumb actor class. Its possible actions are represented by various behavior objects. By composing these behaviors in sequence and/or in parallel, we can build up interesting behaviors from simple building blocks.
Let's say you have a basic entity class, call it Actor or Entity or whatever you like to call such things, that's pretty dumb: it knows how to move itself around the game world, but it doesn't have a brain of its own and just follows outside commands, so to speak. If you are familiar with MVC, this corresponds to the Model.
For the sake of example, I will use some code from my compo game, and it just so happens that my base entity class there was called Vessel. (As an aside, all code here is in D; if you know C++ or Java, you shouldn't have any trouble following it. Just consider it as pseudocode and you should be fine.) The Vessel class is a very lightweight class that has methods like
moveForward(float distance) and
turnTowards(Vector2 target, float maxTurn) that basically do what you would expect them to do.
In order to encapsulate a simple action that such an entity can perform, we introduce the following simple interface (or abstract base class, as you wish):
interface IBehavior
{
void start(Vessel);
bool update(Vessel, float dt);
}
The idea is that when a behavior is activated for a vessel, its start() method is called with that vessel as a parameter. Similarly, in every frame, its update() method is called with the vessel and the delta time since the last update. If update() returns true, that means the behavior has finished whatever it was trying to do.
Conceivably you could make the vessel a member of the behavior, but my aim here was to keep the behavior objects as lightweight as possible.
Let's look at a simple behavior from my game that makes one Vessel follow another one:
class FollowBehavior : IBehavior
{
Vessel target;
this(Vessel target)
{
this.target = target;
}
void start(Vessel)
{
}
bool update(Vessel v, float dt)
{
v.turnTowards(target.pos, dt * v.turnSpeed);
v.moveForward(dt * v.moveSpeed);
return false; // never done
}
}
It's very simple: in every frame, the vessel turns a bit towards its target and then moves forward a bit. This particular implementation just keeps following forever, though it would be easy to return true from update() once we are near enough our target and thus stop following. No code is required in start() for this example.
Other behaviors you can implement in a similar way are a WaitBehavior which just pauses for a given amount of time, a MoveStraightBehavior which moves the vessel towards a fixed destination position, and so on.
The real magic starts when you realize that you can compose these behaviors in interesting ways. Let's say we want a vessel to execute a list of behaviors in sequence, for instance to follow a path given by waypoints. That's very easy: we implement a
SequentialBehavior which is an IBehavior itself, but also contains a list of IBehaviors as well as the index of the currently active one. Whenever it needs to update, it just calls the current behavior's update method. If that reports that it is finished, the SequentialBehavior starts the next behavior in its list and will henceforth update that one. You can also make the SequentialBehavior loop back to the beginning when it's done with its list. Similarly, you can implement a PingPongBehavior which executes its list of behaviors in a forth-and-back manner, which is nice for setting up a patrol route which is not cyclical.
The next interesting realization is that you may want an entity to execute several behaviors in parallel. Say you have an enemy that patrols along a given route, but at the same time it needs to watch out for the player and fire a shot at her when spotted.
Well, that's easy too: we implement a
ParallelBehavior which again is an IBehavior containing a list of IBehaviors, but in its update() method calls the update() method on every one of its children.
Using these composed behaviors, we can construct trees of behaviors which may result in quite complex scripted sequences with a minimum of fuss. Check out this actual (slightly modified) code example from my game which constructs a behavior for a missile:
IBehavior createMissileBehavior(Vessel target, float damage)
{
return new ParallelBehavior([
cast(IBehavior)new FollowBehavior(target),
new SequentialBehavior([
cast(IBehavior)new WallCollisionBehavior,
new SelfDestructBehavior
], false),
new SequentialBehavior([
cast(IBehavior)new VesselCollisionBehavior(target),
new DamageVesselBehavior(target, damage),
new SelfDestructBehavior
], false)
]);
}
(You can disregards the casts, they are needed because of the way the D type system works.) This piece of code sets up a behavior which consists of three branches executing in parallel. The first one makes the missile follow its designated target at all times. The second one checks if a collision with a wall has occured, and if so, self-destructs the missile. Note that WallCollisionBehavior just runs until a collision with a wall has occurred, thus acting as a "gate" for the behavior that comes after it. Finally, the third branch checks if a collision with the designated target has occurred, and if so, damages the target and then self-destructs the missile.
I think it's quite beautiful how complex actions are built up from atomic ones, as well as how the tree structure of the behavior is actually visible in the source code through indentation. (As an aside, this is an example of how D's array literals can make code much more readable.)
In my game, I then implemented a Controller class which holds an entity and its associated behavior and updates every frame accordingly. That's pretty trivial, so I spare you the details.
All in all, I have found that this is a much simpler way of scripting entities and gameplay events than embedding a full-blown scripting language, while retaining many of the advantages. You could also load these trees of events from some text file, thus creating a domain-specific language for programming enemies, or, if you are already using a scripting language, expose an interface to these behaviors to it.
Another interesting aspect of this method is that you can change an entity's behavior at runtime very easily. Say you have a bunch of enemies which have two states, normal and alerted. Just give them their normal behavior tree by default, and when the player does something to alert them, switch out their behavior tree for the alerted one which could make them go actively searching for the player.
I've been wondering whether there is a standard name for the pattern I've described here; I guess the Command pattern is somewhat related, but this here is more closely matched to the requirements of simulating game entities. I should also mention that I may have been subconsciously influenced by some reading I did
on the AIGameDev.com site, though my approach is simpler and I only realized the similarity when I started typing this up.
So, for those who actually bothered reading all this: does this make sense? Have you used something similar? Will this hold up well in more complicated situations? It certainly worked very well for my simple game, and I plan to use this approach again.