Files
UnrealEngine/Engine/Plugins/Runtime/StateTree/Source/StateTreeTestSuite/Private/StateTreeStatePathTest.cpp
2025-05-18 13:04:45 +08:00

455 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreeTest.h"
#include "StateTreeTestBase.h"
#include "StateTreeTestTypes.h"
#include "StateTreeCompilerLog.h"
#include "StateTreeEditorData.h"
#include "StateTreeCompiler.h"
#include "Conditions/StateTreeCommonConditions.h"
#define LOCTEXT_NAMESPACE "AITestSuite_StateTreeTest"
UE_DISABLE_OPTIMIZATION_SHIP
namespace UE::StateTree::Tests
{
struct FStateTreeTest_StatePath_LinkStates : FStateTreeTestBase
{
virtual bool InstantTest() override
{
//Tree 1
// Root
// RootState1 -> Next
// StateLinkedTree (Sub1) -> Next
// StateLinkedTree (Sub1) -> Next
// RootStateLinkedTree (Sub2) -> Next
// RootStateLinkedTree (Sub2) -> Next
// RootStateLinkedTree (Tree2) -> Next
// RootStateLinkedTree (Tree3) -> Next # Tree3 fails
// RootStateLinkedTree (Tree2) -> Next
// RootState2 -> Root
// Sub1
// StateLinkedTree (Tree2) -> Next
// StateLinkedTree (Tree2) -> Next
// StateLinkedTree (Sub2) -> Next
// StateLinkedTree (Sub2) -> Next
// Sub1State1 -> Success
// Sub2
// Sub2State1 -> Next
// Sub2State2 -> Success
//Tree 2
// Root
// RootStateLinkedTree (Sub1) -> Next
// RootStateLinkedTree (Sub1) -> Next
// RootState1 -> Success
// Sub1
// Sub1State1 -> Next
// Sub1State2 -> Success
//Tree 3
// Root
// RootStateLinkedTree (Sub1) -> Success
// Sub1
// Sub1State1 -> cond fail -> success
UStateTree& StateTree1 = NewStateTree();
UStateTree& StateTree2 = NewStateTree();
UStateTree& StateTree3 = NewStateTree();
// Tree1
{
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
UStateTreeState& Root = EditorData.AddSubTree("Tree1Root");
UStateTreeState& Sub1 = EditorData.AddSubTree("Tree1Sub1");
Sub1.Type = EStateTreeStateType::Subtree;
UStateTreeState& Sub2 = EditorData.AddSubTree("Tree1Sub2");
Sub2.Type = EStateTreeStateType::Subtree;
// Root
{
UStateTreeState& Tree1RootState1 = Root.AddChildState("Tree1RootState1", EStateTreeStateType::State);
UStateTreeState& Tree1RootStateSub2A = Root.AddChildState("Tree1RootStateSub2A", EStateTreeStateType::Linked);
//Tree1RootState1
{
{
UStateTreeState& ChildState = Tree1RootState1.AddChildState("Tree1State1StateSub1A", EStateTreeStateType::Linked);
ChildState.SetLinkedState(Sub1.GetLinkToState());
ChildState.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& ChildState = Tree1RootState1.AddChildState("Tree1State1StateSub1B", EStateTreeStateType::Linked);
ChildState.SetLinkedState(Sub1.GetLinkToState());
FStateTreeTransition& Transition = ChildState.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::GotoState);
Transition.State = Tree1RootStateSub2A.GetLinkToState();
}
}
//Tree1RootStateSub2A
{
Tree1RootStateSub2A.SetLinkedState(Sub2.GetLinkToState());
Tree1RootStateSub2A.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree1RootStateSub2B", EStateTreeStateType::Linked);
State.SetLinkedState(Sub2.GetLinkToState());
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree1RootStateLinkTree2A", EStateTreeStateType::LinkedAsset);
State.SetLinkedStateAsset(&StateTree2);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree1RootStateLinkTree3", EStateTreeStateType::LinkedAsset);
State.SetLinkedStateAsset(&StateTree3);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree1RootStateLinkTree2B", EStateTreeStateType::LinkedAsset);
State.SetLinkedStateAsset(&StateTree2);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree1RootState1", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState);
Transition.State = Root.GetLinkToState();
Transition.bDelayTransition = true;
Transition.DelayDuration = 0.999f;
}
}
//Tree1Sub1
{
{
UStateTreeState& State = Sub1.AddChildState("Tree1Sub1StateLinkTree2A", EStateTreeStateType::LinkedAsset);
State.SetLinkedStateAsset(&StateTree2);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Sub1.AddChildState("Tree1Sub1StateLinkTree2B", EStateTreeStateType::LinkedAsset);
State.SetLinkedStateAsset(&StateTree2);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Sub1.AddChildState("Tree1Sub1StateSub2A", EStateTreeStateType::Linked);
State.SetLinkedState(Sub2.GetLinkToState());
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Sub1.AddChildState("Tree1Sub1StateSub2B", EStateTreeStateType::Linked);
State.SetLinkedState(Sub2.GetLinkToState());
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Sub1.AddChildState("TreeASub1State1", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 0.999f;
}
}
//Tree1Sub2
{
{
UStateTreeState& State = Sub2.AddChildState("Tree1Sub2State1", EStateTreeStateType::State);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Sub2.AddChildState("Tree1Sub2State2", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 0.999f;
}
}
}
//Tree 2
{
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
UStateTreeState& Root = EditorData.AddSubTree("Tree2StateRoot");
UStateTreeState& Sub1 = EditorData.AddSubTree("Tree2Sub1");
Sub1.Type = EStateTreeStateType::Subtree;
//Root
{
{
UStateTreeState& State = Root.AddChildState("Tree2RootStateSub1A", EStateTreeStateType::Linked);
State.SetLinkedState(Sub1.GetLinkToState());
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree2RootStateSub1B", EStateTreeStateType::Linked);
State.SetLinkedState(Sub1.GetLinkToState());
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Root.AddChildState("Tree2RootState1", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 0.999f;
}
}
//Tree2Sub1
{
{
UStateTreeState& State = Sub1.AddChildState("Tree2Sub1State1", EStateTreeStateType::State);
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
}
{
UStateTreeState& State = Sub1.AddChildState("Tree2Sub1State2", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 0.999f;
}
}
}
//Tree 3
{
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree3.EditorData);
UStateTreeState& Root = EditorData.AddSubTree("Tree3StateRoot");
UStateTreeState& Sub1 = EditorData.AddSubTree("Tree3Sub1");
Sub1.Type = EStateTreeStateType::Subtree;
//Root
{
UStateTreeState& State = Root.AddChildState("Tree3RootStateSub1A", EStateTreeStateType::Linked);
State.SetLinkedState(Sub1.GetLinkToState());
State.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::Succeeded);
}
//Tree3Sub1
{
UStateTreeState& State = Sub1.AddChildState("Tree3Sub1State1", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 0.999f;
TStateTreeEditorNode<FStateTreeRandomCondition>& Cond = State.AddEnterCondition<FStateTreeRandomCondition>();
Cond.GetNode().EvaluationMode = EStateTreeConditionEvaluationMode::ForcedFalse;
}
}
// Compile tree
{
FStateTreeCompilerLog Log;
FStateTreeCompiler Compiler(Log);
const bool bResult = Compiler.Compile(StateTree3);
AITEST_TRUE(TEXT("StateTree3 should get compiled"), bResult);
}
{
FStateTreeCompilerLog Log;
FStateTreeCompiler Compiler(Log);
const bool bResult = Compiler.Compile(StateTree2);
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult);
}
{
FStateTreeCompilerLog Log;
FStateTreeCompiler Compiler(Log);
const bool bResult = Compiler.Compile(StateTree1);
AITEST_TRUE(TEXT("StateTree1 should get compiled"), bResult);
}
// Create context
FStateTreeInstanceData InstanceData;
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
{
const bool bInitSucceeded = Exec.IsValid();
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
}
{
UE::StateTree::FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("ExecPath should be empty."), ExecPath.Num() == 0);
}
// Test variable and helper functions
int32 ActiveCounter = 0;
TArray<UE::StateTree::FActiveFrameID> PreviousFrameIDs;
auto PreTestFrameId = [&PreviousFrameIDs, &InstanceData]()
{
PreviousFrameIDs.Reset();
for (int32 Index = 0; Index < InstanceData.GetExecutionState()->ActiveFrames.Num(); ++Index)
{
PreviousFrameIDs.Add(InstanceData.GetExecutionState()->ActiveFrames[Index].FrameID);
}
};
auto TestFrameId = [&PreviousFrameIDs, &InstanceData](int32 CorrectAmount)
{
int32 Index = 0;
for (; Index < PreviousFrameIDs.Num() && Index < CorrectAmount; ++Index)
{
if (PreviousFrameIDs[Index] != InstanceData.GetExecutionState()->ActiveFrames[Index].FrameID)
{
return false;
}
}
for (; Index < InstanceData.GetExecutionState()->ActiveFrames.Num() && Index < PreviousFrameIDs.Num(); ++Index)
{
if (PreviousFrameIDs[Index] == InstanceData.GetExecutionState()->ActiveFrames[Index].FrameID)
{
return false;
}
}
return true;
};
// Start tests
{
using namespace UE::StateTree;
const EStateTreeRunStatus Status = Exec.Start();
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2A", "Tree2StateRoot", "Tree2RootStateSub1A", "Tree2Sub1", "Tree2Sub1State1"));
const FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("Has the correct number of path elements"), ExecPath.Num() == 9);
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 4);
{
const FActiveFrameID FirstFrameID = InstanceData.GetExecutionState()->ActiveFrames[0].FrameID;
AITEST_TRUE(TEXT("Frame for Tree1Root is active"), FirstFrameID == FActiveFrameID(++ActiveCounter));
AITEST_TRUE(TEXT("State Tree1Root is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_TRUE(TEXT("State Tree1Root is active"), ExecPath.Contains(FActiveState(FirstFrameID, FActiveStateID(ActiveCounter), FStateTreeStateHandle(0))));
AITEST_TRUE(TEXT("State Tree1RootState1 is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_TRUE(TEXT("State Tree1RootState1 is active"), ExecPath.Contains(FActiveState(FirstFrameID, FActiveStateID(ActiveCounter), FStateTreeStateHandle(1))));
AITEST_TRUE(TEXT("State Tree1State1StateSub1A is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_TRUE(TEXT("State Tree1State1StateSub1A is active"), ExecPath.Contains(FActiveState(FirstFrameID, FActiveStateID(4), FStateTreeStateHandle(2))));
}
{
AITEST_TRUE(TEXT("Frame for Tree1Sub1 is active"), InstanceData.GetExecutionState()->ActiveFrames[1].FrameID == FActiveFrameID(++ActiveCounter));
AITEST_TRUE(TEXT("State Tree1Sub1 is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_TRUE(TEXT("State Tree1Sub1StateLinkTree2A is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
}
{
AITEST_TRUE(TEXT("Frame for Tree2StateRoot is active"), InstanceData.GetExecutionState()->ActiveFrames[2].FrameID == FActiveFrameID(++ActiveCounter));
AITEST_TRUE(TEXT("State Tree2StateRoot is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_TRUE(TEXT("State Tree2RootStateSub1A is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
}
{
AITEST_TRUE(TEXT("Frame for Tree2Sub1 is active"), InstanceData.GetExecutionState()->ActiveFrames[3].FrameID == FActiveFrameID(++ActiveCounter));
AITEST_TRUE(TEXT("State Tree2Sub1 is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_TRUE(TEXT("State Tree2Sub1State1 is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
}
{
AITEST_FALSE(TEXT("No accidental increment"), ExecPath.Contains(FActiveStateID(ActiveCounter+1)));
}
Exec.LogClear();
}
{
using namespace UE::StateTree;
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2A", "Tree2StateRoot", "Tree2RootStateSub1A", "Tree2Sub1", "Tree2Sub1State2"));
FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("Has the correct number of path elements"), ExecPath.Num() == 9);
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 4);
AITEST_TRUE(TEXT("State Tree2Sub1State2 is active"), ExecPath.Contains(FActiveStateID(++ActiveCounter)));
AITEST_FALSE(TEXT("No accidental increment"), ExecPath.Contains(FActiveStateID(ActiveCounter + 1)));
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(4));
Exec.LogClear();
}
{
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2A", "Tree2StateRoot", "Tree2RootStateSub1B", "Tree2Sub1", "Tree2Sub1State1"));
const UE::StateTree::FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("Has the correct number of path elements"), ExecPath.Num() == 9);
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 4);
//@todo Temporary reverted the frameID fixed until TransitionID
//AITEST_TRUE(TEXT("The last frame changed"), TestFrameId(3));
Exec.LogClear();
}
{
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2A", "Tree2StateRoot", "Tree2RootStateSub1B", "Tree2Sub1", "Tree2Sub1State2"));
UE::StateTree::FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("Has the correct number of path elements"), ExecPath.Num() == 9);
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 4);
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(4));
Exec.LogClear();
}
{
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2A", "Tree2StateRoot", "Tree2RootState1"));
UE::StateTree::FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("Has the correct number of path elements"), ExecPath.Num() == 7);
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 3);
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(3));
Exec.LogClear();
}
{
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2B", "Tree2StateRoot", "Tree2RootStateSub1A", "Tree2Sub1", "Tree2Sub1State1"));
UE::StateTree::FActiveStatePath ExecPath = InstanceData.GetExecutionState()->GetActiveStatePath();
AITEST_TRUE(TEXT("Has the correct number of path elements"), ExecPath.Num() == 9);
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 4);
//@todo Temporary reverted the frameID fixed until TransitionID
//AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(2));
Exec.LogClear();
}
{
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2B", "Tree2StateRoot", "Tree2RootStateSub1A", "Tree2Sub1", "Tree2Sub1State2"));
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(4));
Exec.LogClear();
PreTestFrameId();
Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2B", "Tree2StateRoot", "Tree2RootStateSub1B", "Tree2Sub1", "Tree2Sub1State1"));
//@todo Temporary reverted the frameID fixed until TransitionID
//AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(3));
Exec.LogClear();
PreTestFrameId();
Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2B", "Tree2StateRoot", "Tree2RootStateSub1B", "Tree2Sub1", "Tree2Sub1State2"));
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 4);
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(4));
Exec.LogClear();
PreTestFrameId();
Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateLinkTree2B", "Tree2StateRoot", "Tree2RootState1"));
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 3);
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(3));
Exec.LogClear();
}
{
PreTestFrameId();
EStateTreeRunStatus Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateSub2A", "Tree1Sub2", "Tree1Sub2State1"));
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 3);
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(2));
Exec.LogClear();
PreTestFrameId();
Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Should be in the correct state"), Exec.ExpectInActiveStates("Tree1Root", "Tree1RootState1", "Tree1State1StateSub1A", "Tree1Sub1", "Tree1Sub1StateSub2A", "Tree1Sub2", "Tree1Sub2State2"));
AITEST_TRUE(TEXT("Has the correct number of active states"), InstanceData.GetExecutionState()->ActiveFrames.Num() == 3);
AITEST_TRUE(TEXT("All frames are the same"), TestFrameId(3));
Exec.LogClear();
}
Exec.Stop();
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_StatePath_LinkStates, "System.StateTree.StatePath.LinkStates");
} // namespace UE::StateTree::Tests
UE_ENABLE_OPTIMIZATION_SHIP
#undef LOCTEXT_NAMESPACE