Behavior Editor – Arma Reforger
Editor Basics
N° | Description |
---|---|
1 | Node area |
2 | Nodes palette |
3 | Node parameters |
4 | Variables |
5 | Debug |
6 | Console |
Shortcuts
Shortcut | Description |
---|---|
F9 | Create/delete breakpoint |
Ctrl + O | Open |
Ctrl + S | Save |
Ctrl + ⇧ Shift + S | Save As |
Ctrl + C | Copy |
Ctrl + X | Cut |
Ctrl + V | Paste |
Del | Delete node, variable or connection |
Controls
Shortcut | Description |
---|---|
Move around behavior tree | Hold in free space |
Connect nodes | Press and drag from a black bar in the lower or upper part of the node, connect it to the black bar in another node. Connecting variables is analogous but with pins. |
Select multiple nodes | ⇧ Shift + drag.
Ctrl + on different nodes. |
Zoom in/out | Mousewheel up/down. |
Disconnect node | Select connection and press delete. |
Organize nodes | When multiple nodes are selected, they can be aligned or grouped. |
Open scripted node script | Double click on a scripted node |
Introduction to Behavior Trees
Editing behavior trees have a similar basis as FSM. Nodes are being executed from the Root node according to the rules of connection. The biggest difference is that BT is not limited to one state at one time, but can run on multiple branches at one time. There are conditions and effects in nodes, but also more advanced flow control mechanisms, that make BTs powerful, but also more complex. Each node returns success, failure or business to its parent node, which can decide the following steps according to the information.
Behavior tree are difficult to think with, because they don't have the same basic logic as scripting, for example, AND or OR operators are not present and can be a bit difficult to achieve (but there is a possibility to script basic conditions), also the flow of the trees can be harder to understand. But it enables writing complex behaviors with multiple states, which cannot be defined by simpler methods, like FSM.
Concepts of Behavior Trees
Flow Control
- Root – basic node which serves as entry point to which other nodes are directed.
- Sequence – all children nodes will be executed in sequence unless children returns false. Children nodes are assigned numbers to show sequence of execution.
- Selector – opposite of sequence: After executed children returns true, other children will not be executed. Sequence of executed is marked with numbers as well.
- Parallel – run all children nodes simultaneously.
- Repeater – run child multiple times.
- Run BT – launch behavior tree.
Decorator
Decotators are used for evaluating conditions, and according to result of evaluation, they execute their child branches. There are several types of conditions decorators are testing, and different flow effects decorators can have.
Example tests and effects on flow:
- Test validity of given entity (does it exist, is it alive?)
- Test return values of its children (UseChildResult parameter)
- Test scripted condition by creating your own scripted decorators
- Interrupting flow in rest of BT (with parameter AbortType)
More complex operations can be done by decorators. For example executing child node, but returning arbitrary value (either true or false), so the rest of the tree react on it in different ways.
Tasks
Nodes that are executing functions inside Behavior Trees. Any action that AI should provide is task: Move to position, run, play surrender gesture. Even waiting is task. There are 2 types of tasks:
- Immediate– tasks that are done immediately and continue with execution of BT.
- Ongoing (running) – tasks, like move, that take time to complete. At the point in which character is running, BT is suspended, and next time BT is called, will continue from this point. If there is no flow running in paralel, they block execution of anything else in BT until task is done.
Tasks cannot have children, and usually they are organized in sequences to complete more complex operations. For example: Stop, Raise Weapon, Aim, Fire.
Tasks can perform very simple operations, like assigning value to variable, to more complex functionality, like Land (with aircraft).
Behavior Tree Flow
As explained at the beginning, behavior trees are executed from root folder, and through flow control and decorators, pass through the graph. BTs can be called from engine, script or other BT, so it's possible to consider it as a function, which can call other functions, and keep the graph relatively clean, not having all AI functionality in one gigantic graph.
Returning Values to Parent Nodes
Each executed node will return a value to parent node, which can react to it, in two ways:
- Flow control nodes like selector and sequence will either execute another child page, or return value up to its parent node.
- There are 3 return value types: True, false, running
- Running means ongoing behavior like movement, which will prevent execution of the rest of the BT (unless using parallel node)
- Running state will return through the BT up to the Root node, so state of whole behavior tree becomes "running"
Interrupting Behavior: Abort
When you need to interrupt running behavior or prevent execution of other nodes, you can use AbortType parameter, which is part of Decorators. If test in decorator is true and Abort is used, if will stop execution of part of Behavior tree.
Types of Abort:
- Abort Children Branch – Stop execution of all children. When decorator is evaluated, regardless of the result, it will not execute its child node.
- Abort Parent Node with Children – Abort every child node of decorator's pattern, and also all nodes of node's immediate parent.
- Abort Parent Node Further Children – Execute decorator's child branch, but prevent executing any other branches.
In-Game Setup
You need a world with character prefab, which is set up with all important AI components. Everything is prepared in world in: \Arma4Data\worlds\AIDemo\Demo0_Template.ent. If you want to prepare AI in your own world, follow these steps:
- To have character with working AI, it has to have at least AI Control Component, and other AI components enabling AI functionality: AI Character Aiming Component, AI Character Movement Component, Perception Component. Working AI character prefab is ChimeraCharacterAI.
- This character has all required AI components. AIControlComponent creates agent, which is separate entity ordering actions to character.
- In AIControlComponent, set OverrideAIBehaviorData with your Behavior tree (will be created below).
- Run the game with BT editor opened.
- There is list of running BTs in right panel. Click on BT of your soldier and you will see his behavior tree and states, in which it is now (Note: No modifications of this debug tree will be saved, you have to modify the original BT).
Basic Behavior Trees
Simple Action
Most basic behavior tree possible is to attach Task to Root node. This this will make object on which BT is called crouch every time BT is called.
Follow Player
Basic version of follow player is to first select the entity to follow, and then executing the move command.
Now character on which BT is running, will follow player. See parameters of the node to adjust specifics, like obstacle avoidance, precision, etc.
This BT has one minor problem, that can be optimized: When unit is at player, every time that BT will tick, it will always perform left branch for the tree: Getting controlled entity and overwriting the variable.
This is optimized in next graph, which runs as follows:
- Entity decorator tests validity of input, and return negative result.
- Negative test + negative result will return true, so node connected under it (Get Controlled Entity) will be executed.
- It will set player variable,.
- Next time decorator Entity will be called, it will return false, and its branch won't be called.
Guard Player
This behavior tree is switching between 2 states: If unit is more than 10 meters from player, move to him. If unit is closer, crouch and look around with weapon raised. The flow of the tree goes in this way:
- Fist select most left branch, because player entity variable is not assigned.
- Variable player is assigned, and BT exited.
- Second run, test for player variable validity returns negative, and distance check of unit from player is performed.
- Let's say player is further than 10 meters, so test returns true, and sequence on the right is performed: Stand up, Lower weapon (performed with Raise weapon node, and with uncheck parameter raise), move to player, with tolerance of 4 meters.
- Until movement is completed, behavior tree will not check any other condition, like distance decorator in the middle.
- When unit gets close to player, distance decorator will return true, and its children branch is executed: Crouch, Raise Weapon, and create randomized position, in which the unit will look (this is how we get random direction). Orient node will mean unit will look into specified direction, and idle for short time (0.5 second).
- Once this sequence is completed, tree will be executed again. If player's distance is still below 10 meters, children sequence will be executed again, if not, return to point 4.
Guard player and shoot enemies, using scripted functionality
For making engage functionality, there should be third branch, handling the engagement. This is most primitive version of the engagement node, which searches for predefined enemy (searched by variable):
To be able to recognize enemy, we can script our own Task, called Find Enemy. It will find characters nearby except player and guard, and mark them as target. Current target is selected automatically.
Scripted Task: Find Target
- If valid target is found, then BT will continue with the branch, which includes aiming and shooting at the enemy.
This is how task scripted can look. For clarity, the calculation of target is not included.
class AITaskFindTarget : AITaskScripted
{
// Member variables
// Exposing variable m_Radius as attribute will make it visible as parameter inside BT Editor.
// Note: Functionality not completed now
[Attribute("10", "editbox", "Radius")]
float m_Radius;
IEntity m_Player;
// Constructor can be used for initializing default values,
// but for any other operation, use EOnTaskSimulate
void Init()
{
m_Radius = 100;
}
// Make scripted node visible or hidden in nodes palette
bool VisibleInPalette()
{
return true;
}
// Sets up input variables, as array of strings
override TStringArray GetVariablesIn()
{
// Make sure variables are initialized only once
if (!m_aVariablesIn)
{
m_aVariablesIn = new TStringArray;
m_aVariablesIn.Insert("Distance");
}
return m_aVariablesIn;
}
// Sets up output variables
override TStringArray GetVariablesOut()
{
if (!m_aVariablesOut)
{
m_aVariablesOut = new TStringArray;
m_aVariablesOut.Insert("Target");
}
return m_aVariablesOut;
}
// Main method to perform all operations handled by task
// This is just example, to see important parts, without any actual calculations
ENodeResult EOnTaskSimulate(IEntity owner, float dt)
{
// This is engine method to read variables from Behavior tree
GetVariableIn("Distance",m_Radius);
IEntity target = GetValidTarget(m_Radius);
// This is method to output variable into Behavior Tree
SetVariableOut("Target", target);
if (target)
return ENodeResult.Fail;
else
return ENodeResult.Success;
}
}
Case of Scripted Decorator
Scripted Decorator is basically the same, it must be inherited from DecoratorScripted, but it uses the same methods as Scripted Task.
class ExampleScriptedDecorator : DecoratorScripted
{
[Attribute("10", "editbox", "Radius")]
float m_Distance;
bool VisibleInPalette()
{
return true;
}
override TStringArray GetVariablesIn()
{
if (!m_aVariablesIn)
{
m_aVariablesIn = new TStringArray;
m_aVariablesIn.Insert("Distance");
}
}
override TStringArray GetVariablesOut()
{
if (!m_aVariablesOut)
{
m_aVariablesOut = new TStringArray;
m_aVariablesOut.Insert("Target");
}
return m_aVariablesOut;
}
override TStringArray GetVariableType()
{
if (!m_aVariablesOut)
{
m_aVariablesOut = new TStringArray;
m_aVariablesOut.Insert("Target");
}
return m_aVariablesOut;
}
// Instead of EOnTaskSimulate, this method has different return type parameters:
override bool TestFunction(IEntity owner)
{
// You can check the type of variable you are handling:
GetVariableIn("Distance",m_Radius);
SetVariableOut("Target", null);
if (GetVariableType(true,"Distance") == "float")
return true;
else
return false;
}
}
Debugging
To check AI behavior in runtime, all AI entities are listed in the Debug section, which will open the root behavior tree of the unit. From there it's possible to traverse to any other connected behavior.
It's not possible to change any behavior during the runtime or variable, the functionality is read-only. You can read the states of nodes and what values are in variables.
You can put a breakpoint on any node, and the behavior tree will stop its execution. But this will not stop the game (as in the case of script debugger), and if there is any parallel node above breakpoint, other branches of it will keep executing.
Colors in debug:
- Green – node returned Success (variable is assigned)
- Red – node returned Fail (variable is unassigned)
- Blue – node returned Running
- Dark red – node is suspended by breakpoint
Further Reading
- Behavior Editor Nodes
- Intro into Behavior Trees in Gamasutra: Overview on Behavior Trees with useful examples
- Artificial Intelligence for Games, Second Edition: The big book on game AI development
- The Behavior Tree Starter Kit on http://www.gameaipro.com/ by Alex J. Champandard and Philip Dunstan: The first section paints the big picture for behavior trees and explains how to build BTs and how to use them for making AI decisions. The second section dives into the implementation. Finally, it explains the principles of second-generation BTs, and the improvements they offer, e.g. about memory optimizations and event-driven implementations that scale up significantly better on modern hardware.