1537 lines
70 KiB
C++
1537 lines
70 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_TransitionPriority : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
/*
|
|
- Root
|
|
- State1 : Task1 -> Succeeded
|
|
- State1A : Task1A -> Next
|
|
- State1B : Task1B -> Next
|
|
- State1C : Task1C
|
|
|
|
Task1A completed first, transitioning to State1B.
|
|
Task1, Task1B, and Task1C complete at the same time, we should take the transition on the first completed state (State1).
|
|
*/
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1")));
|
|
UStateTreeState& State1A = State1.AddChildState(FName(TEXT("State1A")));
|
|
UStateTreeState& State1B = State1.AddChildState(FName(TEXT("State1B")));
|
|
UStateTreeState& State1C = State1.AddChildState(FName(TEXT("State1C")));
|
|
|
|
auto& Task1 = State1.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
Task1.GetNode().TicksToCompletion = 2;
|
|
State1.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded);
|
|
|
|
auto& Task1A = State1A.AddTask<FTestTask_Stand>(FName(TEXT("Task1A")));
|
|
Task1A.GetNode().TicksToCompletion = 1;
|
|
State1A.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState);
|
|
|
|
auto& Task1B = State1B.AddTask<FTestTask_Stand>(FName(TEXT("Task1B")));
|
|
Task1B.GetNode().TicksToCompletion = 2;
|
|
State1B.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState);
|
|
|
|
auto& Task1C = State1C.AddTask<FTestTask_Stand>(FName(TEXT("Task1C")));
|
|
Task1C.GetNode().TicksToCompletion = 2;
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE("StateTree should get compiled", bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE("StateTree should init", bInitSucceeded);
|
|
|
|
const TCHAR* TickStr(TEXT("Tick"));
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* ExitStateStr(TEXT("ExitState"));
|
|
const TCHAR* StateCompletedStr(TEXT("StateCompleted"));
|
|
|
|
// Start and enter state
|
|
Status = Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree Task1 should enter state"), Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
AITEST_TRUE(TEXT("StateTree Task1A should enter state"), Exec.Expect(Task1A.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Transition from Task1A to Task1B
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task1A should complete"), Exec.Expect(Task1A.GetName(), StateCompletedStr));
|
|
AITEST_TRUE(TEXT("StateTree Task1B should enter state"), Exec.Expect(Task1B.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Task1 completes, and we should take State1 transition.
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE("StateTree Task1 should complete", Exec.Expect(Task1.GetName(), StateCompletedStr));
|
|
AITEST_EQUAL(TEXT("Tree execution should stop on success"), Status, EStateTreeRunStatus::Succeeded);
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionPriority, "System.StateTree.Transition.Priority");
|
|
|
|
struct FStateTreeTest_TransitionPriorityEnterState : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& State0 = Root.AddChildState(FName(TEXT("State0")));
|
|
UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1")));
|
|
UStateTreeState& State1A = State1.AddChildState(FName(TEXT("State1A")));
|
|
UStateTreeState& State2 = Root.AddChildState(FName(TEXT("State2")));
|
|
UStateTreeState& State3 = Root.AddChildState(FName(TEXT("State3")));
|
|
|
|
auto& Task0 = State0.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
State0.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &State1);
|
|
|
|
auto& Task1 = State1.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
Task1.GetNode().EnterStateResult = EStateTreeRunStatus::Failed;
|
|
State1.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &State2);
|
|
|
|
auto& Task1A = State1A.AddTask<FTestTask_Stand>(FName(TEXT("Task1A")));
|
|
State1A.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &State3);
|
|
|
|
auto& Task2 = State2.AddTask<FTestTask_Stand>(FName(TEXT("Task2")));
|
|
State2.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded);
|
|
|
|
auto& Task3 = State3.AddTask<FTestTask_Stand>(FName(TEXT("Task3")));
|
|
State3.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded);
|
|
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE("StateTree should get compiled", bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE("StateTree should init", bInitSucceeded);
|
|
|
|
const TCHAR* TickStr(TEXT("Tick"));
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* ExitStateStr(TEXT("ExitState"));
|
|
const TCHAR* StateCompletedStr(TEXT("StateCompleted"));
|
|
|
|
// Start and enter state
|
|
Status = Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Transition from State0 to State1, it should fail (Task1), and the transition on State1->State2 (and not State1A->State3)
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should complete"), Exec.Expect(Task0.GetName(), StateCompletedStr));
|
|
AITEST_TRUE(TEXT("StateTree Task2 should enter state"), Exec.Expect(Task2.GetName(), EnterStateStr));
|
|
AITEST_FALSE(TEXT("StateTree Task3 should not enter state"), Exec.Expect(Task3.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionPriorityEnterState, "System.StateTree.Transition.PriorityEnterState");
|
|
|
|
struct FStateTreeTest_TransitionNextSelectableState : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
// Root
|
|
// State0: OnCompleted->Nextselectable
|
|
// State1: EnterCondition fails
|
|
// State2: EnterCondition fails
|
|
// State3: EnterCondition successed. OnCompleted->Succeeded
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& State0 = Root.AddChildState(FName(TEXT("State0")));
|
|
UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1")));
|
|
UStateTreeState& State2 = Root.AddChildState(FName(TEXT("State2")));
|
|
UStateTreeState& State3 = Root.AddChildState(FName(TEXT("State3")));
|
|
|
|
auto& EvalA = EditorData.AddEvaluator<FTestEval_A>();
|
|
EvalA.GetInstanceData().bBoolA = true;
|
|
|
|
auto& Task0 = State0.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
State0.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextSelectableState);
|
|
|
|
// Add Task 1 with Condition that will always fail
|
|
auto& Task1 = State1.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
auto& BoolCond1 = State1.AddEnterCondition<FStateTreeCompareBoolCondition>();
|
|
|
|
EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond1, TEXT("bLeft"));
|
|
BoolCond1.GetInstanceData().bRight = !EvalA.GetInstanceData().bBoolA;
|
|
|
|
// Add Task 2 with Condition that will always fail
|
|
auto& Task2 = State2.AddTask<FTestTask_Stand>(FName(TEXT("Task2")));
|
|
auto& BoolCond2 = State2.AddEnterCondition<FStateTreeCompareBoolCondition>();
|
|
|
|
EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond2, TEXT("bLeft"));
|
|
BoolCond2.GetInstanceData().bRight = !EvalA.GetInstanceData().bBoolA;
|
|
|
|
// Add Task 3 with Condition that will always succeed
|
|
auto& Task3 = State3.AddTask<FTestTask_Stand>(FName(TEXT("Task3")));
|
|
auto& BoolCond3 = State3.AddEnterCondition<FStateTreeCompareBoolCondition>();
|
|
State3.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded);
|
|
|
|
EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond3, TEXT("bLeft"));
|
|
BoolCond3.GetInstanceData().bRight = EvalA.GetInstanceData().bBoolA;
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* TickStr = TEXT("Tick");
|
|
const TCHAR* EnterStateStr = TEXT("EnterState");
|
|
const TCHAR* ExitStateStr = TEXT("ExitState");
|
|
const TCHAR* StateCompletedStr = TEXT("StateCompleted");
|
|
|
|
// Start and enter state
|
|
Exec.Start();
|
|
AITEST_TRUE(TEXT("Should be in active state"), Exec.ExpectInActiveStates("Root", "State0"));
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Transition from State0 and tries to select State1. It should fail (Task1 and Tasks2) and because transition is set to "Next Selectable", it should now select Task 3 and Enter State
|
|
Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("Should be in active state"), Exec.ExpectInActiveStates("Root", "State3"));
|
|
AITEST_TRUE(TEXT("StateTree Task0 should complete"), Exec.Expect(Task0.GetName(), StateCompletedStr));
|
|
AITEST_FALSE(TEXT("StateTree Task1 should not enter state"), Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
AITEST_FALSE(TEXT("StateTree Task2 should not enter state"), Exec.Expect(Task2.GetName(), EnterStateStr));
|
|
AITEST_TRUE(TEXT("StateTree Task3 should enter state"), Exec.Expect(Task3.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Complete Task3
|
|
EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should succeed"), Status, EStateTreeRunStatus::Succeeded);
|
|
AITEST_TRUE(TEXT("Should be in active state"), Exec.GetActiveStateNames().Num() == 0);
|
|
AITEST_TRUE(TEXT("StateTree Task3 should complete"), Exec.Expect(Task3.GetName(), StateCompletedStr));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionNextSelectableState, "System.StateTree.Transition.NextSelectableState");
|
|
|
|
struct FStateTreeTest_TransitionNextWithParentData : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& State0 = Root.AddChildState(FName(TEXT("State0")));
|
|
UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1")));
|
|
UStateTreeState& State1A = State1.AddChildState(FName(TEXT("State1A")));
|
|
|
|
auto& RootTask = Root.AddTask<FTestTask_B>(FName(TEXT("RootTask")));
|
|
RootTask.GetInstanceData().bBoolB = true;
|
|
|
|
auto& Task0 = State0.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
State0.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState);
|
|
|
|
auto& Task1A = State1A.AddTask<FTestTask_Stand>(FName(TEXT("Task1A")));
|
|
auto& BoolCond1 = State1A.AddEnterCondition<FStateTreeCompareBoolCondition>();
|
|
|
|
EditorData.AddPropertyBinding(RootTask, TEXT("bBoolB"), BoolCond1, TEXT("bLeft"));
|
|
BoolCond1.GetInstanceData().bRight = true;
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE("StateTree should get compiled", bResult);
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE("StateTree should init", bInitSucceeded);
|
|
|
|
const TCHAR* TickStr(TEXT("Tick"));
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* ExitStateStr(TEXT("ExitState"));
|
|
const TCHAR* StateCompletedStr(TEXT("StateCompleted"));
|
|
|
|
// Start and enter state
|
|
Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Transition from State0 and tries to select State1.
|
|
// This tests that data from current shared active states (Root) is available during state selection.
|
|
Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should complete"), Exec.Expect(Task0.GetName(), StateCompletedStr));
|
|
AITEST_TRUE(TEXT("StateTree Task1A should enter state"), Exec.Expect(Task1A.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionNextWithParentData, "System.StateTree.Transition.NextWithParentData");
|
|
|
|
struct FStateTreeTest_TransitionGlobalDataView : FStateTreeTestBase
|
|
{
|
|
// Tests that the global eval and task dataviews are kept up to date when transitioning from
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A")));
|
|
UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B")));
|
|
|
|
auto& EvalA = EditorData.AddEvaluator<FTestEval_A>(FName(TEXT("Eval")));
|
|
EvalA.GetInstanceData().IntA = 42;
|
|
auto& GlobalTask = EditorData.AddGlobalTask<FTestTask_PrintValue>(FName(TEXT("Global")));
|
|
GlobalTask.GetInstanceData().Value = 123;
|
|
|
|
// State A
|
|
auto& Task0 = StateA.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
StateA.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &StateB);
|
|
|
|
// State B
|
|
auto& Task1 = StateB.AddTask<FTestTask_PrintValue>(FName(TEXT("Task1")));
|
|
EditorData.AddPropertyBinding(EvalA, TEXT("IntA"), Task1, TEXT("Value"));
|
|
auto& Task2 = StateB.AddTask<FTestTask_PrintValue>(FName(TEXT("Task2")));
|
|
EditorData.AddPropertyBinding(GlobalTask, TEXT("Value"), Task2, TEXT("Value"));
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* EnterState42Str(TEXT("EnterState42"));
|
|
const TCHAR* EnterState123Str(TEXT("EnterState123"));
|
|
|
|
// Start and enter state
|
|
Status = Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// Transition from StateA to StateB, Task0 should enter state with evaluator value copied.
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state with value 42"), Exec.Expect(Task1.GetName(), EnterState42Str));
|
|
AITEST_TRUE(TEXT("StateTree Task1 should enter state with value 123"), Exec.Expect(Task2.GetName(), EnterState123Str));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionGlobalDataView, "System.StateTree.Transition.GlobalDataView");
|
|
|
|
struct FStateTreeTest_TransitionDelay : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
const FGameplayTag Tag = GetTestTag1();
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A")));
|
|
UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B")));
|
|
|
|
// State A
|
|
auto& Task0 = StateA.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
Task0.GetNode().TicksToCompletion = 100;
|
|
|
|
FStateTreeTransition& Transition = StateA.AddTransition(EStateTreeTransitionTrigger::OnEvent, EStateTreeTransitionType::GotoState, &StateB);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 0.15f;
|
|
Transition.DelayRandomVariance = 0.0f;
|
|
Transition.RequiredEvent.Tag = Tag;
|
|
|
|
// State B
|
|
auto& Task1 = StateB.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
Task1.GetNode().TicksToCompletion = 100;
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* TickStr(TEXT("Tick"));
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* ExitStateStr(TEXT("ExitState"));
|
|
const TCHAR* StateCompletedStr(TEXT("StateCompleted"));
|
|
|
|
// Start and enter state
|
|
Status = Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// This should cause delayed transition.
|
|
Exec.SendEvent(Tag);
|
|
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should tick"), Exec.Expect(Task0.GetName(), TickStr));
|
|
Exec.LogClear();
|
|
|
|
// Should have execution frames
|
|
AITEST_TRUE(TEXT("Should have active frames"), InstanceData.GetExecutionState()->ActiveFrames.Num() > 0);
|
|
|
|
// Should have delayed transitions
|
|
const int32 NumDelayedTransitions0 = InstanceData.GetExecutionState()->DelayedTransitions.Num();
|
|
AITEST_EQUAL(TEXT("Should have a delayed transition"), NumDelayedTransitions0, 1);
|
|
|
|
// Tick and expect a delayed transition.
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should tick"), Exec.Expect(Task0.GetName(), TickStr));
|
|
Exec.LogClear();
|
|
|
|
const int32 NumDelayedTransitions1 = InstanceData.GetExecutionState()->DelayedTransitions.Num();
|
|
AITEST_EQUAL(TEXT("Should have a delayed transition"), NumDelayedTransitions1, 1);
|
|
|
|
// Should complete delayed transition.
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should exit state"), Exec.Expect(Task0.GetName(), ExitStateStr));
|
|
AITEST_TRUE(TEXT("StateTree Task1 should enter state"), Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionDelay, "System.StateTree.Transition.Delay");
|
|
|
|
struct FStateTreeTest_TransitionDelayZero : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
const FGameplayTag Tag = GetTestTag1();
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A")));
|
|
UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B")));
|
|
|
|
// State A
|
|
auto& Task0 = StateA.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
Task0.GetNode().TicksToCompletion = 100;
|
|
|
|
FStateTreeTransition& Transition = StateA.AddTransition(EStateTreeTransitionTrigger::OnEvent, EStateTreeTransitionType::GotoState, &StateB);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 0.0f;
|
|
Transition.DelayRandomVariance = 0.0f;
|
|
Transition.RequiredEvent.Tag = Tag;
|
|
|
|
// State B
|
|
auto& Task1 = StateB.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
Task1.GetNode().TicksToCompletion = 100;
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
|
|
AITEST_TRUE("StateTree should get compiled", bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* TickStr(TEXT("Tick"));
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* ExitStateStr(TEXT("ExitState"));
|
|
const TCHAR* StateCompletedStr(TEXT("StateCompleted"));
|
|
|
|
// Start and enter state
|
|
Status = Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// This should cause delayed transition. Because the time is 0, it should happen immediately.
|
|
Exec.SendEvent(Tag);
|
|
|
|
Status = Exec.Tick(0.1f);
|
|
AITEST_TRUE(TEXT("StateTree Task0 should exit state"), Exec.Expect(Task0.GetName(), ExitStateStr));
|
|
AITEST_TRUE(TEXT("StateTree Task1 should enter state"), Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionDelayZero, "System.StateTree.Transition.DelayZero");
|
|
|
|
struct FStateTreeTest_PassingTransitionEventToStateSelection : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
|
|
FPropertyBindingPath PathToPayloadMember;
|
|
{
|
|
const bool bParseResult = PathToPayloadMember.FromString(TEXT("Payload.A"));
|
|
|
|
AITEST_TRUE(TEXT("Parsing path should succeeed"), bParseResult);
|
|
|
|
FStateTreeEvent EventWithPayload;
|
|
EventWithPayload.Payload = FInstancedStruct::Make<FStateTreeTest_PropertyStructA>();
|
|
const bool bUpdateSegments = PathToPayloadMember.UpdateSegmentsFromValue(FStateTreeDataView(FStructView::Make(EventWithPayload)));
|
|
AITEST_TRUE(TEXT("Updating segments should succeeed"), bUpdateSegments);
|
|
}
|
|
|
|
// This state shouldn't be selected, because transition's condition and state's enter condition exlude each other.
|
|
UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A")));
|
|
StateA.bHasRequiredEventToEnter = true;
|
|
StateA.RequiredEventToEnter.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct();
|
|
auto& TaskA = StateA.AddTask<FTestTask_Stand>(FName(TEXT("TaskA")));
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& AIntCond = StateA.AddEnterCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
AIntCond.GetInstanceData().Right = 0;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(StateA.GetEventID(), PathToPayloadMember.GetSegments()),
|
|
FPropertyBindingPath(AIntCond.ID, TEXT("Left")));
|
|
|
|
// This state should be selected as the sent event fullfils both transition's condition and state's enter condition.
|
|
UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B")));
|
|
StateB.bHasRequiredEventToEnter = true;
|
|
StateB.RequiredEventToEnter.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct();
|
|
auto& TaskB = StateB.AddTask<FTestTask_PrintValue>(FName(TEXT("TaskB")));
|
|
// Test copying data from the state event. The condition properties are copied from temp instance data during selection, this gets copied from active instance data.
|
|
TaskB.GetInstanceData().Value = -1; // Initially -1, expected to be overridden by property binding below.
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(StateB.GetEventID(), PathToPayloadMember.GetSegments()),
|
|
FPropertyBindingPath(TaskB.ID, TEXT("Value")));
|
|
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& BIntCond = StateB.AddEnterCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
BIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(StateB.GetEventID(), PathToPayloadMember.GetSegments()),
|
|
FPropertyBindingPath(BIntCond.ID, TEXT("Left")));
|
|
|
|
// This state should be selected only initially when there's not event in the queue.
|
|
UStateTreeState& StateInitial = Root.AddChildState(FName(TEXT("Initial")));
|
|
auto& TaskInitial = StateInitial.AddTask<FTestTask_Stand>(FName(TEXT("TaskInitial")));
|
|
// Transition from Initial -> StateA
|
|
FStateTreeTransition& TransA = StateInitial.AddTransition(EStateTreeTransitionTrigger::OnEvent, FGameplayTag(), EStateTreeTransitionType::GotoState, &StateA);
|
|
TransA.RequiredEvent.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct();
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransAIntCond = TransA.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransAIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(TransA.GetEventID(), PathToPayloadMember.GetSegments()),
|
|
FPropertyBindingPath(TransAIntCond.ID, TEXT("Left")));
|
|
// Transition from Initial -> StateB
|
|
FStateTreeTransition& TransB = StateInitial.AddTransition(EStateTreeTransitionTrigger::OnEvent, FGameplayTag(), EStateTreeTransitionType::GotoState, &StateB);
|
|
TransB.RequiredEvent.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct();
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransBIntCond = TransB.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransBIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(TransB.GetEventID(), PathToPayloadMember.GetSegments()),
|
|
FPropertyBindingPath(TransBIntCond.ID, TEXT("Left")));
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
|
|
Status = Exec.Start();
|
|
AITEST_TRUE(TEXT("StateTree TaskInitial should enter state"), Exec.Expect(TaskInitial.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
// The conditions test for payload Value=1, the first event should not trigger transition.
|
|
Exec.SendEvent(GetTestTag1(), FConstStructView::Make(FStateTreeTest_PropertyStructA{0}));
|
|
Exec.SendEvent(GetTestTag1(), FConstStructView::Make(FStateTreeTest_PropertyStructA{1}));
|
|
Status = Exec.Tick(0.1f);
|
|
|
|
AITEST_FALSE(TEXT("StateTree TaskA should not enter state"), Exec.Expect(TaskA.GetName(), EnterStateStr));
|
|
AITEST_TRUE(TEXT("StateTree TaskB should enter state"), Exec.Expect(TaskB.GetName(), TEXT("EnterState1"))); // TaskB decorates "EnterState" with value from the payload.
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_PassingTransitionEventToStateSelection, "System.StateTree.Transition.PassingTransitionEventToStateSelection");
|
|
|
|
struct FStateTreeTest_FollowTransitions : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Root
|
|
// Trans
|
|
// A
|
|
// B
|
|
// C
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData);
|
|
RootPropertyBag.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
|
|
RootPropertyBag.SetValueInt32(FName(TEXT("Int")), 1);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& StateTrans = Root.AddChildState(FName(TEXT("Trans")));
|
|
UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A")));
|
|
UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B")));
|
|
UStateTreeState& StateC = Root.AddChildState(FName(TEXT("C")));
|
|
|
|
// Root
|
|
|
|
// Trans
|
|
{
|
|
StateTrans.SelectionBehavior = EStateTreeStateSelectionBehavior::TryFollowTransitions;
|
|
|
|
{
|
|
// This transition should be skipped due to the condition
|
|
FStateTreeTransition& TransA = StateTrans.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateA);
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransIntCond = TransA.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransIntCond.GetInstanceData().Right = 0;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")),
|
|
FPropertyBindingPath(TransIntCond.ID, TEXT("Left")));
|
|
}
|
|
|
|
{
|
|
// This transition leads to selection, but will be overridden.
|
|
FStateTreeTransition& TransB = StateTrans.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateB);
|
|
TransB.Priority = EStateTreeTransitionPriority::Normal;
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransIntCond = TransB.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")),
|
|
FPropertyBindingPath(TransIntCond.ID, TEXT("Left")));
|
|
}
|
|
|
|
{
|
|
// This transition is selected, should override previous one due to priority.
|
|
FStateTreeTransition& TransC = StateTrans.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateC);
|
|
TransC.Priority = EStateTreeTransitionPriority::High;
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransIntCond = TransC.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")),
|
|
FPropertyBindingPath(TransIntCond.ID, TEXT("Left")));
|
|
}
|
|
}
|
|
|
|
auto& TaskA = StateA.AddTask<FTestTask_Stand>(FName(TEXT("TaskA")));
|
|
auto& TaskB = StateB.AddTask<FTestTask_Stand>(FName(TEXT("TaskB")));
|
|
auto& TaskC = StateC.AddTask<FTestTask_Stand>(FName(TEXT("TaskC")));
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* TickStr = TEXT("Tick");
|
|
const TCHAR* EnterStateStr = TEXT("EnterState");
|
|
const TCHAR* ExitStateStr = TEXT("ExitState");
|
|
|
|
Status = Exec.Start();
|
|
AITEST_FALSE(TEXT("StateTree TaskA should not enter state"), Exec.Expect(TaskA.GetName(), EnterStateStr));
|
|
AITEST_FALSE(TEXT("StateTree TaskB should not enter state"), Exec.Expect(TaskB.GetName(), EnterStateStr));
|
|
AITEST_TRUE(TEXT("StateTree TaskC should enter state"), Exec.Expect(TaskC.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_FollowTransitions, "System.StateTree.Transition.FollowTransitions");
|
|
|
|
struct FStateTreeTest_InfiniteLoop : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData);
|
|
RootPropertyBag.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
|
|
RootPropertyBag.SetValueInt32(FName(TEXT("Int")), 1);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
|
|
UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A")));
|
|
UStateTreeState& StateB = StateA.AddChildState(FName(TEXT("B")));
|
|
|
|
// Root
|
|
|
|
// State A
|
|
{
|
|
StateA.SelectionBehavior = EStateTreeStateSelectionBehavior::TryFollowTransitions;
|
|
{
|
|
// A -> B
|
|
FStateTreeTransition& Trans = StateA.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateB);
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransIntCond = Trans.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")),
|
|
FPropertyBindingPath(TransIntCond.ID, TEXT("Left")));
|
|
}
|
|
}
|
|
|
|
// State B
|
|
{
|
|
StateB.SelectionBehavior = EStateTreeStateSelectionBehavior::TryFollowTransitions;
|
|
{
|
|
// B -> A
|
|
FStateTreeTransition& Trans = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateA);
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& TransIntCond = Trans.AddCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
|
|
TransIntCond.GetInstanceData().Right = 1;
|
|
EditorData.AddPropertyBinding(
|
|
FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")),
|
|
FPropertyBindingPath(TransIntCond.ID, TEXT("Left")));
|
|
}
|
|
}
|
|
|
|
auto& TaskA = StateA.AddTask<FTestTask_Stand>(FName(TEXT("TaskA")));
|
|
auto& TaskB = StateB.AddTask<FTestTask_Stand>(FName(TEXT("TaskB")));
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
const TCHAR* TickStr = TEXT("Tick");
|
|
const TCHAR* EnterStateStr = TEXT("EnterState");
|
|
const TCHAR* ExitStateStr = TEXT("ExitState");
|
|
|
|
GetTestRunner().AddExpectedError(TEXT("Loop detected when trying to select state"), EAutomationExpectedErrorFlags::Contains, 1);
|
|
GetTestRunner().AddExpectedError(TEXT("Failed to select initial state"), EAutomationExpectedErrorFlags::Contains, 1);
|
|
|
|
Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should fail"), Status, EStateTreeRunStatus::Failed);
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_InfiniteLoop, "System.StateTree.Transition.InfiniteLoop");
|
|
|
|
struct FStateTreeTest_RegularTransitions : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Global task and parameter
|
|
// RootA
|
|
// StateB -> Next
|
|
// StateC -> Next
|
|
// StateD -> Next
|
|
// StateE -> Next
|
|
// StateF -> Next
|
|
// StateG -> Succeeded
|
|
|
|
FStateTreeCompilerLog Log;
|
|
|
|
// Main asset
|
|
UStateTree& StateTree = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
FGuid RootParameter_ValueID;
|
|
{
|
|
// Parameters
|
|
FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData);
|
|
RootPropertyBag.AddProperty("Value", EPropertyBagPropertyType::Int32);
|
|
RootPropertyBag.SetValueInt32("Value", -111);
|
|
RootParameter_ValueID = RootPropertyBag.FindPropertyDescByName("Value")->ID;
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask = EditorData.AddGlobalTask<FTestTask_PrintValue>("GlobalTask");
|
|
GlobalTask.GetInstanceData().Value = -1;
|
|
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(GlobalTask.ID, TEXT("Value")));
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree("RootA");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = Root.AddTask<FTestTask_PrintValue>("TaskA");
|
|
Task.GetInstanceData().Value = -1;
|
|
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(Task.ID, TEXT("Value")));
|
|
}
|
|
{
|
|
UStateTreeState& StateB = Root.AddChildState("StateB", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateB.AddTask<FTestTask_PrintValue>("TaskB");
|
|
Task.GetInstanceData().Value = 1;
|
|
FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
{
|
|
UStateTreeState& StateB = Root.AddChildState("StateC", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateB.AddTask<FTestTask_PrintValue>("TaskC");
|
|
Task.GetInstanceData().Value = 2;
|
|
FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
{
|
|
UStateTreeState& StateD = Root.AddChildState("StateD", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateD.AddTask<FTestTask_PrintValue>("TaskD");
|
|
Task.GetInstanceData().Value = 3;
|
|
FStateTreeTransition& Transition = StateD.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &Root);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
|
|
const TCHAR* EnterStateStr(TEXT("EnterState"));
|
|
const TCHAR* ExitStateStr(TEXT("ExitState"));
|
|
|
|
{
|
|
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
|
|
FInstancedPropertyBag Parameters;
|
|
Parameters.MigrateToNewBagInstance(StateTree.GetDefaultParameters());
|
|
Parameters.SetValueInt32("Value", 111);
|
|
|
|
Status = Exec.Start(&Parameters);
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should enter Global tasks"), Exec.Expect("GlobalTask", TEXT("EnterState111")));
|
|
AITEST_TRUE(TEXT("Start should enter StateA"), Exec.Expect("TaskA", TEXT("EnterState111")));
|
|
AITEST_TRUE(TEXT("Start should enter StateB"), Exec.Expect("TaskB", TEXT("EnterState1")));
|
|
Exec.LogClear();
|
|
|
|
Status = Exec.Tick(1.5f); // over tick, should trigger
|
|
AITEST_EQUAL(TEXT("1st Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("1st Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("1st Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("1st Tick should tick StateB"), Exec.Expect("TaskB", TEXT("Tick1")));
|
|
Exec.LogClear();
|
|
|
|
// B go to C
|
|
Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick the StateB"), Exec.Expect("TaskB", TEXT("Tick1")));
|
|
AITEST_TRUE(TEXT("2nd Tick should exit the StateB"), Exec.Expect("TaskB", TEXT("ExitState1")));
|
|
AITEST_TRUE(TEXT("2nd Tick should enter the StateC"), Exec.Expect("TaskC", TEXT("EnterState2")));
|
|
AITEST_FALSE(TEXT("2nd Tick should not exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState111")));
|
|
AITEST_FALSE(TEXT("2nd Tick should not exit the Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111")));
|
|
Exec.LogClear();
|
|
|
|
Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("3rd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick StateC"), Exec.Expect("TaskC", TEXT("Tick2")));
|
|
AITEST_FALSE(TEXT("3th Tick should not exit StateC"), Exec.Expect("TaskD", TEXT("ExitState2")));
|
|
Exec.LogClear();
|
|
|
|
// C go to D
|
|
Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("4th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("4th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("4th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("4th Tick should tick the StateC"), Exec.Expect("TaskC", TEXT("Tick2")));
|
|
AITEST_TRUE(TEXT("4th Tick should exit the StateC"), Exec.Expect("TaskC", TEXT("ExitState2")));
|
|
AITEST_TRUE(TEXT("4th Tick should enter the StateD"), Exec.Expect("TaskD", TEXT("EnterState3")));
|
|
AITEST_FALSE(TEXT("4th Tick should not exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState111")));
|
|
AITEST_FALSE(TEXT("4th Tick should not exit the Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111")));
|
|
Exec.LogClear();
|
|
|
|
Status = Exec.Tick(0.001f);
|
|
AITEST_EQUAL(TEXT("5th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("5th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("5th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("5th Tick should tick StateD"), Exec.Expect("TaskD", TEXT("Tick3")));
|
|
AITEST_FALSE(TEXT("5th Tick should not exit StateD"), Exec.Expect("TaskD", TEXT("ExitState3")));
|
|
Exec.LogClear();
|
|
|
|
// D go to root
|
|
Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("6th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("6th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("6th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("6th Tick should tick StateD"), Exec.Expect("TaskD", TEXT("Tick3")));
|
|
AITEST_TRUE(TEXT("6th Tick should exit the StateD"), Exec.Expect("TaskD", TEXT("ExitState3")));
|
|
AITEST_FALSE(TEXT("6th Tick should not exit the Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111")));
|
|
AITEST_FALSE(TEXT("6th Tick should not enter the Global tasks"), Exec.Expect("GlobalTask", TEXT("EnterState111")));
|
|
AITEST_TRUE(TEXT("6th Tick should exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState=Sustained")));
|
|
AITEST_TRUE(TEXT("6th Tick should enter the StateA"), Exec.Expect("TaskA", TEXT("EnterState=Sustained")));
|
|
AITEST_TRUE(TEXT("6th Tick should enter the StateB"), Exec.Expect("TaskB", TEXT("EnterState1")));
|
|
AITEST_TRUE(TEXT("6th Tick should enter the StateB"), Exec.Expect("TaskB", TEXT("EnterState=Changed")));
|
|
Exec.LogClear();
|
|
|
|
Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("7th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("7th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("7th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111")));
|
|
AITEST_TRUE(TEXT("7th Tick should tick StateB"), Exec.Expect("TaskB", TEXT("Tick1")));
|
|
Exec.LogClear();
|
|
|
|
Exec.Stop();
|
|
AITEST_TRUE(TEXT("Stop Tick should exit the StateB"), Exec.Expect("TaskB", TEXT("ExitState1")));
|
|
AITEST_TRUE(TEXT("Stop Tick should exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState111")));
|
|
AITEST_TRUE(TEXT("Stop should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111")));
|
|
Exec.LogClear();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_RegularTransitions, "System.StateTree.Transition.RegularTransitions");
|
|
|
|
|
|
struct FStateTreeTest_RequestTransition : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
//RootA
|
|
// StateB
|
|
// StateC
|
|
// StateD
|
|
// StateE
|
|
// StateF
|
|
// StateI
|
|
// StateJ
|
|
// StateX
|
|
// StateY
|
|
//RootO
|
|
// StateP
|
|
|
|
FStateTreeCompilerLog Log;
|
|
|
|
enum class ECustomFunctionToRun
|
|
{
|
|
None,
|
|
TransitionD_To_E,
|
|
TransitionB_To_I,
|
|
TransitionB_To_C,
|
|
TransitionC_To_B,
|
|
TransitionB_To_X,
|
|
TransitionB_To_B,
|
|
TransitionB_To_P,
|
|
} TransitionToExecute = ECustomFunctionToRun::None;
|
|
struct FStateHandle
|
|
{
|
|
FStateTreeStateHandle B;
|
|
FStateTreeStateHandle C;
|
|
FStateTreeStateHandle E;
|
|
FStateTreeStateHandle I;
|
|
FStateTreeStateHandle X;
|
|
FStateTreeStateHandle P;
|
|
FGuid B_ID;
|
|
FGuid C_ID;
|
|
FGuid E_ID;
|
|
FGuid I_ID;
|
|
FGuid X_ID;
|
|
FGuid P_ID;
|
|
bool bFinishTasksB = false;
|
|
bool bFinishTasksC = false;
|
|
bool bFinishTasksD = false;
|
|
} AllStateHandle;
|
|
|
|
// Main asset
|
|
UStateTree& StateTree = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
UStateTreeState& StateA = EditorData.AddSubTree("StateA");
|
|
StateA.AddTask<FTestTask_PrintValue>("StateATask");
|
|
|
|
UStateTreeState& StateB = StateA.AddChildState("StateB", EStateTreeStateType::State);
|
|
FTestTask_PrintValue& TaskB = StateB.AddTask<FTestTask_PrintValue>("StateBTask").GetNode();
|
|
AllStateHandle.B_ID = StateB.ID;
|
|
|
|
UStateTreeState& StateC = StateB.AddChildState("StateC", EStateTreeStateType::State);
|
|
FTestTask_PrintValue& TaskC = StateC.AddTask<FTestTask_PrintValue>("StateCTask").GetNode();
|
|
AllStateHandle.C_ID = StateC.ID;
|
|
|
|
UStateTreeState& StateD = StateC.AddChildState("StateD", EStateTreeStateType::State);
|
|
FTestTask_PrintValue& TaskD = StateD.AddTask<FTestTask_PrintValue>("StateDTask").GetNode();
|
|
StateD.SelectionBehavior = EStateTreeStateSelectionBehavior::TryEnterState;
|
|
|
|
UStateTreeState& StateE = StateD.AddChildState("StateE", EStateTreeStateType::State);
|
|
StateE.AddTask<FTestTask_PrintValue>("StateETask");
|
|
AllStateHandle.E_ID = StateE.ID;
|
|
|
|
UStateTreeState& StateF = StateE.AddChildState("StateF", EStateTreeStateType::State);
|
|
StateF.AddTask<FTestTask_PrintValue>("StateFTask");
|
|
|
|
UStateTreeState& StateI = StateB.AddChildState("StateI", EStateTreeStateType::State);
|
|
StateI.AddTask<FTestTask_PrintValue>("StateITask");
|
|
AllStateHandle.I_ID = StateI.ID;
|
|
|
|
UStateTreeState& StateJ = StateI.AddChildState("StateJ", EStateTreeStateType::State);
|
|
StateJ.AddTask<FTestTask_PrintValue>("StateJTask");
|
|
|
|
UStateTreeState& StateX = StateA.AddChildState("StateX", EStateTreeStateType::State);
|
|
StateX.AddTask<FTestTask_PrintValue>("StateXTask");
|
|
AllStateHandle.X_ID = StateX.ID;
|
|
|
|
UStateTreeState& StateY = StateX.AddChildState("StateY", EStateTreeStateType::State);
|
|
StateY.AddTask<FTestTask_PrintValue>("StateYTask");
|
|
|
|
UStateTreeState& StateO = EditorData.AddSubTree("StateO");
|
|
StateO.AddTask<FTestTask_PrintValue>("StateOTask");
|
|
|
|
UStateTreeState& StateP = StateO.AddChildState("StateP", EStateTreeStateType::State);
|
|
StateP.AddTask<FTestTask_PrintValue>("StatePTask");
|
|
AllStateHandle.P_ID = StateP.ID;
|
|
|
|
TaskB.CustomTickFunc = [&TransitionToExecute, &AllStateHandle]
|
|
(FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task)
|
|
{
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_I)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.I);
|
|
}
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_C)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.C);
|
|
}
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_X)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.X);
|
|
}
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_B)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.B);
|
|
}
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_P)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.P);
|
|
}
|
|
if (AllStateHandle.bFinishTasksB)
|
|
{
|
|
Context.FinishTask(*Task, EStateTreeFinishTaskType::Succeeded);
|
|
}
|
|
};
|
|
|
|
TaskC.CustomTickFunc = [&TransitionToExecute, &AllStateHandle]
|
|
(FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task)
|
|
{
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionC_To_B)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.B);
|
|
}
|
|
if (AllStateHandle.bFinishTasksC)
|
|
{
|
|
Context.FinishTask(*Task, EStateTreeFinishTaskType::Succeeded);
|
|
}
|
|
};
|
|
|
|
TaskD.CustomTickFunc = [&TransitionToExecute, &AllStateHandle]
|
|
(FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task)
|
|
{
|
|
if (TransitionToExecute == ECustomFunctionToRun::TransitionD_To_E)
|
|
{
|
|
Context.RequestTransition(AllStateHandle.E);
|
|
}
|
|
|
|
if (AllStateHandle.bFinishTasksD)
|
|
{
|
|
Context.FinishTask(*Task, EStateTreeFinishTaskType::Succeeded);
|
|
}
|
|
};
|
|
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE("StateTree should get compiled", bResult);
|
|
|
|
AllStateHandle.B = StateTree.GetStateHandleFromId(AllStateHandle.B_ID);
|
|
AllStateHandle.C = StateTree.GetStateHandleFromId(AllStateHandle.C_ID);
|
|
AllStateHandle.E = StateTree.GetStateHandleFromId(AllStateHandle.E_ID);
|
|
AllStateHandle.I = StateTree.GetStateHandleFromId(AllStateHandle.I_ID);
|
|
AllStateHandle.X = StateTree.GetStateHandleFromId(AllStateHandle.X_ID);
|
|
AllStateHandle.P = StateTree.GetStateHandleFromId(AllStateHandle.P_ID);
|
|
}
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE("StateTree should init", bInitSucceeded);
|
|
}
|
|
|
|
auto ResetInstanceData = [this, &StateTree, &InstanceData]()
|
|
{
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
EStateTreeRunStatus Status = Exec.Stop();
|
|
AITEST_EQUAL(TEXT("Should stop"), Status, EStateTreeRunStatus::Stopped);
|
|
}
|
|
InstanceData = FStateTreeInstanceData();
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("State should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
}
|
|
return true;
|
|
};
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL("Start should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE("Start should enter Global tasks", Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Start StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Start StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Start StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Start StateD"), LogOrder);
|
|
AITEST_FALSE(TEXT("Tick StateA"), Exec.Expect("StateATask", TEXT("Tick0")));
|
|
AITEST_FALSE(TEXT("Tick StateB"), Exec.Expect("StateBTask", TEXT("Tick0")));
|
|
AITEST_FALSE(TEXT("Tick StateC"), Exec.Expect("StateCTask", TEXT("Tick0")));
|
|
AITEST_FALSE(TEXT("Tick StateD"), Exec.Expect("StateDTask", TEXT("Tick0")));
|
|
AITEST_FALSE(TEXT("Start StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Start StateB"), Exec.Expect("StateBTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Start StateC"), Exec.Expect("StateCTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Start StateD"), Exec.Expect("StateDTask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL("1st Tick should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE("1st Tick no transition", Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateC"), Exec.Expect("StateCTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateD"), Exec.Expect("StateDTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateC"), Exec.Expect("StateCTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateD"), Exec.Expect("StateDTask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionD_To_E;
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("2nd tick sold be in new state"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD", "StateE", "StateF"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateETask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateE"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateFTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateF"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateC"), Exec.Expect("StateCTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateD"), Exec.Expect("StateDTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateC"), Exec.Expect("StateCTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateD"), Exec.Expect("StateDTask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
// Same transition but with a completed D
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionD_To_E;
|
|
AllStateHandle.bFinishTasksD = true;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD", "StateE", "StateF"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
//@TODO State D completed. It should be a new state.
|
|
//LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed"));
|
|
//AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
//@TODO State D completed. It should be a new state.
|
|
//LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Changed"));
|
|
//AITEST_TRUE(TEXT("Enter StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateETask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateE"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateFTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateF"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateC"), Exec.Expect("StateCTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateC"), Exec.Expect("StateCTask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
AllStateHandle.bFinishTasksD = false;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionB_To_I;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateI", "StateJ"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateITask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateI"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateJTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateJ"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionB_To_C;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateD"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionC_To_B;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateD"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionC_To_B;
|
|
AllStateHandle.bFinishTasksC = true;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
//@TODO State C completed. It should change for another state
|
|
//LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed"));
|
|
//AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
//@TODO State C completed. It should change for another state
|
|
//LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed"));
|
|
//AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateB"), LogOrder);
|
|
//@TODO State C completed. It should be a new state.
|
|
//LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Changed"));
|
|
//AITEST_TRUE(TEXT("Enter StateC"), LogOrder);
|
|
//@TODO State C completed. It should be a new state.
|
|
//LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Changed"));
|
|
//AITEST_TRUE(TEXT("Enter StateD"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionB_To_X;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateX", "StateY"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateXTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateX"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateYTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateY"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionB_To_B;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Sustained"));
|
|
AITEST_TRUE(TEXT("Exit StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Sustained"));
|
|
AITEST_TRUE(TEXT("Enter StateD"), LogOrder);
|
|
AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0")));
|
|
AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0")));
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
TransitionToExecute = ECustomFunctionToRun::TransitionB_To_P;
|
|
if (!ResetInstanceData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateO", "StateP"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0"));
|
|
AITEST_TRUE(TEXT("Tick StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateD"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateC"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateB"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateATask", TEXT("ExitState=Changed"));
|
|
AITEST_TRUE(TEXT("Exit StateA"), LogOrder);
|
|
LogOrder = LogOrder.Then("StateOTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateO"), LogOrder);
|
|
LogOrder = LogOrder.Then("StatePTask", TEXT("EnterState=Changed"));
|
|
AITEST_TRUE(TEXT("Enter StateP"), LogOrder);
|
|
Exec.LogClear();
|
|
TransitionToExecute = ECustomFunctionToRun::None;
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
EStateTreeRunStatus Status = Exec.Stop();
|
|
AITEST_EQUAL(TEXT("Should stop"), Status, EStateTreeRunStatus::Stopped);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_RequestTransition, "System.StateTree.Transition.RequestTransition");
|
|
|
|
} // namespace UE::StateTree::Tests
|
|
|
|
UE_ENABLE_OPTIMIZATION_SHIP
|
|
|
|
#undef LOCTEXT_NAMESPACE
|