Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411490 Posts in 69371 Topics- by 58428 Members - Latest Member: shelton786

April 24, 2024, 09:29:54 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Scripting game actors by composable behaviors
Pages: [1] 2
Print
Author Topic: Scripting game actors by composable behaviors  (Read 5766 times)
muku
Level 10
*****


View Profile
« on: April 13, 2009, 06:18:32 AM »

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):

Code:
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:

Code:
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:

Code:
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.
Logged
Wishfallen
Level 0
**


Flat-based!


View Profile
« Reply #1 on: April 13, 2009, 07:08:00 AM »

Awesome! I found this when I was looking up how to do scripted movements and things too Smiley

But shouldn't this be in the tutorials section?
Logged

Signature nullified!
astrospoon
Level 4
****


Andy Hull


View Profile WWW
« Reply #2 on: April 13, 2009, 07:32:01 AM »

This is indeed a very useful setup to have. The enemy behavior in Nun N Gun works almost just like this. We would load in "Script" files for an enemies behavior, and it really was just a pattern of behaviors that could be loaded in from a give set. (Like: Walk, Pause, Shoot, Jump, etc..) I think we had different "triggers" too that you could define behaviors for. So there was an Idle pattern, a "walked to platform edge" pattern, etc, so that it could switch patterns based on what was happening to a given entity.

If your game can support all of the needed enemy functions with a simple setup like this (losing flexibility on the scripting end) then go for it! But the tough part is to know when things are complicated enough to switch over to a full blow scripting language. Because switching over mid-way is a pain, and if you need it really powerful, "rolling your own" becomes a pretty poor choice.
Logged

muku
Level 10
*****


View Profile
« Reply #3 on: April 13, 2009, 07:51:07 AM »

I was considering putting it in the tutorial section, but I wasn't sure whether it was too specific to make much sense there. But since it seems that this is actually a more wide-spread technique than I thought, I guess it would be good to have it in that section. If a mod wants to move it over, I'd be just fine with that.

As for knowing when it's best to switch to something more complex like a scripting language; it's a good question. I have thought about it a bit, and I think one of the drawbacks of this method is that it's rather hard to have the equivalent of local variables. Say an enemy needs to be able to chase several entities and keep track of which one it's currently going for: this would be harder to do within this framework. You could introduce a per-entity registry of name/value pairs to pull it off, but then you just may have reached the tipping point of complexity where this approach no longer makes sense.

On the other hand, most basic constructs of scripting languages can be quite well reproduced: you can have loops, you can have something akin to an if-clause, you can even have something like function calls by keeping some frequently-used behavior trees in a common location and letting entities refer to them. (Though you'd have to think about what state behaviors may have and where they store it.) It's interesting.
Logged
Drew
Level 0
***


Hooray


View Profile
« Reply #4 on: April 13, 2009, 07:55:42 AM »

I'm using something like this for my current project.  I have stock collision behaviors that I can assign to objects. 

I have pairs of properties like Heavy/Crushable, Fire/Flammable, Deadly/Vulnerable, etc and when objects with properties of one pair collide, certain things occur.  I can assign these properties to objects and they will automatically respond to collisions properly.  It's pretty neat because I can add or remove properties at runtime.  You have to define an order of precedence, but it's not so bad in practice.

The properties also have an update function like your system, but I haven't actually used it for anything yet.

Logged
David Pittman
Level 2
**


MAEK GAEM


View Profile WWW
« Reply #5 on: April 13, 2009, 08:35:22 AM »

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.

I was going to say that this concept sounded very similar to AlexC's behavior trees, and then I saw this.

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.

I'm using behavior trees as documented at AIGameDev for the AIs in my game, and it's working well so far. I haven't quite figured out the optimal granularity of tasks--it seems like I'm implementing many leaf tasks that only do one very small thing, but that also seems like the best way to benefit from code reuse in this paradigm. There's a lot of really nifty things I'm finding I can do, like actively monitoring a condition with a parallel (so that the AI bails out of its main task if the parallel condition task fails).

The only thing I'm doing notably different than you is that I actually define the structure of the tree in an INI file and build it at runtime based on that definition instead of implementing specific functions like createMissileBehavior(). I lose any context in the code for the structure of the tree, but I can rearrange parts or change task parameters without recompiling. Also, I use a task scheduler like AlexC describes--I'm not clear from reading your post whether you do that or simply evaluate the tree from the root every tick.

In any case, cheers on an excellent post, I think you could definitely make a tutorial out of this.
Logged

muku
Level 10
*****


View Profile
« Reply #6 on: April 13, 2009, 10:28:27 AM »

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.

I was going to say that this concept sounded very similar to AlexC's behavior trees, and then I saw this.

The mind is a funny place. I investigated this stuff a couple of months back and found it very intriguing, but never had the opportunity to put it into practice. Now, when I was working on Vessel-IV, I can't for the life of me recall consciously thinking back to this method; I really had the impression (delusion, probably) that I was developing this from scratch in my mind. Only afterwards did the striking resemblance hit me. But you can never know how stuff in your brain influences each other...

Quote
I'm using behavior trees as documented at AIGameDev for the AIs in my game, and it's working well so far. I haven't quite figured out the optimal granularity of tasks--it seems like I'm implementing many leaf tasks that only do one very small thing, but that also seems like the best way to benefit from code reuse in this paradigm. There's a lot of really nifty things I'm finding I can do, like actively monitoring a condition with a parallel (so that the AI bails out of its main task if the parallel condition task fails).

So, in your model, does a parallel end when either of its branches ends? I had this option as a boolean parameter to the constructor, but I didn't make enough use of it to find out what works better. By default, my parallels ended only when all of their branches were finished.

Quote
The only thing I'm doing notably different than you is that I actually define the structure of the tree in an INI file and build it at runtime based on that definition instead of implementing specific functions like createMissileBehavior(). I lose any context in the code for the structure of the tree, but I can rearrange parts or change task parameters without recompiling.

That's most probably what I would have done for a larger project or a separate game engine. Interestingly, one of the first reasons I implemented this at all was that it promised easy experimentation in a small project developed under a strict competition deadline, without having to implement a separate file parser or scripting language. It worked very well, I could toss new behaviors together in a matter of minutes.

Quote
Also, I use a task scheduler like AlexC describes--I'm not clear from reading your post whether you do that or simply evaluate the tree from the root every tick.

I do evaluate it every tick. My controller class looks more or less like this:
Code:
class EnemyController
{
Vessel vessel;
private IBehavior behavior;

this(Vessel v)
{
vessel = v;
}

void setBehavior(IBehavior b)
{
behavior = b;
behavior.start(vessel);
}

void update(float dt)
{
if (behavior)
behavior.update(vessel, dt);
}
}
As I said, simplicity and ease of implementation was really the main concern here. Also, it's a small, simple game, and performance really wasn't a problem. Besides, my trees were never very deep, two or three levels at the most.

I do recall reading a bit about schedulers, but I can't fully recall how that worked. Is there a blog article on the AIGameDev site explaining it, or could you briefly summarize what you do?

Thanks for the insightful comments!
« Last Edit: April 13, 2009, 10:31:28 AM by muku » Logged
David Pittman
Level 2
**


MAEK GAEM


View Profile WWW
« Reply #7 on: April 13, 2009, 10:56:50 AM »

So, in your model, does a parallel end when either of its branches ends? I had this option as a boolean parameter to the constructor, but I didn't make enough use of it to find out what works better. By default, my parallels ended only when all of their branches were finished.

Kind of. I have two ways tasks can end (success or failure), so the behavior of my composite tasks depends on the result of its children. My default behavior for a Parallel is to fail as soon as any one of its subtasks fail, but to wait to succeed until all of its subtasks succeed. Later, I found some places where I needed different behavior, and so I went and made it completely generalized, where I can specify the number of failures it accepts before it returns a failure or the number of successes it accepts before it returns success. (Likewise, my Sequence fails as soon as one of its subtasks fails.)

I do recall reading a bit about schedulers, but I can't fully recall how that worked. Is there a blog article on the AIGameDev site explaining it, or could you briefly summarize what you do?

The basic version (which is how I've implemented it) is described here. It's just a list of tasks that are run each tick. A sequence would push its child tasks onto the scheduler instead of explicitly calling update() on them, for example. The main benefit of doing it this way is that you don't have to run all your logic from the root node every frame (so precondition tasks that have already returned true don't need to be executed repeatedly). I guess you also avoid a deep callstack, but that wouldn't be an issue except for very complex trees. The downside is that it becomes more difficult to get the result of the update() call where you need it, so you have to register composite tasks as listeners and notify them when their child tasks finish. Frankly, unless your behavior trees get really costly, there's probably not a compelling reason to switch to using a scheduler.
« Last Edit: April 13, 2009, 11:14:04 AM by David Pittman » Logged

agj
Level 10
*****



View Profile WWW
« Reply #8 on: April 13, 2009, 08:37:12 PM »

This is very nice! I had been wondering lately what I would need to do to create complex animations purely in code for games when I'm not using Flash, and this sounds like a reasonably simple way of doing it.
Logged

minasss
Level 0
***



View Profile WWW
« Reply #9 on: April 14, 2009, 05:08:11 AM »

I've used the same method in a simple flash game and I have to admit that this is very usefull
you can stack or parellize behaviors and with a few lines of code it is possible to create very complex behaviors...in addiction it is very simple to distribuite the computation to different cpu cores with this approach
Logged
nihilocrat
Level 10
*****


Full of stars.


View Profile WWW
« Reply #10 on: April 14, 2009, 04:58:31 PM »

A bit off-topic, but it's interesting to see someone make a game in D. I checked it out several years ago but didn't get very far struggling with getting SDL to work with it. How did it treat you in comparison to C++ or Java?

Panda3D does something similar to this, but it's more intended as a sequential/parallel sequence of scenegraph actions, rather than more generic Model ones.

This, along with the Observer model (i.e. frequent use of game events to get many unrelated components to do something at the same time) and a few other things are really the cornerstone of flexible game engine code. It's sad to see there is no engine or library about that addresses merely these concerns and lets you plug it into whatever other code you want to use it with.

Hmm...
« Last Edit: April 14, 2009, 05:08:24 PM by nihilocrat » Logged

muku
Level 10
*****


View Profile
« Reply #11 on: April 14, 2009, 11:14:34 PM »

A bit off-topic, but it's interesting to see someone make a game in D. I checked it out several years ago but didn't get very far struggling with getting SDL to work with it. How did it treat you in comparison to C++ or Java?

All my recent games (in particular, all my compo entries) were done in D. I'll never go back to C++ if I can help it, mark my words. It's just so much more convenient and productive. I wouldn't want to miss features like garbage collection, a foreach loop, delegates, a module system, auto-typed variables, nested functions, function literals, built-in associative arrays and so on anymore. Its meta-programming system is more powerful and at the same time much more readable than that of C++. And its compilation times are so much quicker than C++, it's not even funny. All in all I'd say it's (at least) almost as productive as Python while having almost the performance of C++, which is a pretty damn sweet spot for me.

Sure there's still some rough spots, the language isn't as mature yet as older ones. But now that the original compiler's source has been opened and there are actually multiple alternative compiler implementations, I'm pretty sure that will change rapidly.

By the way, getting SDL to work is trivial these days: Just install Derelict and you have SDL, OpenGL, FreeType and lots of other game-relevant libraries at your fingertips.

Sorry for raving, but sometimes I just feel compelled to spread the word Wink If you're interested, my first compo game's source code is available (see the thread or my blog), and I'll probably release the source for Vessel-IV shortly.


Quote
This, along with the Observer model (i.e. frequent use of game events to get many unrelated components to do something at the same time) and a few other things are really the cornerstone of flexible game engine code. It's sad to see there is no engine or library about that addresses merely these concerns and lets you plug it into whatever other code you want to use it with.

Hmm...

Maybe we should go bug Alec about his Monocle engine plans... Wink
Logged
raigan
Level 5
*****


View Profile
« Reply #12 on: April 15, 2009, 06:25:19 AM »

slightly more off-topic: are you using D for cross-platform games, or just PC? I'd like to know how easy Mac/Linux support is, GDC stalled at v0.2 isn't really a big confidence booster Sad
Logged
muku
Level 10
*****


View Profile
« Reply #13 on: April 15, 2009, 06:33:01 AM »

slightly more off-topic: are you using D for cross-platform games, or just PC? I'd like to know how easy Mac/Linux support is, GDC stalled at v0.2 isn't really a big confidence booster Sad

Unfortunately I've never had the opportunity to try and compile my games on Linux or Mac. Whatever you do, though, don't use GDC, it's virtually unmaintained. DMD (the original Digital Mars compiler) has a Linux version which seems to be fine. Since 1.040 there's a Mac port too, but I don't know whether it's usable yet, and I don't have a Mac anyway.

Also, LDC (the LLVM-based compiler) is making huge progress and might soon be a viable option on several platforms.

I should really try compiling some games on Linux soon Undecided
Logged
raigan
Level 5
*****


View Profile
« Reply #14 on: April 15, 2009, 07:49:49 AM »

awesome, thanks! I didn't know DMD supported mac.. maybe we can finally use D!!
Logged
Will Vale
Level 4
****



View Profile WWW
« Reply #15 on: April 19, 2009, 04:13:43 PM »

Back on topic, I really like this idea, and your presentation of it is much more succinct than the talk linked on AIGameDev. I think I'll give it a try when I get to looking at ship AI for Sublight.

Thanks for taking the time to describe a neat system.

Logged
Robotacon
Pixelhead
Level 3
******


Story mode


View Profile
« Reply #16 on: April 19, 2009, 10:50:44 PM »

Awesome thread. AIGameDev looks like a great site.
Now I'm stoked about jumping into enemy AI.
Logged
TeaAndBiscuits
Level 0
***


View Profile
« Reply #17 on: April 20, 2009, 01:03:54 AM »

I love your system! I think the original point of a behaviour tree is to make AI a little easier by breaking stuff down into a classic classification problem. Often Behaviour trees are implemented with so many functions and conditions that this simplicity is lost.

Bravo! I am about to do some test AI for my game and was thinking about using behaviour trees. I think you may have convinced me.
  Beer!

Logged
muku
Level 10
*****


View Profile
« Reply #18 on: April 20, 2009, 01:22:18 AM »

Thanks guys. If anyone wants to see how it all fits together in a real-world example: I have now posted the full source code for Vessel-IV in my compo thread (with the usual caveat that it's code from a competition, so it may not be perfectly clean). The file you should be most interested in is called Behavior.d, if I recall correctly.
Logged
mirosurabu
Guest
« Reply #19 on: April 25, 2009, 06:26:25 AM »

Interesting approach which sounds similar to data-driven component-based model. Sequencer component is very neat thing, as it helps to build complex behaviors by sequencing primitive ones. It's even more sweet when components are sequenced using specialized scripting language.
Logged
Pages: [1] 2
Print
Jump to:  

Theme orange-lt created by panic