Creating Custom Flow Nodes
Flow nodes control how the behavior tree is executed and how control is ceded from one node to the other. You can create your own Flow nodes to have greater control over how your tree is executed.
Flow nodes can be extremely powerful, but the way that they work is quite simple. They don't have any control over the status or the next node beyond its parent or children, and they only act as scaffolding for the rest of the tree.
The method used to control the "flow" of the tree is the Tick
method. It has the following signature:
public abstract int Tick(object flowMemory, NodeStatus status, int index);
If you've read the Creating Custom Actions page, you'll know the purpose of the flowMemory
parameter. The two other parameters, status
and index
, contain information about the context given to this node.
The Tick
method for a Flow node will run under two circumstances:
- It is ticked from its parent
- It is ticked after its child is done executing
The index
parameter is tasked with containing the values that describe these circumstances. When a flow node is ticked from its parent, the index
parameter will have a value of -1. When ticked from its child, it will contain the index of that child in the array of children for this flow node.
The status
parameter will contain the last status of the tree. When ticking from a child, its value will be that of the child's Tick
return value. However, if the Flow node is ticked from its parent, it will contain the status of the last action executed by the tree. It may not necessarily be in the current flow's child array. Keep this in mind when programming your logic.
As stated above, flow nodes cannot modify the status of their child nodes, only pass them along to other parts of the tree. Its return value, of type int
gives the index of the next child node to execute. This index is zero-based, so if you want to execute a flow's second child, return a value of 1. A return value of -1 will move up teh tree to the flow's parent, passing along the status of the last node.
As an example, here is the code for the Tick
method for the Selector node. This node will execute its children, in order, until one of them returns Success
.
if (index == -1 && children.Length > 0)
return 0;
if (index + 1 > children.Length - 1 || status == NodeStatus.Success)
return -1;
return index + 1;
The first if
statement is used to execute the first child no matter what the status
parameter's value is. Remember, it contains the value of the last executed action node, not necessarily in the current children
array.
The second if
statement checks for two possibilities:
- The index is at the end of the child array
- The status of the last executed action node is
Success
In both cases, we want to travel back up to the parent, so we return -1.
If the node was not successful but also not the last node in teh array, we move onto the next node by returning index + 1
.
You can also include variables and instance parameters in your Flow
class to add extra control over the next node executed. For example, you could have an extremely simple node that will execute the node given by a Blackboard variable:
using Schema;
using UnityEngine;
public class SelectFromVariable : Flow
{
public BlackboardEntrySelector<int> nodeIndex;
public override int Tick(object flowMemory, NodeStatus status, int index)
{
int i = nodeIndex.value;
// If i is out of bounds, move up to the parent
if (i < 0 || i > children.Length - 1)
return -1;
return i;
}
}
This behavior makes creating custom flow nodes a very powerful tool for making your trees as custom-fit as possible.