Event-Handling Strategies for Juggling Purposeful Behaviors

Assume you have a long-term AI behavior controlling a character in a game. It could be implemented as a script, a behavior tree, or any other sequence of actions to follow. How do you deal with events happening in the world in a way that’s coherent with this active behavior?

The AI logic needs to make two major decisions when receiving incoming events from the wold. It must work out:

  1. Whether to terminate the running behaviors or just suspend them.

  2. If new behaviors should start, or resume existing ones from suspension.

Obviously, in special cases, events should trigger both operations. However, you can simplify the AI tremendously by separating the logic into two types of event handlers.

Handling Broken Assumptions

Generally when you build AI, it’s useful to make behaviors goal-oriented, so they each solve a specific problem. As you design behaviors to reach their goals, you’ll find yourself relying on certain conditions about the context they run in.

Then, to make sure that the AI responds to events in a logical way, you need to capture these assumptions using some custom logic. For example:

  • In a script, you could monitor your assumptions by writing a custom event handler for all the facts that could change and break the behavior.

  • In a behavior tree, you would use parallel conditions high-up in the tree to check those assumptions regularly and bail out if necessary.

Once each behavior can monitor its own pre-conditions and make sure it terminates immediately once its assumptions are wrong, you can start thinking about handling other events.

Responding to New Opportunities

To react to potential opportunities, you need a global handler to take care of all the events (including those which also invalidate certain assumptions). This type of handler is much harder to implement, as there are many more cases to handle, but you can keep things simple by:

  • Only deciding what new behaviors to start.

  • Establishing priorities of the new behaviors.

  • Letting other behaviors suspend or terminate themselves.

Basically, this approach makes it a bit easier to deal with the huge matrix of possible choices that grows with the number of active behaviors. Instead, you just select new behaviors, give them a priority, and if there’s a clash over resources, let other behaviors bail out if their assumptions are broken.

Conclusion

It’s hard to capture all the subtleties in this problem without writing a whole dissertation on the subject, but at least now you have the basics of a solid system for handling events. It’s best to separate event handlers into two groups: one for monitoring running behaviors and shutting them down if assumptions are broken, and the other as a global event handler for starting new behaviors and working out their relative priorities.

Do you use any particular tricks for handling events in the context of purposeful behaviors?

9 Comments ↓

#1 Ian Morrison on 11.12.07 at 2:15 am

I am going to reread this article when my brain isn’t primed to explode from dealing with deadlines. The idea of a custom event handler inside actions is neat, though..

#2 Angel Cabrero on 11.13.07 at 10:07 am

Hi,

Very interesting subject, in my current work i am dealing with these difficulties. I am currently working with scripted behavior trees, to be used by designers (and so they need to be very simple, robust and intuitive).

The solution I am trying right now for event handling is based on parallel branches that can check for conditions.

Additionaly, each node un the tree can define a custom ‘context’ proterty, defined by the user.

For the moment, the only response posible is to break the tree execution, but there are 2 options for that:
+ Terminate the entire script
+ Terminate a concrete context.

The nodes terminated return a special ‘aborted’ termination code, so as there parents can threat the case in any special fashion…, and that is another interesting subject to talk about :)

My next step will be implement a node action that can run another AI script tree from inside the current script, to let for some modularity and extensibility (and these came along with a lot of additional difficulties on resource conflicts and priorities… :)

Best regards,
Angel.

#3 Bjoern Knafla on 02.18.08 at 11:14 am

Angel, your “context” gets me thinking if a programmer should be able to tag certain sub-trees with a symbol or a tag or an enumeration. Is this what you meant?

A new opportunity handler could possible be implemented like an array of conditions as childrens of a parallel node (like Alex describes all the time). However if a condition is triggered it gets sort of a introspection of the state of the tree through the currently active symbols - and might therefore be able to decide what to do next in a more appropriate matter.

Where does this lead? While currently the traversal state of a tree is implicitly represented by the way it is executed. By adding symbols or contexts or tags (or however you want to name them) there is also an external description of the tree state which can be used for introspection (a meta view).

I am not sure how much this would complicate the implementation or even the use of a BT. There certainly is a balance to strike between flexibility and power and ease of use because of well known limits and patterns of using a BT. Though only experimentation will show where to strike this balance ;-)

Cheers,
Bjoern

#4 Angel Cabrero on 02.18.08 at 11:51 am

Hi BJoern,

Yes, the “context” mean more or less what you described. It is a simple idea, meant to be used for simple cases by designers (or programmers).
They can be writing an script for an agent doing different things, and they can mark de meaning of each part o the script with a context or tag or whatever. So, they can effectively have a parallel branch checking for conditions and if any condition is raised, then they could look for the current context active to decide witch response is suitable for the current three state.

But there is also another functionality, more simple, with no parallel execution: you can have a selector node, to decide between different rutines, each rutine can be tagged with a differente ‘context’, and while you are running that rutine, y you enconter any problem during the execution, you can decide to terminate only your context, and let the selector node continue executing the next available rutine. So in this case, we are using the ‘context’ as a simple mean to reference ancestor nodes to abort then directly.

Hope this explanation helps :)

Cheers,
Angel.

#5 Bjoern Knafla on 02.18.08 at 12:38 pm

Hey Angel,

thanks for your helpful explanation - your context idea sounds really interesting and inspiring!

Cheers,
Bjoern

#6 Bjoern Knafla on 02.19.08 at 1:21 pm

The context idea keeps nagging at the back of my mind: a context would get even more useful if it can be queried for some attributes describing the subtree is represents or the state of the subtree it represents. Such attributes could be:
- the currently active conditions and actions
- if it is interruptible ind principle and in the current moment
- how long it might take to evaluate it (to get knowledge about animations that need to run for a certain amount of time)
- sub-contexts it holds

Perhaps different contexts for different purposes would be helpful (one context just for “is active” tagging, on to inform about animation capabilities, etc.).

Cheers,
Bjoern

#7 Angel Cabrero on 02.19.08 at 1:44 pm

hi Bjoern,

well, when thinking about the context feature for our behavior-tree framework, my first objective was to keep it fairly clear and simple, so as it could be used for designer or any other non-programmer person, so maybe it could be a good idea to keep diferent mechanisms for this other uses you mentioned or at least keep then clearly separated in the API. Maybe, the interruptible flag could be easely understandable and simple to use for basic decisions. For me its important to take care on the complexity of the interface of a framework, so as to avoid leading to bad use or bad understanding of the way things can or should be done with the framework.

Cheers, and thanks for sharing your thoughs about the idea :)
Angel.

#8 Bjoern Knafla on 02.19.08 at 2:05 pm

Hey Angel,

yeah, balancing power with ease of use and a good interface is the high art of software development and a constant struggle.

While I am browsing through the tutorials and the sources of Game::AI++ I get the impression, that the “context” idea as a meta tag to the BT is something that Alex could use easily or which is already implicitly present because of the way a tree might be interpreted as a solution space for a planner… though after 10 minutes of source code reading I don’t have a real understanding of all the things going on in Game::AI++ ;-)

I am opting for an article that brings together the BT idea, the task idea and the interpreter idea to explain how trees are traversed, how tasks are really scheduled and how trees could be interpreted in different ways. :-)

Cheers,
Bjoern

#9 alexjc on 02.19.08 at 2:31 pm

I think tags are a cool idea. I’ve never used them, but I think it helps modularity:
Game Design 2.0: Learning from Social Networks

Anyway, I like to keep tasks in a central place (like a scheduler or interpreter) so they can be iterated over easily. You can then have multiple behavior trees running in parallel, and have them check each other to decide the best course of action.

This is a bit like a blackboard architecture combined with behavior trees, which Max Dyckhoff mentioned his 2007 talk at GDC. It works to keep all the parts of the logic simpler…

Bjoern, you’re right, there’s lots to tie together here!

Alex

Leave a Comment

Game AI Character