Tutorial: Your First 60 Minutes with StateTree

Tutorial: Your First 60 Minutes with StateTree

A practical guide to understanding and working with StateTree as it relates to AI. It covers key concepts, terminology, and execution flow for StateTree. This is accomplished while building a basic wildlife AI agent using the new StateTreeAIComponent.

3 Likes

Hi,I found this project on github to study-(GitHub - Ji-Rath/MassAITesting: A project primarily used to test UE5 Mass AI system). I have a lot of problems.I refactored it with version 5.4. Massbehavior Statetree always only the EnterState,Will not run tick. Even if I return to the running State.Its LastTick state will still change to success or failed after enterstate,Could not get LastTick State to running state. how can i do :neutral_face:

This is really great seeing a guide on this stuff. I spent a couple weeks digging through the source and doing experiments and wrote a guide for myself. I’d like to add to or clarify some points from the guide.

Under StateTree Execution Flow / StateTree State Selection

  1. 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.

From my experience this should be:
When changing states, the tasks and states are exited in order from the leaf of the current state branch until the common ancestor of the next state’s branch, and from the common ancestor states are entered through the next branch to reach the next state.

So if you’re current state is Root.Colors.Green and you go to Root.Colors.Red, only Green is exited before entering Red.

Also to clarify the point on StateTree Transition Selection. I like to think that Tasks can Finish or not Finish, and Tasks finishing trigger a State’s Completion (Succeed or Failed).

When a State enters a Completed state, it triggers a check on the Transitions.

If a State has no Transitions it will jump back up the StateTree passing on the Completed status to the ancestors. This could go all the way back up to the Root State which will force reentry of the whole StateTree! In other words, if you Complete a State without Transitions, you’ll loop over the StateTree re-entering States which may not be what you intended! To interrupt this, you can add a Transition to the Parent of a Child State like On State Completed transition to the Parent State.

Also it needs to be mentioned that every State on the active branch gets ticked every frame to run Tasks and check for Transitions.

1 Like

Great guide! Thanks for posting it!

I found an issue in the code for the PossessedBy function, in the section titled Character Setup. If the user doesn’t call

Super::PossessedBy(NewController);

it will break the possession functionality from ACharacter and APawn. And that line’s missing from the function example in the tutorial.

It might take hours for new users to realize what’s the issue or have them give up on the tutorial or Unreal altogether.

Cheers!

2 Likes

Let me guess… STCharacter is whatever my character base C++ class that was automatically created when the project was created using 3rd person template (C++). And STPlayerController I have to create based on APlayerController.

Regarding:
Adding and Implementing the Team Agent Interface

I feel there is something missing here trying to do this in UE 5.4.2
I can’t get it to work.

It never assigns the player as an enemy.

? Assuming it somehow requires assigning STPlayerController as the default player controller, but project settings are greyed out.
So I make a bunch of blueprints based on these classes in order to assign them in (new BP) of a game mode. And it still doesn’t work.

I assumed TeamId of 2 was somehow supposed to make an enemy of our player, but it doesn’t.

The AIPerception component only seems to notice the player when all the affilitations are ticked, but it is still not an enemy, still doesn’t flee.

My first encounter with:
IGenericTeamAgentInterface
Which seems to be something arcane, judging by the ancient and unclear topics about it.

So State Tree is supposed to be an alternative to Behaviour Trees.
And it should be, but this is not straight forward at all.

Yeah, the C++ part of that tutorial seems to assume you’ll only try to do it if you are already very familiar with Unreal. It doesn’t even say you need your Blueprints and GameMode setup to spawn the new types of PlayerController and Character.

On code, besides overriding the functions as described in the tutorial:

  • ASTCharacter should have ACharacter as base class.
  • Make sure PosssessedBy calls Super::PossessedBy like I mentioned.
  • ASTPlayerController should have APlayerController as base class.

On Blueprint:

  • Setup BP_ThirdPersonGameMode to use STPlayerController as the Player Controller Class.
  • Reparent BP_ThirdPersonCharacter to the STCharacter class by going to the BP Class Settings and changing the Parent Class in the details panel.

Now, when you play:

  • The PlayerController for you, the human player, will assign itself the team ID 2, because of STPlayerController’s construction code.
  • The Character, when possessed by the PlayerController, will execute the STCharacter’s version of PossessedBy and copy the PlayerController’s ID for itself, so it’ll be 2 as well.
  • The AI and enemy character will have the default ID of 255. That will make them enemies because the default attitude solver for the FGenericTeamId deems different IDs as ETeamAttitude::Enemy. That default solver is the DefaultTeamAttitudeSolver method in AIInterfaces.cpp.

OnTargetPerceptionInfoUpdated

It only detects the player as a friendly (when affiliation Friendly is checked)

Sending the ‘Danger’ State Tree Event, on the Limb state ‘Danger’ the children are never selected - not even a dummy delay and debug test task.

The tease about EQS is less exciting when this does not work.

Maybe it would work if I simply duplicated the character instead as there may be a problem with this TeamId. It’s certainly confusing enough with this short cut version of a tutorial. But none of this seems easily exposed - like set the player’s team or even see the variable, or the AI Perception Component’s ‘Get Perceived Hostile Actors’ array and how to use that other than it only ever returning a blank.

State Tree is constantly running the condition check, which does not seem sensible to have this limb activated at all times.

I can get it to the FLEE state by transitioning to flee from PEACEFUL, but it never enters WATCH from the assess state.
Probably because the ‘Get Perceived Hostile Actors’ array is never set to anything other than None - if we set this soemwhere I missed it even after several re readings.

I have a project with Limb states that actually work, but it does not involve the AIPerception Component.

Which I now trust as much as I trust the UPawnSensing Component.

Have you debugged the custom functions in C++ for the custom ST classes? I figured what I was doing wrong when my PossessedBy breakpoint was not hitting. Also check if the custom PlayerController GetGenericTeamId is hit and returns the expected value of 2.

Perhaps even add a breakpoint to DefaultTeamAttitudeSolver, to see what’s going on when it compares the two teams.

The tutorial is not focused on the C++ part, so it’s lacking a lot there. It’s definitely intended for those familiar with the engine and C++, so either leave it for later or debug it as much as you can until you figure out what’s missing. Don’t lose hope. Mine’s working, so the feature does work. It’s just not Blueprint friendly.

I WISH I had only spent an hour on this particualr tutorial/feature.

DefaultTeamAttitudeSolver
this is buried in the engine, why should I be poking around here…

Not focused on C++ and not blueprint friendly. Kind of stretching the definitions of tutorial and features.