Your First 60 Minutes with StateTree
Disclaimer
This guide is intended for advanced user who are already familiar with how the engine works. As part of the guide, other AI systems will be mentioned but not covered in depth as it is not the main focus of this guide.
Plugin Setup
Since this guide is all about StateTree, it relies on a couple plugins necessary for running StateTree with Actors. In the Unreal Editor, select Edit->Plugins from the menu bar. Enable the following plugins:
StateTree
Gameplay StateTree
Both plugins are production ready. The StateTree plugin contains the core framework for StateTree and its editor. The Gameplay StateTree plugin contains engine-provided components that allow Actors to use StateTree.
Concept Overview
StateTree Synopsis
StateTree at its core is a general purpose, hierarchical state machine. While it was used for AI in The Matrix Awakens and City Sample, it can be applied to use-cases of anything that uses stateful data. This can be but is not limited to:
Fighting game button combo selectors
Animation selection
Quest logic
And yes, AI behaviors ;)
For a more detailed talk over the theory and ideas behind StateTree, here is a deep dive presentation on StateTree given by Mikko Mononen at Unreal Fest Prague 2024: StateTree Deep Dive
StateTree Key Terminology
Schema - Sets up use-case specific data and can constrain or extend which nodes may be used with the StateTree
Context - Data provided by the schema that is available to bind to for any task or condition
Parameters - Additional data defined by the user for use in the StateTree. When assigning an instance of a StateTree asset, such as in StateTreeComponent or running a linked StateTree asset, users can provide custom values to be used for the parameters.
Global Task - Tasks that run for the lifetime of the StateTree. These can be useful in exposing external data to the tree, configuring event listeners, and cleaning up the tree when stopping.
Evaluator - These have largely been phased out in favor of using Global Tasks. Global Tasks handle the same uses as Evaluators.
Property Categories - Metadata used for properties in tasks and conditions to allow binding data to task inputs and outputs. The categories currently used are:
Context - Will automatically try to bind to best-fitting Context data but can be overridden to other data
Input - Property passed into a task/condition that must be bound for the StateTree to compile. Other public properties may accept bindings, but they are seen as optional bindings which you can also specify values to use.
Output - A property that is output from a task/condition that other inputs can bind to. You cannot bind the output to a parameter to store its return value.
Property References - References that allow for writing data back to the parameters similar to Blackboard keys.
In 5.4, property references are only available in C++, but there is a BP version coming in 5.5. The BP struct is called FStateTreeBlueprintPropertyRef, and it is already present in the UE5 Main branch. The BP version can be used to read the parameters, but it does not allow for setting the parameter as is possible with FStateTreePropertyRef in C++.
State - Organizational layers inside of StateTree that can contain child states, tasks, entry conditions, and transitions. States can have varying types
State - Basic state used in the tree
Group - Cannot have any tasks but can still have child states, enter conditions, and transitions
Linked - Links to a Subtree state within the SAME StateTree asset, but execution will keep the current branch of states in the hierarchy
Linked Asset - Allows for running another StateTree asset inside of the tree
Subtree - A state that can be linked to from a Linked state and may have tasks, child states, conditions, and transitions still
Transition - Rules for where and when a state should change to another state in the tree
StateTree Execution Flow
StateTree State Selection
This is the default state selection process for StateTree. Selection looks like a depth-first search with StateTree exploring all the way to the first valid leaf state. By default, StateTree will only ever transition to a valid leaf state and never stop at an intermediate state in the tree. It is possible to specify different selection behaviors for states in the tree that change how state selection is done, but all of those changes are things that must be opted in to use. There will be brief descriptions of what those other selection behaviors are after explaining the default behavior.
Tree starts at the root node on startup
Child nodes are evaluated in order from top to bottom
If a child state has an Enter Condition, the enter condition is checked
Important note: if the enter condition relies on the output of a task higher in the treeās hierarchy, there are times where it can fail because the input parameter does not have a proper value. StateTree will not run prerequisite tasks for the enter condition to be able to evaluate it. This could change in the future, but it is not currently planned.
If the enter conditions are passed successfully, the state will be chosen if it is a leaf, and if it is a intermediate state, the tree begins evaluating this stateās child states
If the enter condition fails or none of the deeper children states succeed to enter, the next child state is tested
If all children for the tree fail to be entered, the tree will stop and enter a status of Tree Failed.
When changing states, the tasks and states are exited in order from leaf to root of the previous state, and the enter state events happen from root to leaf.
The tree will only exit states up to the first shared state between the previously running state and the state into which it is transitioning.
Special state selection behaviors:
Try Enter
This allows for an intermediate state to be entered even if it has leaf states
The state will be entered as long as its enter conditions are satisfied
Try Children in Order
This is the default behavior of attempting to enter the child states in order from top to bottom
Try Follow Transitions
Instead of entering the state, it will attempt to follow the transitions set up on the state
None
This prevents the state from being selected directly
StateTree Transition Selection
When a task completes or an event is triggered, StateTree will begin attempting to transition by triggering the transitions in order from the leaf state to the root. The first transition that succeeds will be used. This allows for transitions to be set on intermediate states to be used in the event a leaf state does not have a valid transition.
Something very important to remember is that any task that finishes, whether successful or not, on ANY active state will attempt to trigger transitions. As such, not all tasks need to call FinishTask unless they should force a re-evaluation of the tree. For example if a task is getting a location to be used with MoveTo, the task should get the location but not call for FinishTask as it will cause the tree to transition before the move can be finished.
Building the Outline for Your StateTree
Creating the StateTree Asset
To create the StateTree asset, right-click in the Content Browser or press the + Add button and choose Artificial Intelligence->StateTree from the menu. In the Pick Schema for StateTree window, choose the StateTree AI Component schema. The StateTree AI Component schema has an extra context property for binding to an AI controller in addition to the context Actor, and as such, the schema requires an AI controller to be run. Name the StateTree ST_Wildlife. Open the StateTree.
The StateTree editor comes with several windows and tabs by default, but the focus for the first bit of use will be around the Asset Details panel on the left of the editor and the StateTree window in the center. The Asset Details panel shows the info of the StateTree such as schema used, base classes of the context data, the names of our context data variables, parameters, evaluators, global tasks, and theme. This is where the data and tasks that are always available on the tree reside.
In the schema section, you can change the schema the associated tree uses as well as set the base classes that the context must use. By changing the classes, StateTree can guarantee that certain data are available for binding from the context and any variables associated with that class. For now, leave the classes set to their defaults.
While the StateTree AI Component schema specifies an AIController context variable, it is easy to overlook that the default StateTree Component schema will attempt to make the Actor context variable be the owning controller of the brain component if two criteria are met. First, does the BrainComponent have an AI owner/controller, and second, does the controller derive from the base class listed in Context Actor Class. Since AI controllers are Actors, this will end up binding both of the context variables to the AI controller if the classes are not specified.
Add States to the Tree
Now, it is time to flesh out the tree beyond its singular Root state. States can be added to the tree by pressing the + Add State button at the top of the tree view or by right-clicking a state and selecting Add State from the context menu while specifying if it is a child or sibling state. You can also drag states to change their order and depth in the StateTree hierarchy. A thin blue line when dragging will re-order the states at the current depth, but if you hover over another state and see a blue box, it will parent the dragged state to the state being hovered.
For designing the layout of the StateTree, it can be useful to think of the various actions the wildlife can do. While there are many options to consider, the main behaviors this wildlife will use are:
Wander around the level
Flee from danger
Watch threats after fleeing
Graze/eat
Idle
Assess situation when in danger
This is by no means an exhaustive list of possible behaviors for wildlife, but it gives a good place to start with designing the layout of the StateTree. It is possible to list all of these as sibling states like a traditional finite state machine, but since StateTree is a hierarchical state machine, these behaviors can be further organized into states and child states. That being said, the behaviors can be grouped into two higher-level states: Peaceful and Danger. The resultant hierarchy looks like this:
Danger
Flee
Assess Situation
Watch Threat
Peaceful
Wander
Graze
Idle
Add the states to the StateTree and organize them to look like the above outline.
Notice that the leaf node states have transitions automatically set up to return to the Root node when they complete. This is an implicit transition to prevent the StateTree from becoming stuck in a single branch in the event no transitions are configured. Once a transition without a condition is added, the automatic transition to Root is removed.
Making the StateTree Easier to Visually Distinguish
A new feature to StateTree in 5.4 is the ability to set themes for the StateTree asset. It is possible to change the color of states to use the theme color to quickly convey information about the state. Each state has its own Color property in its Details panel which can be set to one of the theme colors. Themes are set per StateTree asset and could convey groupings, types of states, selection behaviors, or other information. Here, they will be used to help denote groupings of the states.
Create a new color theme by pressing the + button to the right of Colors in the Asset Details panel. Call the new color Danger and set the color to use a shade of red. Create another color named Peaceful which uses a green color.
Add the theme colors to the relevant states. This can be done for each individual state by changing the Color property in the Details panel. It is also possible to select multiple states in the tree at once to change the color on all of them.
Adding Behavior to States via Tasks
An Intro to StateTree Tasks
States, in and of themselves, do not provide any behavior or logic to the AI agent. Behavior logic is driven inside of tasks that are added to a state. There is no limit to the number of tasks that a state may have, and any state in the tree can have tasks unless it is marked as a Group state. Every task on the state runs while it is active, and active states include parent states in the tree. The usual setup is for leaf states to contain the action tasks, and intermediate states to have tasks for configuring or claiming resources.
Starting to Build Behavior
It is time to add behavior to the states, and that means tasks. Select the Wander state and add a task by clicking the + button for Tasks in the Details panel. The engine comes with a few prebuilt tasks for use in StateTree, and one of these is Move To which was added to work with the new StateTree AI Component schema. Select Move To from the dropdown menu for the newly created task. Expand the details for the task by clicking the arrow to the left of the dropdown.
The first thing to notice is the AIController property tagged as Context. The chain icon in the middle denotes that StateTree has automatically bound the context AI Controller to this taskās AI Controller property. It is possible to supply a different AI Controller from the context by clicking the binding button on the right. For this task, keep the context AI controller bound as the agent moving is the one running the StateTree.
Second, the Move To task requires either a location or Actor to mark as the destination of the move. The goal of the Wander state is for the wildlife to roam about undirected instead of to a specific Actor. There is also not a task supplied by the engine to get a location. It is time to make a custom task to accomplish this.
Creating a Task to Find a Random Location
Open the Content Browser and create a new BP class. The class will derive from StateTreeTaskBlueprintBase and call it STTask_GetRandomLocation. Open the BP task. To obtain a useful random location, the task will need some information passed into it. Add a variable to the task called Actor with a type of Actor Object Reference. Set the variable to be instance editable and in the Details panel, change the Category to Context. Setting the category to Context will allow StateTree to automatically attempt to bind the property from its context properties.
The task also needs to return the location that it finds to the StateTree. Tasks do not have a way to return a value similar to what may be used in a function. Instead, it uses a variable categorized as Output. Add another variable to the task called Location of type Vector marked as instance editable and in the Output category.
For this task to be more configurable in the future, it would be helpful to have a property to optionally set a custom max distance away from the actor to roam. This is done by adding a float variable to the task called Distance Away and marking it as instance editable. While not required, it can be useful to mark the category as Parameter to order the variables easily to see what requires being bound and what is optional. Set the default value for Distance Away to 500.0.
Overriding Task Functions to Run Logic
StateTree tasks have 3 phases where their logic can run over their lifetime: entering the state, ticking the state, and exiting the state. Finding the location is something that only needs to be done once per time the task is run. That eliminates needing to use TickState, and it makes sense to find it upon entering the state rather than exiting it. Choose the EnterState option from the dropdown for overriding functions.
In the Event Graph, drag off from the EnterState event and add a node for GetRandomLocationInNavigableRadius. For the Origin, use the context actorās location. Wire the Distance Away variable to the Radius pin. From the Random Location pin, set Location. There are times when this node could fail to find a valid navigable location, and handling that requires that both the bool and vector are stored simultaneously. This can be accomplished by making a struct that has both a vector and bool for doing so in BP, or the task can be created in C++ where it is possible to handle this more easily.
The function should look like this:
One Taskās Output is Another Taskās Input
With the ability to get a location now available, it is time to add the new task to the state. Back in the StateTree editor, add the task before the Move To task by clicking on the arrow outside and to the right of the task dropdown for Move To and selecting Insert from the menu. The task should now be the first in the list. If you would like to use a different value for Distance Away than the default, you can supply the value directly or bind the property to other data available in the StateTree.
In the Move To task, bind the Destination property to STTask_GetRandomLocation->Location by clicking the bind button to the right of Destination. StateTree will populate the name of the task and the property of the task to which it is bound in the taskās details. This can be helpful in tracking down what data is being bound for a given property.
Testing Out the New StateTree
Making a Custom AI Controller
Now that the StateTree has a valid state with logic, it can be tested to see if the logic behaves like expected. Since this StateTree is built using the StateTreeAIComponent schema, the AI controller will need to hold the StateTreeAIComponent. Create a new BP class that derives from DetourCrowdAIController. Name the new class AIC_Wildlife. Optionally, this can be done with the base AIController class as well, but by using the Detour controller, the wildlife can optionally use DetourCrowd for avoidance.
Inside the AI controller, add a new component by clicking on the + Add button on the top left in the Components panel. Add a StateTreeAI component to the BP and name it āStateTreeAIā. Compile the BP. Select the StateTreeAI component and set the StateTree asset in the Details panel to use ST_Wildlife. From the BeginPlay event node, drag off and add the StartLogic (StateTreeAI) node. This will automatically hook the StateTreeAI component up to the target pin of the node.
Setting Up the Test Level
For the initial testing, create a new level by choosing File->New Level from the menu bar. Choose the option for the Basic level. In the new level, add a NavMesh Bounds Volume to the level. Expand the navmesh bounds to encompass the whole of the plane. Save the level and name it WildlifeTest_Lvl.
Place the third-person template character into the level. If the project does not include the template, you can add the template content by clicking the + Add button in the Content Browser and selecting the option for Add Feature or Content Pack⦠then choose the third person template. With the BP_ThirdPersonCharacter selected, search for āControllerā in the Details panel and set the AI Controller Class to use AIC_Wildlife.
Configuring the StateTree for Testing
In ST_Wildlife, it is time to set the context classes to use our testing setup. The AIController class should use AIC_Wildlife, and the Actor context should use BP_ThirdPersonCharacter.
Next, the StateTree needs to be set up to only transition into the Peaceful -> Wander states and not into the Danger states. This can be done in several ways including by adding an entry condition for Danger that would always fail or rearranging the tree so that Peaceful is before Danger. However, the easiest manner for doing this is to right click on the Danger state and uncheck the option for State Enabled. This should make the Danger state darker in color and add the Disabled chip to the Details panel.
This setup will prevent the StateTree from being able to enter Danger or its child states without adding extra pieces to the StateTree. It can be very useful when testing and debugging StateTree to force conditions or prevent certain transitions from happening. Now go back to the level and PIE. The character should move to a random location in the level one time before becoming stationary. Hooray for small victories!
Transition Setup for Peaceful States
Running a state and its tasks are fantastic! However, being able to change behavior and states are exceedingly important. It would be significantly better if the AI in the level would move to a spot, wait a couple seconds, and then move to another random location than the current behavior of only moving to a random location a singular time. The good news is that transitions allow for this to happen. The better news is that setting them up along with new tasks is quite simple.
Adding Transitions to the States
Open the ST_Wildlife asset and select the Wander state. In the Details panel, click the + button next to Transitions to add a transition to the state. Expand the transition and change the Trigger to be On State Succeeded. Change Transition To to go to the Graze state. This will automatically add a default On State Failed transition to return to Root. For completeness sake and having explicit control over the StateTree, add the transition for On State Failed going to Root to the state. Wander should now look like this:
Now to configure the transitions for the other Peaceful states. Graze should transition to Idle using On State Complete, and Idle transitions back to Root when it completes.
Using Delay Tasks
With the current implementation, it will work to move into the Graze state after Wander succeeds. However, Graze will fail to transition to Idle as there are no tasks on the branch Root->Peaceful->Graze that can result in completing. StateTree will handle this by returning to Root to prevent the tree entering a blocked state.
This can be fixed by adding tasks to the other leaf states so they can follow their own transitions. Select the Graze state and add a Delay task. Set the Duration to 3.0. To differentiate Graze from Idle, add an additional task to Graze. For now, set it to use the Debug Text Task. Set the Text to āGrazingā and Font Scale to 2.0. In order for the text to stay with the Actor who is running the StateTree, link the Reference Actor to use the Context Actor of the tree.
While this project is done without custom animations, a task could be created to run the animation on the Actor and use the duration of the animation, with a random deviation if the animation can loop or exit early, that a Delay task could bind to. The animation task could also handle calling FinishTask on its own without the need for a Delay task.
Add a Delay Task to the Idle state. Set the Random Deviation to 1.0 and leave the Delay at 1.0. This will allow for a small space between grazing and moving once more.
Run It Again!
The time has come to run the StateTree once again. Go into the level and PIE. The character should be running to a nearby location to begin grazing. Once there, the grazing text should appear, the text will be removed when transitioning to the Idle state. Now the process should repeat itself. Congratulations on a StateTree-driven agent with behavior!
Creating Reactive Behavior Using Perception
One of the critical behaviors for wildlife is to respond to changes in the environment. In games it is largely perceiving some kind of threat such as predators or players. This can be accomplished with the AI Perception system inside the engine. The next section covers adding in reactivity for transitioning into the Danger states.
Updating AIC_Wildlife to Include Perception
Open the AIC_Wildlife asset. Add an AIPerception component to the AI controller. Compile the BP and re-select the perception component from the Components panel to pull up its details. Click the + button by Sense Config in the Details panel. In the dropdown, choose AI Sight config from the list of senses. Expand the sense config details. Change the Sight Radius and Lose Sight Radius to 1000.0. Set the Peripheral Vision Half Angle to 60.0. Expand the Detection by Affiliation section and ensure that Detect Enemies is the only option checked. The last thing to update is Max Age which should be set to 5.0.
If you are only using BP or do not want to delve into C++ for this, you should set the Detection by Affiliation to enable all 3 options. There is not a way in BP to configure the team agent interface used by the Perception System for agents to be on separate teams. The guide will cover creating custom classes to implement the team agent interface in C++. While the guide will use nodes for hostile actors, you will need to use other nodes such as Get Currently Perceived Actors. You will want to filter through the perceived actors array to ensure the actor is not another wildlife agent if you only wish them to react to the player.
Adding and Implementing the Team Agent Interface
This part will delve into C++ as it is required to implement the team interface used by the perception system. The specific interface is IGenericTeamAgentInterface and is implemented by default on AI Controllers. Now, it will need to be implemented for the player controller and base project character class. In this tutorial, those classes are named STCharacter and STPlayerController. Open the two classes in your IDE.
To implement the interface, the projectās Build.cs file will need to be modified to include the AI module in its dependencies. Open the {Project_Name}.Build.cs file which can be found in the project solution folder under Source/{Project Name}. Add āAIModuleā to the range of strings on the line for PublicDependencyModuleNames.AddRange. This will allow the project to include the header for IGenericTeamAgentInterface.
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "AIModule" });Player Controller Setup
The project used for this was called "ST" which is why the classes and API macro are appended with ST. Using the classes created by your project would be a proper way to follow the examples.
Inside of the player controller header, add the interface header by using #include āGenericTeamAgentInterface.hā above the include of the generated header for the player controller. On the line declaring the class, add , public IGenericTeamAgentInterface to the end of the line. It will look like this:
class ST_API ASTPlayerController : public APlayerController, public IGenericTeamAgentInterfaceNext, add functions from the interface as well as a variable to store the team ID on the controller. The functions being used are:
SetGenericTeamId
GetGenericTeamId
Add a private variable of FGenericTeamId named TeamId to the class as well. The class should now have a section similar to this in the file:
//----------------------------------------------------------------------// // IGenericTeamAgentInterface //----------------------------------------------------------------------// public: virtual void SetGenericTeamId(const FGenericTeamId& NewTeamId) override; virtual FGenericTeamId GetGenericTeamId() const override { return TeamId; } private: FGenericTeamId TeamId;
To implement SetGenericTeamId, open the .cpp file. The function should change the TeamId to the new ID provided. The usual guidance is to only change the ID if they are different, especially if it will be replicated to avoid unnecessary calls to update. The function looks like this:
void ASTPlayerController::SetGenericTeamId(const FGenericTeamId& NewTeamId) { if (TeamId != NewTeamId) { TeamId = NewTeamId; } }
The ID will also need to be set when creating the player controller. This will require adding a constructor to both the .h and .cpp files. In the header, add the constructor for the class such as: ASTPlayerController(const FObjectInitializer& ObjectInitializer);. Go back to the .cpp file and add the definition for the constructor. You will need to call SetGenericTeamId(FGenericTeamId(2)); inside of the constructor so that the TeamID will not be the default value like what is assigned to the AI controller.
ASTPlayerController::ASTPlayerController(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SetGenericTeamId(FGenericTeamId(2)); }
Character Setup
Open the projectās character header and repeat the above steps for including the interface header and inheriting from it. The character will add the same two methods as well as the private variable for the team ID as the player controller used. In addition, it will need to override the PossessedBy method so it can update its local team ID with that being used by the controller. The block of code will look similar to this:
virtual void PossessedBy(AController* NewController) override;
//----------------------------------------------------------------------//
// IGenericTeamAgentInterface
//----------------------------------------------------------------------//
private:
FGenericTeamId TeamId;
public:
virtual void SetGenericTeamId(const FGenericTeamId& TeamID) override;
Next, add the definitions for the PossessedBy and SetGenericTeamId methods to the .cpp file. For SetGenericTeamId, this should be an empty function as the team ID should not be set by any code outside of PossessedBy. Inside of PossessedBy, it will need to cast the new controller being provided to IGenericTeamAgentInterface and if successful, set the team ID to that being used by the controller. An example of that code can be found here:
void ASTCharacter::PossessedBy(AController* NewController) { Super::PossessedBy(NewController); if (IGenericTeamAgentInterface* ControllerAsTeamProvider = Cast<IGenericTeamAgentInterface>(NewController)) { TeamId = ControllerAsTeamProvider->GetGenericTeamId(); } }
Since the character only updates the team ID when being possessed by a controller, there is no need to manually set the team ID in the constructor.
Transitioning to the Danger State
When thinking of setting up the transition for entering Danger, it could reasonably be placed on any of the leaf states, but that means setting up duplicate transitions on each of the leaf states as the AI should be able to go to Danger regardless of the specific action it is taking. This is a prime candidate for using an intermediate state to host the transition. It can be placed on Peaceful and triggered during any of its child states as the state will be active for all of the leaf states.
Open ST_Wildlife and select the Peaceful state. Add a new transition on the state and change the Trigger to be On Event. The On Event transition trigger will listen for a StateTreeEvent with a specified Event Tag that is processed as part of TriggerTransitions. For this event, specify a new gameplay tag to be used that is StateTree.Wildlife.Danger. This tag can be used from BP or code to now reliably transition the tree into the Danger state. Set the Transition To to Danger as well.
Using Perception Events to Send StateTreeEvents
Open AIC_Wildlife and select the AIPerception component. In the Details panel, scroll down to the Events section and click the + next to OnTargetPerceptionInfoUpdated. This will give information about what Actor was sensed as well as the sense that detected them. Since the only configured sense is sight, that will always be the given sense, but it can be used with a hearing config where the information could be used for determining the biggest current threat.
From the event node, drag off from the Update Info pin and break the struct. Add an Is Valid node to check that the Target is valid. Drag off from the Stimulus pin and break that struct. Expand the broken struct and drag from the Successfully Sensed pin to create a Branch node. Connect the Is Valid pin to the Branch node. If it is true, store the perceived Actor in a public, instance editable variable called Perceived Threat of type Actor. The last step is to add a node for Send StateTree Event. The Target is the StateTreeAI component. Make an event from the Event pin using the tag StateTree.Wildlife.Danger and the Origin set to AI Controller. The overall OnTargetPerceptionInfoUpdated event should look like this:
Danger Behavior Creation
Now that the tree can transition to Danger when a hostile actor is perceived, it is time to look at the behavior needed to run each state. For Flee, the behavior is very similar to Wander. The biggest difference is how the location to move is selected. While some randomness is good, it would be preferable to run away from the perceived threat. This requires a new task to be created.
Get Flee Location
Create a new BP StateTreeTask called STTask_GetFleeLocation. Open STTask_GetFleeLocation. Add Context variables for Actor and AI Controller and an Output variable for Flee Location of type Vector. This will take care of the needed bindings in the StateTree. Override the function for EnterState. In the event graph, get the AI Controller and get its AI Perception component. Drag the perception component pin and add an Is Valid node for it. Drag the perception component pin again and add the node for GetPerceivedHostileActors. Add a branch to check that the perceived hostile actors array is not empty. If the array contains any elements, use the Get Unit Direction (Vector) node to calculate the unit direction from the first actor in the arrayās location to the context Actorās location. Multiple the value of the vector by a float of 1000, and then add the result to the Context Actorās location. This new vector will serve as the Origin of a GetRandomLocationInNavigableRadius node. Set the Radius of the node to be 250. Use the Random Location pin to set the Output Flee Location.
Create a function called GetRandomLocation for STTask_GetFleeLocation. This function will serve as a backup to get a randomized location in the event one of the checks fails. The function gets a random location in navigable radius around the Context Actor and sets the location into Flee Location.
You may be thinking it would be much simpler to use EQS for this, and you are absolutely correct. There is a task for running EQS queries in StateTree that will be included in 5.5 and is currently available in the UE5 Main branch.
Finalizing the Details of Flee
Back in ST_Wildlife, add the new STTask_GetFleeLocation to the Flee state. Also add a Move To task to the state. For STTask_GetFleeLocation, all of the properties should be bound automatically as they are pulled from the Context data or an Output that cannot be bound. In Move To, bind the Destination property to use the Flee Location of STTask_GetFleeLocation. Now add a transition for Flee that moves to Assess Situation when the state completes.
Assess Surroundings Setup
Assess Surroundings has simple logic needs as it is a state for giving a brief pause when fleeing to see if the wildlife is far enough away from the threat to be considered safe or if it should continue to flee. As such, the only task needed is a basic Delay task. The Duration should be something small such as 0.2 similar to how a rabbit or squirrel will pause when running for a brief moment to orient themselves.
The true magic for Assess Surroundings comes in how it decides to transition to other states. If the actor has gained sufficient distance from the threat to be safe, it should proceed into the Watch Threat step, but if it is still too close, the state will transition back into the Flee state. This can be expressed with two transitions which both use On State Completed. The second transition is the simplest and will go directly to Flee if selected. The first transition however will go to Watch Threat and have a condition added. StateTree comes with a simple Distance Compare condition, but it requires that the properties already be distilled to their locations and cannot take in Actors from which to extrapolate the location. It is time to create the first StateTree Condition.
Create a new BP class with a parent class of StateTreeConditionBlueprintBase and name the condition STCond_DistanceBetweenActors. Open the new BP. StateTreeConditions only have one function called Receive Test Condition that returns a bool for if it was successful. Add two Input variables for First Actor and Second Actor. Add a float variable for Distance that is public and instance editable. In the function graph, get the location of both Actor variables and use the Distance Squared node for calculating the distance between the locations. Check if the squared distance of the actors is greater than Distance^2 and return the result. This could be done in C++ and follow the other engine conditions that allow for supplying the comparison type if you so choose.
Add the new condition to the first transition of Assess Surroundings that is attempting to go to Watch Threat. Bind the First Actor to the Context Actor and the Second Actor to the AI Controllerās Perceived Threat. Set the Distance to 1000. The stateās details should look like this:
Keeping an Eye on the Threat
Watch Threat is all about looking back at where the threat is. If the threat gets too close, the wildlife should run away again, but if the threat is gone or no longer chasing, the wildlife should resume its normal pattern of life. Start by adding a transition that goes to Peaceful when the state completes. Add another transition that uses On Event with the StateTree.Wildlife.Danger Event Tag to go back into Flee. This is just like the transition used on Peaceful and can be triggered by the perception event used in the AI controller without more code being needed.
Now for the behavior of the state, Watch Threat needs to look at the threat as well as have a time for when the state should end if the threat is no longer a worry. To accomplish the second behavior, add a Delay task to the state. Set the Duration to 5.0 with a Random Deviation of 2.0. This will allow for the wildlife to pause long enough to truly seem wary of the threat. These values can easily be tweaked to suit the projectās specific desires of the wildlife.
To watch the threat will require a new task to be created. Create a new BP class from StateTreeTaskBlueprintBase called STTask_FocusOnHostile. Open the BP. Add a Context variable for AI Controller. Add a function to the BP called Look at Hostile. Open the functions graph. The initial setup for this task is identical to what was done for STTask_GetFleeLocation in that the AI Perception component and the perceived hostile actors array should both be checked for being valid. If both parts are valid, get the first entry in the perceived hostile actors array and get its location. Use the Find Look at Rotation node to get the rotation needed to look from the currently controlled pawnās location to the location of the first entry in the perceived hostile actors. The rotation will be used in the node Set Actor Rotation which will use the controlled pawn as its Target.
Create another function for the BP called Focus on Last Perceived Threat which will attempt to look at the threat stored in AIC_Wildlifeās Perceived Threat. Open this function. The first step is to cast the AIController to AIC_Wildlife. If the cast is successful, check if the Perceived Threat of the controller is valid. If the Perceived Threat is valid, find the look at rotation and set the actorās location like what was done in Look at Hostile. Add Finish Task nodes for the failure cases of the cast and Is Valid that has Succeeded set to false. The state should not continue to run if there is no threat to currently watch.
In Look at Hostile, add calls to Focus on Last Perceived Threat for the failure cases of invalid perception component or an empty perceived hostile actors array. On the main event graph, override the EnterState event and wire it up to call Look at Hostile. Here are the function graphs:
In ST_Wildlife, add the new STTask_FocusOnHostile to the Watch Threat state. Watch Threat should look like this:
Final Danger Changes
It is now time to re-enable the Danger state by right-clicking the state and clicking the option for State Enabled from the menu. Since Danger is the first state under Root, it will be the first considered for selection which is not the desired flow of the tree. To prevent from going into Danger needlessly, an enter condition can be placed onto it. The only time Danger should be considered is if there are currently perceived hostile actors.
Create a new BP derived from StateTreeConditionBlueprintBase called STCond_HasPerceivedHostiles. Open the new BP. Add a Context variable for the AIController. For Receive Test Condition, this is going to use a setup similar to what was used for the previous two tasks of checking if the perception component of the AI controller is valid and that the perceived hostile actors array is not empty. In the event of an invalid perception component, the condition should return false. The condition looks like this:
Add the condition to the Enter Conditions of Danger.
Testing It Out One Last Time
With all the changes to StateTree, it is now time to test it all out together. Open the level and PIE. The AI should now react to the player entering into their sight radius by running away from the player. It can be easier to visualize the sight cone by enabling the Gameplay Debugger and the Perception category. The Gameplay Debugger also shows the current state of the StateTree being run by the AI with a list of active states and tasks.
This is a great place to explore adding more characters running ST_Wildlife to see how they avoid each other and the performance of running lots of separate StateTree components at once. Add more states. Change a behavior pattern. Use custom meshes and animations. Create a predator animal AI that chases the others.