Most useful programming languages implement conditionals; they look like if or switch statements — and sometimes lookup tables. In the context of AI behaviors, a selector does the same thing by picking one of its child behaviors to run.
A selector based on probabilities, with three child behaviors.
In the previous article, you learnt how powerful sequences can be when assembling hierarchies. Certainly, they are very useful, but they can’t do everything… and that’s where selectors come in. Combining these two simple primitives together to build your hierarchies, you can express most game behaviors.
Using both sequences and selectors instead of a traditional scripting language, you get all the power you need without the hassle. It becomes intuitive for designers to craft the behaviors, and they don’t have to wrestle with awkward syntax.
Selectors can each have their own criteria for selecting the child behavior. Some in particular are very useful for game designers to tweak:
Probability selectors pick one of their child nodes randomly based on the weights provided by the designer.
Priority selectors are simply an ordered list of behaviors that are tried one after the other until one matches.
Alternatively, selectors can be made non-deterministic. The designer’s use this to specify that it’s ok for behaviors to run in any order, and the result will still be realistic.
This gives you room to insert any AI algorithm to improve the quality of the behaviors, or the performance of the AI engine. For example, you could use a form of machine learning to select a child behavior (like a decision tree).
To be fully responsible for their sub-trees, you’ll need selectors to deal with termination codes. Typically, you’ll implement the code for the selector only once, and then use instances of that throughout your behavior tree.
So, just make sure the following termination status codes are dealt with appropriately:
If a child behavior succeeds, the selector can terminate successfully also.
In case a child behavior fails cleanly, the selector may backtrack and try the next child in order.
When a critical error occurs, the selector should bail out with the same error.
Dealing with these three cases makes your selectors work both for decision making/planning, as well as control/execution of the behaviors. This makes them easy to use everywhere!