This article is for CRYENGINE 3.4 or earlier. The Behavior Selection Tree and old-style behavior scripts was deprecated in favor of the Modular Behavior Tree in CRYENGINE 3.5 and beyond.
Since CryENGINE 3.3.0 Behavior Trees are obsolete and have been replaced by Behavior Selection Trees.
Any class can utilize a behavior/decision tree. The benefits of using a behavior tree are:
You will need to accomplish these steps to set up a new Behavior Tree:
(Location: Game\Scripts\BehaviorTree\Profiles)
A Profile is an Xml file that connects the Tree chart to an arbitrary list of Activation Condition charts. You refer to this profile by name in code when you want to start using the tree in your class.
A typical profile looks as followed:
<BTProfile tree="TestClass">
<ActivationConditions>
<ActivationCondition name="TestClass" table="TestClass" doReset="0" />
</ActivationConditions>
</BTProfile>
BTProfile: tree - This attribute defines which Tree chart should be loaded and used.
Note that the order of the Activation Condition definitions in the Profile Xml sheet is important! Input is parsed from top-down, so tables along the bottom get the input signals sent to them last.
(Location: Game\Scripts\BehaviorTree\ActivationConditions)
Activation Condition charts are Excel spreadsheets. The name of the file should match the name referred to by the Profile Xml. There are two important sheets to define in your Activation Conditions chart:
TestClassAC.xls* Signals - This chart defines the signals (input code) to alter the knowledge table. When a signal is sent to the tree, it is looked up in this sheet. You are able to modify the knowledge table as needed per signal, utilizing optional data that may have been passed along with the signal call.
(Location: Game\Scripts\BehaviorTree\Trees)
Tree charts are Excel spreadsheets. The name of the file should match the name referred to by the Profile Xml. There are two important sheets to define in your Tree chart:
Node properties - This is the tree implementation, where you define the outputs of the nodes and the conditional statements for traversing the tree. Each line represents a node in the tree, parent or child.
(Location: Game\Scripts\BehaviorTree\Tactics.xml)
Tactics are a way to coordinate two or more trees by restricting certain parts of the tree from being traversed until enough candidates are ready to satisfy the tactic. The Tactics.xml defines all valid tactics. The sub-tactics of these definitions are then referenced throughout the trees where they are valid. There are two important sheets to define in your Tree chart:
Requirements - This is where you define all valid sub-tactics and their requirements.
Users can be placed inside a group by being given a unique Tactics Group Id. This can be any integer value you specify. When a user starts a sub-tactic, if that user is part of a Tactics Group, only other members of his group can join him in that sub-tactic. This is an optional method to isolate a common pool of potential candidates.
(Location: Game\Scripts\BehaviorTree\Globals.xml)
Global Activation Conditions are stored in a unique Activation Conditions Chart. This is a special knowledge table that is accessible and modifiable by any and all active Profile users. Code can also send signals straight to the Global table for quick changing of values. See #Lua Language Rules section for more details on how to access this knowledge table.
Values
Globals.xlsSignals
Any class can load up and use a Behavior Tree by inheriting from the IBSSProfileUser abstract base class. When you inherit from this, you can then load a behavior tree and connect it to the class via a unique Id returned.
This ABC should be inherited by the class which will be responsible for handling input and output to the tree. The virtual functions are as followed:
To link the class to a tree, you should use the InitNewUser function in the IBSSProfileManager object returned from AISystem. Pass in the name of the Profile to load. A TBSSProfileUserId value is returned which you should store. Use this in future calls to send input to the tree or perform other operations on it.
As part of your cleanup process, be sure to use the RemoveUser function to delete the tree.
Other functions of interest:
To send input to the Activation Condition charts, you should use the SendUserInput function in the IBSSProfileManager object returned from AISystem. You should pass along your unique TBSSProfileUserId returned when you linked to the tree, described above. The name of the signal and optional data are passed along to all Activation Condition charts, from top to bottom as defined in the Profile Xml sheet.
When the tree has output to send back, it is sent through the OnBTUserEvent function inherited from IBSSProfileUser. The arguments sent are as followed:
When eBTUE_OnNewLeafNode is received, the tree is informing you that a new leaf node has been hit. However, it is up to you to permit the tree to switch over to that leaf node i.e., make a decision. You can do this by calling the AllowNodeChange function on the tree object sent in as a function argument. If you do not do this, the tree will continue to send out this event each frame. There are reasons you may want to stall a decision, for example to introduce a delay or to prevent constant state changes from happening due to an influx of new data. In any case, you will not receive eBTUE_OnDecisionMade until you have allowed the node change to go through.
Here is an example class that is loading our TestTree defined above and using it.
// Test behavior tree using class
class CBehaviorTreeTest : public IBSSProfileUser
{
public:
CBehaviorTreeTest();
virtual ~CBehaviorTreeTest();
// Sent when action is processed
void OnButtonState(bool bDown);
void SetGlobalValue(bool bValue);
// IBSSProfileUser
bool GetBTUserName(string &sOut) const;
bool CanBTUserTreeRun() const;
void OnBTUserEvent(EBTUserEvents event, IPersonalBehaviorTree *pTree, const string& sData, const ScriptAnyValue* pArgValue = NULL);
//~IBSSProfileUser
private:
TBSSProfileUserId m_userId;
};
//////////////////////////////////////////////////////////////////////////
CBehaviorTreeTest::CBehaviorTreeTest()
: m_userId(g_uBTUserId_Invalid)
{
// Link to tree and get the unique Id back
IBSSProfileManager *pManager = gEnv->pAISystem->GetBSSProfileManager();
if (pManager)
{
pManager->InitNewUser(this, "TestClass", m_userId);
}
CRY_ASSERT_MESSAGE(m_userId != g_uBTUserId_Invalid, "CBehaviorTreeTest failed to init with the \"TestClass\" BT profile");
}
//////////////////////////////////////////////////////////////////////////
CBehaviorTreeTest::~CBehaviorTreeTest()
{
// Release the tree
IBSSProfileManager *pManager = gEnv->pAISystem->GetBSSProfileManager();
if (pManager && m_userId != g_uBTUserId_Invalid)
{
pManager->RemoveUser(m_userId);
m_userId = g_uBTUserId_Invalid;
}
}
//////////////////////////////////////////////////////////////////////////
void CBehaviorTreeTest::OnButtonState(bool bDown)
{
IBSSProfileManager *pManager = gEnv->pAISystem->GetBSSProfileManager();
if (pManager && m_userId != g_uBTUserId_Invalid)
{
// Send the input signal into our Activation Conditions chart.
// The 'data' property will be set to the value of 'bDown'.
pManager->SendUserInput(m_userId, "SetButtonState", bDown);
}
}
//////////////////////////////////////////////////////////////////////////
void CBehaviorTreeTest::SetGlobalValue(bool bValue)
{
IBSSProfileManager *pManager = gEnv->pAISystem->GetBSSProfileManager();
if (pManager)
{
// Send a signal to the Global Activation Conditions chart.
// The 'data' property will be set to the value of 'bValue'.
pManager->SendGlobalSignal("SetTestValue", bValue);
}
}
//////////////////////////////////////////////////////////////////////////
bool CBehaviorTreeTest::GetBTUserName(string &sOut) const
{
// A good unique name for our tree.
sOut = "CBehaviorTreeTest";
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CBehaviorTreeTest::CanBTUserTreeRun() const
{
// Tree can always be ran!
return true;
}
//////////////////////////////////////////////////////////////////////////
void CBehaviorTreeTest::OnBTUserEvent(EBTUserEvents event, IPersonalBehaviorTree *pTree, const string& sData, const ScriptAnyValue* pArgValue)
{
switch (event)
{
// Sent when a parent node has been traversed which defined some signal data.
case eBTUE_OnNodeSignal:
{
CryLogAlways("[CBehaviorTreeTest] Event=eBTUE_OnNodeSignal Data=%s", sData.c_str());
}
break;
// Sent when a new leaf node has been hit.
case eBTUE_OnNewLeafNode:
{
CryLogAlways("[CBehaviorTreeTest] Event=eBTUE_OnNewLeafNode Data=%s", sData.c_str());
// Call to allow the tree to switch to this leaf node (make a decision).
// NOTE: Decision won't be made until we call this! We could stall here for a moment!
pTree->AllowNodeChange();
}
break;
// Sent when the tree has made a new decision (new leaf node and change was allowed to be made).
case eBTUE_OnDecisionMade:
{
CryLogAlways("[CBehaviorTreeTest] Decision Made! %s", sData.c_str());
}
break;
default:
CRY_ASSERT_MESSAGE(false, "CBehaviorTreeTest received unhandled user event");
break;
}
}
When dealing with the Lua code chunks present in the Activation Conditions or Tree charts, there are a few specific things you can reference in the Lua code.
Within the Tactics Ordering Function code, there are a different set of a few specific things you can reference in the Lua code.