Using Decorators to Improve Behaviors

Alex J. Champandard on July 26, 2007

How do you build complex behaviors without them becoming too complicated? Most modern games require intelligent and realistic actors to support the design, but it’s often difficult to create their AI.

One solution to this problem is to use modularity to assemble behaviors from simpler parts. Previously, you learned about sequences, selectors and parallels. These are “composites” that combine multiple behaviors together, but sometimes it’s necessary to add features to a single behavior. That’s what a decorator is for.

Decorating a leaf behavior in the tree.

What Is a Decorator?

A decorator adds functionality to any modular behavior, without necessarily knowing what it does. In a sense, it takes the original behavior and adds decorations, i.e. new features. The name is inspired by the software design pattern.

In the context of a behavior tree, a decorator can be inserted anywhere but as a leaf, since it requires a child behavior to improve its functionality. But technically, it’s not a branch either as it only has one sub-tree. In essence, it’s an extension to a sub-tree which creates a more complex behavior.

Types of Decorators

A decorator is a class of nodes in the behavior tree; it does not implement any specific features. In fact, the default decorator would be an identity operation which does not modify the child behavior in any way.

More interesting examples of decorators are:

These prevent a behavior from activating under certain circumstances.

  • Limit the number of times a behavior can be run.

  • Prevent a behavior from firing too often with a timer.

  • Temporarily deactivate a behavior by script.

  • Restrict the number of behaviors running simultaneously.

Managers & Handlers
These are decorators that are responsible for managing whole subtrees rather than single behaviors.

  • Deal with the error status code and restart planning or execution.

  • Store information for multiple child nodes to access as a blackboard.

Control Modifiers
These decorators are used to pretend that the execution of the child node happened differently.

  • Force a return status, e.g. always fail or always succeed.

  • Fake a certain behavior, i.e. keep running instead of failing or succeeding.

Meta Operations
Such decorators are useful during development, and can be inserted into a tree automatically when needed.

  • Debug breakpoint; pause execution and prompt the user.

  • Logging; track this node execution and print to the console.

In Practice

The decorators that modify control flow are typically useful when creating the structure of the tree, along with the other composites like sequences and selectors. But once it’s in place, the fine tuning can start.

Most of the other decorator types listed above are used for making such minor changes to the behaviors. They can be inserted harmlessly at any place in a behavior tree. A visual editor can support commands such as Decorate Selected Node which can be applied locally in a safe and predictable way.

This approach makes it much easier to create complex trees for virtually any behavior designers can dream of.

Does your AI have the equivalent of decorators?

Discussion 4 Comments

gware on July 26th, 2007

It's a very interesting application of this pattern. to answer the question : we use subclassing to do most of the stuff described above. I believe it's a good approach in the case of a tree of managers (but I would also say it's less elegant;). Using subclassing, factories and configuration scripts you can acheive something very much alike. It's "easy" to obtain dynamic tree building, thus having good modularity without having to "build the code" each time you want to change hierarchies. Also, in my opinion, "meta operations" as decorator seems unnecessary. I think that only coders will want to break: most development environment offers good tools for that (conditional break points seems to do this work, and it's probably easier and quicker). Plus, it can make the tree a bit difficult to comprehend during debug (since you may end up with developpers have completelly different trees, some breaking here but doing that, other logging, and so on). I prefer good old data driven logging and hardcoded break points :) Anyway, that 's a very nice use of the decorator (I know, i'm repeating myself, but I've been quite surprised by this post:) )

krinosx on July 27th, 2007

Well... I´m not working with GameDevelopment YET! I build commercial applications and hava been used decorator to do Filters. I think decorators are usefull to filters... maybe the best apporach. I read in wikipedia the "decorator patter" and its interesting the example with the Scrollbars... I have made once a "mobile componnent" that was a grid... but make my code become a piece of shit to implement the scrollbars.. So usefull apporach of decorators in your post!

gware on July 27th, 2007

Another helpful pattern when implementing behaviors could be the memento pattern. It's very common to have a part of your engine asking the AI system to "simulate" some behavior. Memento patterns help you save the state of a an object and reset it back to normal. This pattern can be very helpful when planning since you can use them as "check points" during the planning phase. Are you using this kind of patterns , or planning this functionality for Game::AI++?

alexjc on July 27th, 2007

[B]Gabriel,[/B] The biggest problem with subclassing is that you can't do it dynamically. So your designers are limited by what was compiled into the game... With decorators you can improve any behavior, even at runtime! As for debugging and logging, I tend to think of these decision making systems as their own scripting language. The programmer might have 100% bug-free code, and not require any debugging or logging, but when the designers start to build complex behaviors, they might need some help figuring out what's going on... That's why the decorators are useful for breakpoints and logging too. Designers can use it on their own. Also, it means the core interpreter does not need to contain this logic, and it becomes much simpler because of it. As for the Memento pattern, I wasn't familiar with it. But I have something similar in place for individual variables, so it's easy to [TT]undo()[/TT] when backtracking. I will look into it further, thanks! [B]Giuliano,[/B] Thanks for your comments... Decorators are certainly helpful beyond AI behaviors!

If you'd like to add a comment or question on this page, simply log-in to the site. You can create an account from the sign-up page if necessary... It takes less than a minute!