Important Note: I am not using the CryEngine anymore, so this page is up “as-is”. It is probably very flawed and incomplete. And I won’t be able to provide any help about this topic, sorry. But feel free to discuss things in the comments.
This very basic tutorial will help you setting up a basic AI behavior for melee combat. Meaning that it will yield a behavior that you can assign to your entity character. The entity starts in idle mode, then seeks the player when it enters its field of view, starts approaching until a certain distance and starts attacking once it’s in range for its melee attack to make contact.
First I want to thank the_grim and all the community members from crydev.org for making a lot of this information available in the first place. This is only possible because of what I learned from them. Now let’s move on to the topic at hand!
The Selection Tree file:
To start with, we need to define a Selection Tree. This file contains a Variables section (user-defined variables), a SignalVariables section (the engine’s own hard-coded signals) and a Priority section, listing all the behaviors and their conditions:
<SelectionTrees>
<SelectionTree name="Swordsman" type="BehaviorSelectionTree">
<Variables>
<Variable name="Alerted"/>
<Variable name="AwareOfPlayer"/>
<Variable name="IsAttackRange"/>
</Variables>
<SignalVariables>
<Signal name="OnEnemySeen" variable="AwareOfPlayer" value="true"/>
<Signal name="OnEnemySeen" variable="Alerted" value="true"/>
<Signal name="OnLostSightOfTarget" variable="AwareOfPlayer" value="false"/>
<Signal name="OnCloseContact" variable="IsAttackRange" value="true"/>
<Signal name="OnCloseContact" variable="Alerted" value="true"/>
<Signal name="OnCloseContact" variable="AwareOfPlayer" value="true"/>
<Signal name="OnThreateningSoundHeard" variable="Alerted" value="true"/>
<Signal name="OnReceivingDamage" variable="Alerted" value="true"/>
</SignalVariables>
<LeafTranslations />
<Priority name="Root">
<Leaf name="SwordsmanAttack" condition="IsAttackRange"/>
<Leaf name="SwordsmanApproach" condition="AwareOfPlayer"/>
<Leaf name="SwordsmanSeek" condition="Alerted"/>
<Leaf name="SwordsmanIdle"/>
</Priority>
</SelectionTree>
</SelectionTrees>
In this case I’ll name the file “Swordsman.xml” and I’ll save it in …/Game/Scripts/AI/SelectionTrees/.
The GoalPipes file:
This is where we define what should happen and when. Notice that you’ll recognize the names of the GoalPipes, since they’re the ones we declared as “leaves” in the Selection Tree.
<GoalPipes>
<GoalPipe name="swordsman_idle">
<Locate name="player"/>
</GoalPipe>
<GoalPipe name="swordsman_seek">
<Locate name="player" blocking="false"/>
<Script code="entity.Behavior:AnalyzeSituation(entity);"/>
</GoalPipe>
<GoalPipe name="swordsman_approach">
<Locate name="player" blocking="false"/>
<AcqTarget name="player" blocking="false"/>
<Speed id="Run" blocking="true"/>
<Stick blocking="true" distance="2.3" AlignBeforeMove="true" UseLastOp="true" LookAtLastOp="true"/>
<Script code="entity.Behavior:AnalyzeSituation(entity);"/>
</GoalPipe>
<GoalPipe name="swordsman_attack">
<Locate name="player" blocking="false"/>
<AcqTarget name="player" blocking="false"/>
<FireCmd mode="MeleeForced" useLastOp="true" timeout="1.5" blocking="true"/>
<FireCmd mode="Off" blocking="false"/>
<Approach time="0.25" UseLastOp="true" LookAtLastOp="true" blocking="true"/>
<Script code="entity.Behavior:AnalyzeSituation(entity);"/>
</GoalPipe>
</GoalPipes>
I’ll name this file “GoalPipesSwordsman.xml” and I’ll save it in …/Game/Scripts/AI/GoalPipes/.
The Personalities file:
Those files correspond to the GoalPipes we just defined. So there is one .lua file for each GoalPipe:
SwordsmanApproach.lua
-- SwordsmanApproach behavior.
-- Based on a file created by the_grim
local Behavior = CreateAIBehavior("SwordsmanApproach",
{
Alertness = 2,
Constructor = function (self, entity)
Log("============")
Log("Approaching!");
Log("============")
entity:SelectPipe(0, "swordsman_approach");
end,
Destructor = function(self, entity)
end,
OnGroupMemberDiedNearest = function ( self, entity, sender,data)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
OnGroupMemberDied = function( self, entity, sender)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
AnalyzeSituation = function (self, entity, sender, data)
local range = 2.5;
local distance = AI.GetAttentionTargetDistance(entity.id);
Log("Distance in approach:");
Log(distance);
if(distance > (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", false);
elseif(distance < (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", true);
end
end,
})
SwordsmanAttack.lua
-- SwordsmanAttack behavior by the_grim
-- Based on FogOfWarAttack by Francesco Roccucci
local Behavior = CreateAIBehavior("SwordsmanAttack",
{
Alertness = 2,
Constructor = function (self, entity)
entity:MakeAlerted();
entity:DrawWeaponNow();
Log("=======")
Log("Attack!")
Log("=======")
entity:SelectPipe(0,"swordsman_attack");
end,
OnGroupMemberDiedNearest = function ( self, entity, sender,data)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
OnGroupMemberDied = function( self, entity, sender)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
AnalyzeSituation = function (self, entity, sender, data)
local range = 2.5;
local distance = AI.GetAttentionTargetDistance(entity.id);
Log("Distance in attack:")
Log(distance);
if(distance > (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", false);
elseif(distance < (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", true);
end
end,
})
SwordsmanIdle.lua
-- SwordsmanIdle behavior
-- Created by the_grim
local Behavior = CreateAIBehavior("SwordsmanIdle",
{
Alertness = 0,
Constructor = function (self, entity)
Log("Idling...");
AI.SetBehaviorVariable(entity.id, "AwareOfPlayer", false);
entity:SelectPipe(0,"swordsman_idle");
entity:DrawWeaponNow();
end,
Destructor = function(self, entity)
end,
OnGroupMemberDiedNearest = function ( self, entity, sender,data)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
OnGroupMemberDied = function( self, entity, sender)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
AnalyzeSituation = function (self, entity, sender, data)
local range = 2.5;
local distance = AI.GetAttentionTargetDistance(entity.id);
if(distance > (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", false);
elseif(distance < (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", true);
end
end,
})
SwordsmanSeek.lua
-- SwordsmanSeek behavior
-- Created by the_grim
local Behavior = CreateAIBehavior("SwordsmanSeek",
{
Alertness = 2,
Constructor = function (self, entity)
Log("Seeking!");
entity:SelectPipe(0, "swordsman_seek");
end,
Destructor = function(self, entity)
end,
OnGroupMemberDiedNearest = function ( self, entity, sender,data)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
OnGroupMemberDied = function( self, entity, sender)
AI.SetBehaviorVariable(entity.id, "Alerted", true);
end,
AnalyzeSituation = function (self, entity, sender, data)
local range = 2.5;
local distance = AI.GetAttentionTargetDistance(entity.id);
if(distance > (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", false);
elseif(distance < (range)) then
AI.SetBehaviorVariable(entity.id, "IsAttackRange", true);
end
end,
})
We need to save those files in a folder with the name of the Selection Tree (in this case “Swordsman”) …/Game/Scripts/AI/Behaviors/Personalities/Swordsman/.
Registering the behavior:
Finally, we need to tell the engine that this new type of behavior should be available in-game, and to do so, we need to register it in the PipesManager.lua file, located in …/Game/Scripts/AI/GoalPipes/. Just find the following section and add a line at the end to point towards your GoalPipes file:
-- Specific (Xml)
AI.LoadGoalPipes("Scripts/AI/GoalPipes/GoalPipesFogOfWar.xml");
AI.LoadGoalPipes("Scripts/AI/GoalPipes/GoalPipesBasicAI.xml");
AI.LoadGoalPipes("Scripts/AI/GoalPipes/GoalPipesCivilian.xml");
AI.LoadGoalPipes("Scripts/AI/GoalPipes/GoalPipesZombie.xml");
AI.LoadGoalPipes("Scripts/AI/GoalPipes/GoalPipesSwordsman.xml");
After that, the Selection Tree will become available in Sandbox once you select an entity:
Again, this is a very basic tutorial, but I hope it helps!