1695 lines
83 KiB
C++
1695 lines
83 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
|
|
{
|
|
|
|
namespace Private
|
|
{
|
|
struct FScopedCVarBool
|
|
{
|
|
FScopedCVarBool(const TCHAR* VariableName)
|
|
{
|
|
CVar = IConsoleManager::Get().FindConsoleVariable(VariableName);
|
|
check(CVar);
|
|
bPreviousValue = CVar->GetBool();
|
|
}
|
|
~FScopedCVarBool()
|
|
{
|
|
CVar->SetWithCurrentPriority(bPreviousValue);
|
|
}
|
|
|
|
FScopedCVarBool(const FScopedCVarBool&) = delete;
|
|
FScopedCVarBool& operator=(const FScopedCVarBool&) = delete;
|
|
|
|
void Set(bool NewValue)
|
|
{
|
|
CVar->SetWithCurrentPriority(NewValue);
|
|
check(CVar->GetBool() == NewValue);
|
|
}
|
|
|
|
private:
|
|
IConsoleVariable* CVar;
|
|
bool bPreviousValue;
|
|
};
|
|
} // namespace Private
|
|
|
|
struct FStateTreeTest_FailEnterLinkedAsset : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
FStateTreeCompilerLog Log;
|
|
|
|
// Asset 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
UStateTreeState& Root2 = EditorData2.AddSubTree(FName(TEXT("Root2")));
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task2 = Root2.AddTask<FTestTask_Stand>(FName(TEXT("Task2")));
|
|
TStateTreeEditorNode<FTestTask_Stand>& GlobalTask2 = EditorData2.AddGlobalTask<FTestTask_Stand>(FName(TEXT("GlobalTask2")));
|
|
GlobalTask2.GetInstanceData().Value = 123;
|
|
|
|
// Always failing enter condition
|
|
TStateTreeEditorNode<FStateTreeCompareIntCondition>& IntCond2 = Root2.AddEnterCondition<FStateTreeCompareIntCondition>();
|
|
EditorData2.AddPropertyBinding(GlobalTask2, TEXT("Value"), IntCond2, TEXT("Left"));
|
|
IntCond2.GetInstanceData().Right = 0;
|
|
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
|
|
// Main asset
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root1")));
|
|
UStateTreeState& A1 = Root.AddChildState(FName(TEXT("A1")), EStateTreeStateType::LinkedAsset);
|
|
A1.SetLinkedStateAsset(&StateTree2);
|
|
|
|
UStateTreeState& B1 = Root.AddChildState(FName(TEXT("B1")), EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task1 = B1.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
|
|
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);
|
|
|
|
Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("StateTree should enter GlobalTask2"), Exec.Expect(GlobalTask2.GetName(), EnterStateStr));
|
|
AITEST_TRUE(TEXT("StateTree should exit GlobalTask2"), Exec.Expect(GlobalTask2.GetName(), ExitStateStr));
|
|
AITEST_FALSE(TEXT("StateTree should not enter Task2"), Exec.Expect(Task2.GetName(), EnterStateStr));
|
|
AITEST_TRUE(TEXT("StateTree should enter Task1"), Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_FailEnterLinkedAsset, "System.StateTree.LinkedAsset.FailEnter");
|
|
|
|
struct FStateTreeTest_EnterAndExitLinkedAsset : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
FStateTreeCompilerLog Log;
|
|
|
|
// Tree1
|
|
// Root1
|
|
// A1 (linked to Tree2) OnCompleted->B1
|
|
// B1 Task1
|
|
// Tree2 GlobalTask2 => 2 ticks
|
|
// Root2 Task2 => 1 tick
|
|
|
|
// Asset 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
UStateTreeState& Root2 = EditorData2.AddSubTree(FName(TEXT("Root2")));
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task2 = Root2.AddTask<FTestTask_Stand>(FName(TEXT("Task2")));
|
|
Task2.GetNode().TicksToCompletion = 1;
|
|
TStateTreeEditorNode<FTestTask_Stand>& GlobalTask2 = EditorData2.AddGlobalTask<FTestTask_Stand>(FName(TEXT("GlobalTask2")));
|
|
GlobalTask2.GetNode().TicksToCompletion = 2;
|
|
{
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
|
|
// Main asset
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root1")));
|
|
UStateTreeState& A1 = Root.AddChildState(FName(TEXT("A1")), EStateTreeStateType::LinkedAsset);
|
|
A1.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState);
|
|
A1.SetLinkedStateAsset(&StateTree2);
|
|
|
|
UStateTreeState& B1 = Root.AddChildState(FName(TEXT("B1")), EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task1 = B1.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
Task1.GetNode().TicksToCompletion = 1;
|
|
{
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
|
|
const TCHAR* EnterStateStr = TEXT("EnterState");
|
|
const TCHAR* SustainedEnterStateStr = TEXT("EnterState=Sustained");
|
|
const TCHAR* ChangedEnterStateStr = TEXT("EnterState=Changed");
|
|
const TCHAR* ExitStateStr = TEXT("ExitState");
|
|
const TCHAR* SustainedExitStateStr = TEXT("ExitState=Sustained");
|
|
const TCHAR* ChangedExitStateStr = TEXT("ExitState=Changed");
|
|
const TCHAR* StateCompletedStateStr = TEXT("StateCompleted");
|
|
const TCHAR* TickStr = TEXT("Tick");
|
|
|
|
{
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL("Start should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE("StateTree should enter GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ChangedEnterStateStr));
|
|
AITEST_FALSE("StateTree should not exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ChangedExitStateStr));
|
|
AITEST_FALSE("StateTree should not exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), SustainedExitStateStr));
|
|
AITEST_TRUE("StateTree should enter Task2", Exec.Expect(Task2.GetName(), ChangedEnterStateStr));
|
|
AITEST_FALSE("StateTree should not exit Task2", Exec.Expect(Task2.GetName(), ChangedExitStateStr));
|
|
AITEST_FALSE("StateTree should not exit Task2", Exec.Expect(Task2.GetName(), SustainedExitStateStr));
|
|
AITEST_FALSE("StateTree should not enter Task1", Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
// Task2 completes.
|
|
EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL("Tick should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("StateTree should exit the root state but not the global"),
|
|
Exec.Expect(GlobalTask2.GetName(), TickStr)
|
|
.Then(Task2.GetName(), TickStr)
|
|
.Then(Task2.GetName(), StateCompletedStateStr)
|
|
.Then(Task2.GetName(), ChangedExitStateStr)
|
|
.Then(GlobalTask2.GetName(), ChangedExitStateStr)
|
|
.Then(Task1.GetName(), ChangedEnterStateStr)
|
|
);
|
|
AITEST_FALSE("StateTree should not tick Tasks1", Exec.Expect(Task1.GetName(), TickStr));
|
|
AITEST_FALSE("StateTree should not exit Task1", Exec.Expect(Task1.GetName(), ExitStateStr));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
Exec.Stop();
|
|
}
|
|
// Change the order, the global completes before task2. Task2 should not tick
|
|
{
|
|
Task2.GetNode().TicksToCompletion = 1;
|
|
GlobalTask2.GetNode().TicksToCompletion = 1;
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL("Start should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
// GlobalTask2 completes Task2 won't tick and won't complete.
|
|
EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL("Tick should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("StateTree should exit the root state and the global"),
|
|
Exec.Expect(GlobalTask2.GetName(), TickStr)
|
|
.Then(Task2.GetName(), ChangedExitStateStr)
|
|
.Then(GlobalTask2.GetName(), ChangedExitStateStr)
|
|
.Then(Task1.GetName(), EnterStateStr)
|
|
);
|
|
AITEST_FALSE("StateTree should not tick Tasks2", Exec.Expect(Task2.GetName(), TickStr));
|
|
AITEST_FALSE("StateTree should not tick Tasks1", Exec.Expect(Task1.GetName(), TickStr));
|
|
AITEST_FALSE("StateTree should not exit Task1", Exec.Expect(Task1.GetName(), ExitStateStr));
|
|
Exec.LogClear();
|
|
}
|
|
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_EnterAndExitLinkedAsset, "System.StateTree.LinkedAsset.EnterAndExit");
|
|
|
|
struct FStateTreeTest_EnterAndExitLinkedAsset2 : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
FStateTreeCompilerLog Log;
|
|
|
|
// Tree1
|
|
// Root1 Task0
|
|
// A1 (linked to Tree2) no transition
|
|
// B1 Task1
|
|
// Tree2 GlobalTask2 => 2 ticks
|
|
// Root2 Task2 => 1 tick
|
|
|
|
// Asset 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
UStateTreeState& Root2 = EditorData2.AddSubTree(FName(TEXT("Root2")));
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task2 = Root2.AddTask<FTestTask_Stand>(FName(TEXT("Task2")));
|
|
Task2.GetNode().TicksToCompletion = 1;
|
|
TStateTreeEditorNode<FTestTask_Stand>& GlobalTask2 = EditorData2.AddGlobalTask<FTestTask_Stand>(FName(TEXT("GlobalTask2")));
|
|
GlobalTask2.GetNode().TicksToCompletion = 2;
|
|
{
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
|
|
// Main asset
|
|
UStateTree& StateTree = NewStateTree();
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root1")));
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task0 = Root.AddTask<FTestTask_Stand>(FName(TEXT("Task0")));
|
|
Task0.GetNode().TicksToCompletion = 999999;
|
|
|
|
UStateTreeState& A1 = Root.AddChildState(FName(TEXT("A1")), EStateTreeStateType::LinkedAsset);
|
|
A1.SetLinkedStateAsset(&StateTree2);
|
|
|
|
UStateTreeState& B1 = Root.AddChildState(FName(TEXT("B1")), EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task1 = B1.AddTask<FTestTask_Stand>(FName(TEXT("Task1")));
|
|
Task1.GetNode().TicksToCompletion = 1;
|
|
{
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
|
|
const TCHAR* EnterStateStr = TEXT("EnterState");
|
|
const TCHAR* SustainedEnterStateStr = TEXT("EnterState=Sustained");
|
|
const TCHAR* ChangedEnterStateStr = TEXT("EnterState=Changed");
|
|
const TCHAR* ExitStateStr = TEXT("ExitState");
|
|
const TCHAR* SustainedExitStateStr = TEXT("ExitState=Sustained");
|
|
const TCHAR* ChangedExitStateStr = TEXT("ExitState=Changed");
|
|
const TCHAR* StateCompletedStateStr = TEXT("StateCompleted");
|
|
const TCHAR* TickStr = TEXT("Tick");
|
|
|
|
{
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL("Start should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE("StateTree should enter GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ChangedEnterStateStr));
|
|
AITEST_FALSE("StateTree should not exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ChangedExitStateStr));
|
|
AITEST_FALSE("StateTree should not exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), SustainedExitStateStr));
|
|
AITEST_TRUE("StateTree should enter Task2", Exec.Expect(Task2.GetName(), ChangedEnterStateStr));
|
|
AITEST_FALSE("StateTree should not exit Task2", Exec.Expect(Task2.GetName(), ChangedExitStateStr));
|
|
AITEST_FALSE("StateTree should not exit Task2", Exec.Expect(Task2.GetName(), SustainedExitStateStr));
|
|
AITEST_FALSE("StateTree should not enter Task1", Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
// Task2 completes. The linked state didn't complete. Global is sustained.
|
|
EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL("Tick should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("StateTree should exit the root state but not the global"),
|
|
Exec.Expect(GlobalTask2.GetName(), TickStr)
|
|
.Then(Task2.GetName(), TickStr)
|
|
.Then(Task2.GetName(), StateCompletedStateStr)
|
|
//@TODO the state completed. It should be a new state and not sustained.
|
|
//.Then(Task2.GetName(), ChangedExitStateStr)
|
|
//@TODO call enter/exit of globals.
|
|
//.Then(GlobalTask2.GetName(), SustainedExitStateStr)
|
|
.Then(Task0.GetName(), SustainedExitStateStr)
|
|
.Then(Task0.GetName(), SustainedEnterStateStr)
|
|
//@TODO call enter/exit of globals.
|
|
//.Then(GlobalTask2.GetName(), SustainedEnterStateStr)
|
|
//@TODO the state completed. It should be a new state and not sustained.
|
|
//.Then(Task2.GetName(), ChangedEnterStateStr)
|
|
);
|
|
AITEST_FALSE("StateTree should not tick Task1", Exec.Expect(Task1.GetName(), TickStr));
|
|
AITEST_FALSE("StateTree should not exit Task1", Exec.Expect(Task1.GetName(), ExitStateStr));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
Exec.Stop();
|
|
}
|
|
// Change the order, the global completes before task2. Task2 should not tick.
|
|
{
|
|
Task2.GetNode().TicksToCompletion = 1;
|
|
GlobalTask2.GetNode().TicksToCompletion = 1;
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL("Start should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
// GlobalTask2 completes. The tree completed. Task2 won't tick and won't complete.
|
|
EStateTreeRunStatus Status = Exec.Tick(0.1f);
|
|
AITEST_EQUAL("Tick should complete with Running", Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("StateTree should exit the root state and the global"),
|
|
Exec.Expect(GlobalTask2.GetName(), TickStr)
|
|
//@TODO The tree completed. It is a new state.
|
|
//.Then(Task2.GetName(), ChangedExitStateStr)
|
|
//@TODO call enter/exit of globals.
|
|
//.Then(GlobalTask2.GetName(), ChangedExitStateStr)
|
|
.Then(Task0.GetName(), SustainedExitStateStr)
|
|
.Then(Task0.GetName(), SustainedEnterStateStr)
|
|
//@TODO call enter/exit of globals.
|
|
//.Then(GlobalTask2.GetName(), ChangedEnterStateStr)
|
|
//@TODO The tree completed. It is a new state.
|
|
//.Then(Task2.GetName(), EnterStateStr)
|
|
);
|
|
AITEST_FALSE("StateTree should not tick Tasks2", Exec.Expect(Task2.GetName(), TickStr));
|
|
AITEST_FALSE("StateTree should not tick Tasks1", Exec.Expect(Task1.GetName(), TickStr));
|
|
AITEST_FALSE("StateTree should not exit Task1", Exec.Expect(Task1.GetName(), EnterStateStr));
|
|
AITEST_FALSE("StateTree should not exit Task1", Exec.Expect(Task1.GetName(), ExitStateStr));
|
|
Exec.LogClear();
|
|
}
|
|
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_EnterAndExitLinkedAsset2, "System.StateTree.LinkedAsset.EnterAndExit2");
|
|
|
|
struct FStateTreeTest_MultipleSameLinkedAsset : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Root
|
|
// StateA -> Next
|
|
// StateB -> Next
|
|
// StateLinkedTreeA (Tree2) -> Next
|
|
// StateLinkedTreeB (Tree2) -> Next
|
|
// StateLinkedTreeC (Tree2) -> Next
|
|
// StateC -> Succeeded
|
|
//Tree 2
|
|
// Global task and parameter
|
|
// RootE
|
|
// StateA (with transition OnTick to succeeded)
|
|
|
|
FStateTreeCompilerLog Log;
|
|
|
|
// Asset 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
FGuid RootParameter_ValueID;
|
|
{
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
{
|
|
// Parameters
|
|
FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData2);
|
|
RootPropertyBag.AddProperty("Value", EPropertyBagPropertyType::Int32);
|
|
RootPropertyBag.SetValueInt32("Value", -111);
|
|
RootParameter_ValueID = RootPropertyBag.FindPropertyDescByName("Value")->ID;
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask = EditorData2.AddGlobalTask<FTestTask_PrintValue>("Tree2GlobalTaskA");
|
|
GlobalTask.GetInstanceData().Value = -1;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(GlobalTask.ID, TEXT("Value")));
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData2.AddSubTree("Tree2StateRoot");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root.AddTask<FTestTask_PrintValue>("Tree2StateRootTaskA");
|
|
Task1.GetInstanceData().Value = -2;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(Task1.ID, TEXT("Value")));
|
|
}
|
|
{
|
|
UStateTreeState& State = Root.AddChildState("Tree2StateA", EStateTreeStateType::State);
|
|
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = State.AddTask<FTestTask_PrintValue>("Tree2StateATaskA");
|
|
Task1.GetInstanceData().Value = -2;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(Task1.ID, TEXT("Value")));
|
|
}
|
|
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
|
|
// Main asset
|
|
UStateTree& StateTree1 = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask = EditorData.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTaskA");
|
|
GlobalTask.GetInstanceData().Value = 99;
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree("Tree1StateRoot");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTaskA");
|
|
Task1.GetInstanceData().Value = 88;
|
|
}
|
|
{
|
|
UStateTreeState& StateB = Root.AddChildState("Tree1StateA", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateB.AddTask<FTestTask_PrintValue>("Tree1StateATaskA");
|
|
Task.GetInstanceData().Value = 1;
|
|
FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
{
|
|
UStateTreeState& StateB = Root.AddChildState("Tree1StateB", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateB.AddTask<FTestTask_PrintValue>("Tree1StateBTaskA");
|
|
Task.GetInstanceData().Value = 2;
|
|
FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
{
|
|
UStateTreeState& C1 = Root.AddChildState("Tree1StateLinkedTreeA", EStateTreeStateType::LinkedAsset);
|
|
C1.SetLinkedStateAsset(&StateTree2);
|
|
C1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
C1.SetParametersPropertyOverridden(RootParameter_ValueID, true);
|
|
C1.Parameters.Parameters.SetValueInt32("Value", 111);
|
|
}
|
|
{
|
|
UStateTreeState& C2 = Root.AddChildState("Tree1StateLinkedTreeB", EStateTreeStateType::LinkedAsset);
|
|
C2.SetLinkedStateAsset(&StateTree2);
|
|
C2.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
C2.SetParametersPropertyOverridden(RootParameter_ValueID, true);
|
|
C2.Parameters.Parameters.SetValueInt32("Value", 222);
|
|
}
|
|
{
|
|
UStateTreeState& C3 = Root.AddChildState("Tree1StateLinkedTreeC", EStateTreeStateType::LinkedAsset);
|
|
C3.SetLinkedStateAsset(&StateTree2);
|
|
C3.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
C3.SetParametersPropertyOverridden(RootParameter_ValueID, true);
|
|
C3.Parameters.Parameters.SetValueInt32("Value", 333);
|
|
}
|
|
{
|
|
UStateTreeState& StateD = Root.AddChildState("Tree1StateC", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateD.AddTask<FTestTask_PrintValue>("Tree1StateBTaskA");
|
|
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(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
|
|
Private::FScopedCVarBool CVarTickGlobalNodesWithHierarchy = TEXT("StateTree.TickGlobalNodesFollowingTreeHierarchy");
|
|
for (int32 Counter = 0; Counter < 2; ++Counter)
|
|
{
|
|
const bool bTickGlobalNodesWithHierarchy = Counter == 0;
|
|
CVarTickGlobalNodesWithHierarchy.Set(bTickGlobalNodesWithHierarchy);
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
{
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("EnterState99"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("EnterState88"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("EnterState1"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateATaskA"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.5f); // over tick, should trigger
|
|
AITEST_EQUAL(TEXT("1st Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("1st should tick tasks Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("1st should tick tasks Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("Tick1"));
|
|
AITEST_TRUE(TEXT("1st should tick tasks Tree1StateATaskA"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("Tick1"));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Tree1StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("ExitState1"));
|
|
AITEST_TRUE(TEXT("2nd Tick should exit Tree1StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("EnterState2"));
|
|
AITEST_TRUE(TEXT("2nd Tick should enter Tree1StateBTaskA"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("3rd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("3rd should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("3rd should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("Tick2"));
|
|
AITEST_TRUE(TEXT("3rd should tick Tree1StateBTaskA"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("4th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("Tick2"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1StateBTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("ExitState2"));
|
|
AITEST_TRUE(TEXT("4th Tick should exit Tree1StateBTaskA"), LogOrder);
|
|
//@todo: bug: order is wrong
|
|
//LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("EnterState111"))
|
|
//AITEST_TRUE(TEXT("4th Tick should enter Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("EnterState111"));
|
|
AITEST_TRUE(TEXT("4th Tick should enter Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("EnterState111"));
|
|
AITEST_TRUE(TEXT("4th Tick should enter Tree2StateATaskA"), LogOrder);
|
|
AITEST_TRUE(TEXT("4th Tick should tick tasks"), Exec.Expect("Tree2GlobalTaskA", TEXT("EnterState111")));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(0.001f);
|
|
AITEST_EQUAL(TEXT("5th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
if (bTickGlobalNodesWithHierarchy)
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree2StateATaskA"), LogOrder);
|
|
}
|
|
else
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("5h Tick should tick Tree2StateATaskA"), LogOrder);
|
|
}
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("6th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
if (bTickGlobalNodesWithHierarchy)
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree2StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("ExitState111"));
|
|
AITEST_TRUE(TEXT("6th Tick should exit Tree2StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("ExitState111"));
|
|
AITEST_TRUE(TEXT("6th Tick should exit Tree2StateRootTaskA"), LogOrder);
|
|
//@todo: bug: The exit state is not called on the global, it's a new frame.
|
|
//LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("ExitState111"));
|
|
//AITEST_TRUE(TEXT("6th Tick should exit Tree2GlobalTaskA"), LogOrder);
|
|
//@todo: bug: The enter state is not called on the global, it's a new frame.
|
|
//LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("EnterState222"));
|
|
//AITEST_TRUE(TEXT("6th Tick should enter Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("EnterState222"));
|
|
AITEST_TRUE(TEXT("6th Tick should enter Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("EnterState222"));
|
|
AITEST_TRUE(TEXT("6th Tick should enter Tree2StateATaskA"), LogOrder);
|
|
}
|
|
else
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick99"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick88"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("Tick111"));
|
|
AITEST_TRUE(TEXT("6th Tick should tick Tree2StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("ExitState111"));
|
|
AITEST_TRUE(TEXT("6th Tick should exit Tree2StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("ExitState111"));
|
|
AITEST_TRUE(TEXT("6th Tick should exit Tree2StateRootTaskA"), LogOrder);
|
|
//@todo: bug: The exit state is not called on the global, it's a new frame.
|
|
//LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("ExitState111"));
|
|
//AITEST_TRUE(TEXT("6th Tick should exit Tree2GlobalTaskA"), LogOrder);
|
|
//@todo: bug: The enter state is not called on the global, it's a new frame.
|
|
//LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("EnterState222"));
|
|
//AITEST_TRUE(TEXT("6th Tick should enter Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("EnterState222"));
|
|
AITEST_TRUE(TEXT("6th Tick should enter Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateATaskA", TEXT("EnterState222"));
|
|
AITEST_TRUE(TEXT("6th Tick should enter Tree2StateATaskA"), LogOrder);
|
|
}
|
|
Exec.LogClear();
|
|
}
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_MultipleSameLinkedAsset, "System.StateTree.LinkedAsset.MultipleSameTree");
|
|
|
|
struct FStateTreeTest_EmptyStateWithTickTransitionLinkedAsset : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Root
|
|
// StateA -> Next
|
|
// StateLinkedTree (Tree2) -> Next
|
|
// StateB -> Root
|
|
//Tree 2
|
|
// Global task and parameter
|
|
// Root
|
|
// FailState (condition false)
|
|
// StateA (condition true and with transition OnTick to succeeded)
|
|
|
|
FStateTreeCompilerLog Log;
|
|
|
|
// Asset 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask = EditorData.AddGlobalTask<FTestTask_PrintValue>("Tree2GlobalTaskA");
|
|
GlobalTask.GetInstanceData().Value = 21;
|
|
|
|
UStateTreeState& Root = EditorData.AddSubTree("Tree2StateRoot");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = Root.AddTask<FTestTask_PrintValue>("Tree2StateRootTaskA");
|
|
Task.GetInstanceData().Value = 22;
|
|
}
|
|
{
|
|
UStateTreeState& State = Root.AddChildState("Tree2StateFail", EStateTreeStateType::State);
|
|
// Add auto fails condition
|
|
auto& Condition = State.AddEnterCondition<FStateTreeTestBooleanCondition>();
|
|
Condition.GetInstanceData().bSuccess = false;
|
|
|
|
// Should never see
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = State.AddTask<FTestTask_PrintValue>("Tree2StateFailTaskA");
|
|
Task.GetInstanceData().Value = 23;
|
|
}
|
|
{
|
|
UStateTreeState& State = Root.AddChildState("Tree2StateB", EStateTreeStateType::State);
|
|
|
|
// Add auto success condition
|
|
auto& Condition = State.AddEnterCondition<FStateTreeTestBooleanCondition>();
|
|
Condition.GetInstanceData().bSuccess = true;
|
|
|
|
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
|
|
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
|
|
// Main asset
|
|
UStateTree& StateTree1 = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
|
|
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask = EditorData.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTaskA");
|
|
GlobalTask.GetInstanceData().Value = 11;
|
|
}
|
|
UStateTreeState& Root = EditorData.AddSubTree("Tree1StateRoot");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTaskA");
|
|
Task.GetInstanceData().Value = 12;
|
|
}
|
|
{
|
|
UStateTreeState& State = Root.AddChildState("Tree1StateA", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = State.AddTask<FTestTask_PrintValue>("Tree1StateATaskA");
|
|
Task.GetInstanceData().Value = 13;
|
|
|
|
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
{
|
|
UStateTreeState& C1 = Root.AddChildState("Tree1StateLinkedTree", EStateTreeStateType::LinkedAsset);
|
|
C1.SetLinkedStateAsset(&StateTree2);
|
|
C1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
}
|
|
{
|
|
UStateTreeState& State = Root.AddChildState("Tree1StateB", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = State.AddTask<FTestTask_PrintValue>("Tree1StateBTaskA");
|
|
Task.GetInstanceData().Value = 14;
|
|
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &Root);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0;
|
|
}
|
|
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
|
|
Private::FScopedCVarBool CVarTickGlobalNodesWithHierarchy = TEXT("StateTree.TickGlobalNodesFollowingTreeHierarchy");
|
|
for (int32 Counter = 0; Counter < 2; ++Counter)
|
|
{
|
|
const bool bTickGlobalNodesWithHierarchy = Counter == 0;
|
|
CVarTickGlobalNodesWithHierarchy.Set(bTickGlobalNodesWithHierarchy);
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
{
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("EnterState11"));
|
|
AITEST_TRUE(TEXT("Start enters in the correct order Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("EnterState12"));
|
|
AITEST_TRUE(TEXT("Start enters in the correct order Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("EnterState13"));
|
|
AITEST_TRUE(TEXT("Start enters in the correct order Tree1StateATaskA"), LogOrder);
|
|
AITEST_TRUE(TEXT("Start should be in the correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateA"));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.5f); // over tick, should trigger
|
|
AITEST_EQUAL(TEXT("1st Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("1st Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("1st Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("Tick13"));
|
|
AITEST_TRUE(TEXT("1st Tick should tick Tree1StateATaskA"), LogOrder);
|
|
AITEST_TRUE(TEXT("Start should be in the correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateA"));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("Tick13"));
|
|
AITEST_TRUE(TEXT("2nd Tick should tick Tree1StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("EnterState21"));
|
|
AITEST_TRUE(TEXT("2nd Tick should enter Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("State Tree Test Boolean Condition", TEXT("TestCondition=0"));
|
|
AITEST_TRUE(TEXT("2nd Tick should test Bool"), LogOrder);
|
|
LogOrder = LogOrder.Then("State Tree Test Boolean Condition", TEXT("TestCondition=1"));
|
|
AITEST_TRUE(TEXT("2nd Tick should test Bool"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateATaskA", TEXT("ExitState13"));
|
|
AITEST_TRUE(TEXT("2nd Tick should exit Tree1StateATaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("EnterState22"));
|
|
AITEST_TRUE(TEXT("2nd Tick should enter Tree2StateRootTaskA"), LogOrder);
|
|
AITEST_FALSE(TEXT("2nd Tick should not enter the fail state."), Exec.Expect("Tree2StateFailTaskA"));
|
|
AITEST_TRUE(TEXT("2nd Tick should be in the correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateLinkedTree", "Tree2StateRoot", "Tree2StateB"));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("3rd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("3rd Tick should be in the correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateLinkedTree", "Tree2StateRoot", "Tree2StateB"));
|
|
if (bTickGlobalNodesWithHierarchy)
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick21"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick22"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
}
|
|
else
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick21"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick22"));
|
|
AITEST_TRUE(TEXT("3rd Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
}
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("4th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
if (bTickGlobalNodesWithHierarchy)
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick21"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick22"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("ExitState22"));
|
|
AITEST_TRUE(TEXT("4th Tick should exit Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("ExitState21"));
|
|
AITEST_TRUE(TEXT("4th Tick should exit Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("EnterState14"));
|
|
AITEST_TRUE(TEXT("4th Tick should enter Tree1StateBTaskA"), LogOrder);
|
|
}
|
|
else
|
|
{
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("Tick21"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("Tick22"));
|
|
AITEST_TRUE(TEXT("4th Tick should tick Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("ExitState22"));
|
|
AITEST_TRUE(TEXT("4th Tick should exit Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2GlobalTaskA", TEXT("ExitState21"));
|
|
AITEST_TRUE(TEXT("4th Tick should exit Tree2GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("EnterState14"));
|
|
AITEST_TRUE(TEXT("4th Tick should enter Tree1StateBTaskA"), LogOrder);
|
|
}
|
|
AITEST_TRUE(TEXT("4th Tick should be in the correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateB"));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(0.001f);
|
|
AITEST_EQUAL(TEXT("5th Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1GlobalTaskA", TEXT("Tick11"));
|
|
AITEST_TRUE(TEXT("5th Tick should tick Tree1GlobalTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateRootTaskA", TEXT("Tick12"));
|
|
AITEST_TRUE(TEXT("5th Tick should tick Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1StateBTaskA", TEXT("Tick14"));
|
|
AITEST_TRUE(TEXT("5th Tick should tick Tree1StateBTaskA"), LogOrder);
|
|
AITEST_TRUE(TEXT("5th Tick should be in the correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateB"));
|
|
Exec.LogClear();
|
|
}
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_EmptyStateWithTickTransitionLinkedAsset, "System.StateTree.LinkedAsset.EmptyStateWithTickTransition");
|
|
|
|
struct FStateTreeTest_RecursiveLinkedAsset : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Root
|
|
// StateLinkedTree1 (Tree2) -> Next
|
|
// StateA -> Succeeded
|
|
//Tree 2
|
|
// Root
|
|
// StateLinkedTreeA (Tree1) -> Next
|
|
// StateA -> Succeeded
|
|
|
|
UStateTree& StateTree1 = NewStateTree();
|
|
UStateTreeState* Root1 = nullptr;
|
|
// Asset 1 definition
|
|
{
|
|
UStateTreeEditorData& EditorData1 = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
|
|
Root1 = &EditorData1.AddSubTree("Tree1StateRoot");
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree 1 should get compiled"), bResult);
|
|
}
|
|
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
UStateTreeState* Root2 = nullptr;
|
|
// Asset 2 definition
|
|
{
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
Root2 = &EditorData2.AddSubTree("Tree2StateRoot");
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree 2 should get compiled"), bResult);
|
|
}
|
|
// Asset 1 implementation
|
|
{
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root1->AddTask<FTestTask_PrintValue>("Tree1StateRootTaskA");
|
|
Task1.GetInstanceData().Value = 101;
|
|
}
|
|
{
|
|
UStateTreeState& C1 = Root1->AddChildState("Tree1StateLinkedTree1", EStateTreeStateType::LinkedAsset);
|
|
C1.Tag = GetTestTag1();
|
|
C1.SetLinkedStateAsset(&StateTree2);
|
|
C1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
}
|
|
{
|
|
UStateTreeState& StateA = Root1->AddChildState("Tree1StateA", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateA.AddTask<FTestTask_PrintValue>("Tree1StateA");
|
|
Task.GetInstanceData().Value = 102;
|
|
FStateTreeTransition& Transition = StateA.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, Root1);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0f;
|
|
}
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
// Asset 2 implementation
|
|
UStateTreeState* Tree2StateLinkedTree1 = nullptr;
|
|
{
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root2->AddTask<FTestTask_PrintValue>("Tree2StateRootTaskA");
|
|
Task1.GetInstanceData().Value = 201;
|
|
}
|
|
{
|
|
UStateTreeState& C1 = Root2->AddChildState("Tree2StateLinkedTree1", EStateTreeStateType::LinkedAsset);
|
|
C1.Tag = GetTestTag2();
|
|
C1.SetLinkedStateAsset(&StateTree2);
|
|
C1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
Tree2StateLinkedTree1 = &C1;
|
|
}
|
|
{
|
|
UStateTreeState& StateD = Root2->AddChildState("Tree2StateA", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = StateD.AddTask<FTestTask_PrintValue>("Tree2StateA");
|
|
Task.GetInstanceData().Value = 202;
|
|
FStateTreeTransition& Transition = StateD.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, Root2);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0f;
|
|
}
|
|
|
|
// circular dependency detected
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree2);
|
|
AITEST_FALSE(TEXT("StateTree should not compiled"), bResult);
|
|
}
|
|
// Fix circular dependency
|
|
{
|
|
Tree2StateLinkedTree1->SetLinkedStateAsset(&StateTree1);
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
// Run test
|
|
{
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
{
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
}
|
|
|
|
{
|
|
GetTestRunner().AddExpectedError(TEXT("Trying to recursively enter subtree"), EAutomationExpectedErrorFlags::Contains, 1);
|
|
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1StateRootTaskA", TEXT("EnterState101"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateRootTaskA", TEXT("EnterState201"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2StateRootTaskA"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree2StateA", TEXT("EnterState202"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2StateA"), LogOrder);
|
|
Exec.LogClear();
|
|
AITEST_TRUE(TEXT("Doesn't have the expected error message."), GetTestRunner().HasMetExpectedMessages());
|
|
}
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_RecursiveLinkedAsset, "System.StateTree.LinkedAsset.RecursiveLinkedAsset");
|
|
|
|
struct FStateTreeTest_LinkedAssetTransitionSameTick : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Root
|
|
// State1 -> Delay 1 -> StateLinkedTree1
|
|
// LinkState2 (Tree2) -> Next
|
|
// State3 -> Root
|
|
//Tree 2
|
|
// Root
|
|
// State1 -> Succeeded
|
|
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
UStateTreeState* Root2 = nullptr;
|
|
// Asset 2
|
|
{
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
Root2 = &EditorData2.AddSubTree("Tree2StateRoot");
|
|
|
|
{
|
|
UStateTreeState& C1 = Root2->AddChildState("Tree2State1", EStateTreeStateType::State);
|
|
C1.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
|
|
}
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree should not compiled"), bResult);
|
|
}
|
|
// Asset 1
|
|
UStateTree& StateTree1 = NewStateTree();
|
|
{
|
|
UStateTreeState* Root1 = nullptr;
|
|
{
|
|
UStateTreeEditorData& EditorData1 = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
|
|
Root1 = &EditorData1.AddSubTree("Tree1StateRoot");
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree 1 should get compiled"), bResult);
|
|
}
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root1->AddTask<FTestTask_PrintValue>("Tree1StateRootTask1");
|
|
Task1.GetInstanceData().Value = 100;
|
|
}
|
|
{
|
|
UStateTreeState& State1 = Root1->AddChildState("Tree1State1", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = State1.AddTask<FTestTask_PrintValue>("Tree1State1Task1");
|
|
Task.GetInstanceData().Value = 101;
|
|
Task.GetInstanceData().TickRunStatus = EStateTreeRunStatus::Succeeded;
|
|
State1.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState);
|
|
}
|
|
{
|
|
UStateTreeState& LinkState2 = Root1->AddChildState("Tree1State2LinkedTree2", EStateTreeStateType::LinkedAsset);
|
|
LinkState2.SetLinkedStateAsset(&StateTree2);
|
|
LinkState2.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
}
|
|
{
|
|
UStateTreeState& State3 = Root1->AddChildState("Tree1State3", EStateTreeStateType::State);
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task = State3.AddTask<FTestTask_PrintValue>("Tree1State3Task1");
|
|
Task.GetInstanceData().Value = 103;
|
|
FStateTreeTransition& Transition = State3.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, Root1);
|
|
Transition.bDelayTransition = true;
|
|
Transition.DelayDuration = 1.0f;
|
|
}
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler(Log);
|
|
const bool bResult = Compiler.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
|
|
}
|
|
|
|
// Run test
|
|
{
|
|
FStateTreeInstanceData InstanceData;
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
{
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
}
|
|
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1StateRootTask1", TEXT("EnterState100"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTask1"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1State1Task1", TEXT("EnterState101"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1State1"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.01f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State2LinkedTree2", "Tree2StateRoot", "Tree2State1"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1StateRootTask1", TEXT("Tick100"));
|
|
AITEST_TRUE(TEXT("Start should tick Tree1StateRootTask1"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1State1Task1", TEXT("Tick101"));
|
|
AITEST_TRUE(TEXT("Start should tick Tree1State1"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1State1Task1", TEXT("ExitState101"));
|
|
AITEST_TRUE(TEXT("Start should exit Tree1State1"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
//Tree2State1 -> Succeeded should transition to Tree1State3
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.01f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State3"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1StateRootTask1", TEXT("Tick100"));
|
|
AITEST_TRUE(TEXT("Start should tick Tree1StateRootTask1"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1State3Task1", TEXT("EnterState103"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1State3"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.01f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State3"));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
EStateTreeRunStatus Status = Exec.Tick(1.01f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1"));
|
|
FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("Tree1StateRootTask1", TEXT("Tick100"));
|
|
AITEST_TRUE(TEXT("Start should tick Tree1StateRootTask1"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1State3Task1", TEXT("ExitState103"));
|
|
AITEST_TRUE(TEXT("Start should exit Tree1State3"), LogOrder);
|
|
LogOrder = LogOrder.Then("Tree1State1Task1", TEXT("EnterState101"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1State1"), LogOrder);
|
|
Exec.LogClear();
|
|
}
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_LinkedAssetTransitionSameTick, "System.StateTree.LinkedAsset.TransitionSameTick");
|
|
|
|
struct FStateTreeTest_Linked_GlobalParameter : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Global task and parameter
|
|
// Root
|
|
// StateLinkedTree1 (Tree2) -> Next
|
|
// SubTree2
|
|
// SubTree
|
|
// State3 (with transition OnTick to succeeded)
|
|
//Tree 2
|
|
// Global task and parameter
|
|
// Root
|
|
// State1 (with transition OnTick to succeeded)
|
|
|
|
auto AddInt = [](FInstancedPropertyBag& PropertyBag, FName VarName)
|
|
{
|
|
PropertyBag.AddProperty(VarName, EPropertyBagPropertyType::Int32);
|
|
PropertyBag.SetValueInt32(VarName, -99);
|
|
return PropertyBag.FindPropertyDescByName(VarName)->ID;
|
|
};
|
|
auto AddDouble = [](FInstancedPropertyBag& PropertyBag, FName VarName)
|
|
{
|
|
PropertyBag.AddProperty(VarName, EPropertyBagPropertyType::Double);
|
|
PropertyBag.SetValueDouble(VarName, -99.0);
|
|
return PropertyBag.FindPropertyDescByName(VarName)->ID;
|
|
};
|
|
|
|
FGuid Tree2GlobalParameter_ValueID_Int;
|
|
|
|
// Tree 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
|
|
// note double before int
|
|
AddDouble(GetRootPropertyBag(EditorData2), "Tree2GlobalDouble");
|
|
Tree2GlobalParameter_ValueID_Int = AddInt(GetRootPropertyBag(EditorData2), "Tree2GlobalInt");
|
|
|
|
// Global tasks
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask1 = EditorData2.AddGlobalTask<FTestTask_PrintValue>("Tree2GlobalTask1");
|
|
{
|
|
GlobalTask1.GetInstanceData().Value = -1;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), "Tree2GlobalInt"), FPropertyBindingPath(GlobalTask1.ID, "Value"));
|
|
}
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask2 = EditorData2.AddGlobalTask<FTestTask_PrintValue>("Tree2GlobalTask2");
|
|
GlobalTask2.GetInstanceData().Value = -2;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(GlobalTask2.ID, "Value"));
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData2.AddSubTree("Tree2StateRoot");
|
|
{
|
|
AddInt(Root.Parameters.Parameters, "Tree2StateRootParametersInt");
|
|
AddDouble(Root.Parameters.Parameters, "Tree2StateRootParametersDouble");
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), "Tree2GlobalInt"), FPropertyBindingPath(Root.Parameters.ID, "Tree2StateRootParametersInt"));
|
|
}
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root.AddTask<FTestTask_PrintValue>("Tree2StateRootTask1");
|
|
Task1.GetInstanceData().Value = -1;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), "Tree2GlobalInt"), FPropertyBindingPath(Task1.ID, "Value"));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task2 = Root.AddTask<FTestTask_PrintValue>("Tree2StateRootTask2");
|
|
Task2.GetInstanceData().Value = -2;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(Root.Parameters.ID, "Tree2StateRootParametersInt"), FPropertyBindingPath(Task2.ID, "Value"));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task3 = Root.AddTask<FTestTask_PrintValue>("Tree2StateRootTask3");
|
|
Task3.GetInstanceData().Value = -3;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(Task3.ID, "Value"));
|
|
}
|
|
{
|
|
UStateTreeState& State1 = Root.AddChildState("Tree2State1", EStateTreeStateType::State);
|
|
{
|
|
AddDouble(State1.Parameters.Parameters, "Tree2State1ParametersDouble");
|
|
AddInt(State1.Parameters.Parameters, "Tree2State1ParametersInt");
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(Root.Parameters.ID, "Tree2StateRootParametersInt"), FPropertyBindingPath(State1.Parameters.ID, "Tree2State1ParametersInt"));
|
|
}
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = State1.AddTask<FTestTask_PrintValue>("Tree2State1Task1");
|
|
Task1.GetInstanceData().Value = -1;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(EditorData2.GetRootParametersGuid(), "Tree2GlobalInt"), FPropertyBindingPath(Task1.ID, TEXT("Value")));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task2 = State1.AddTask<FTestTask_PrintValue>("Tree2State1Task2");
|
|
Task2.GetInstanceData().Value = -2;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(Root.Parameters.ID, "Tree2StateRootParametersInt"), FPropertyBindingPath(Task2.ID, TEXT("Value")));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task3 = State1.AddTask<FTestTask_PrintValue>("Tree2State1Task3");
|
|
Task3.GetInstanceData().Value = -3;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(Task3.ID, "Value"));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task4 = State1.AddTask<FTestTask_PrintValue>("Tree2State1Task4");
|
|
Task3.GetInstanceData().Value = -4;
|
|
EditorData2.AddPropertyBinding(FPropertyBindingPath(State1.Parameters.ID, "Tree2State1ParametersInt"), FPropertyBindingPath(Task4.ID, "Value"));
|
|
|
|
State1.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
|
|
}
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
|
|
// Tree 1
|
|
UStateTree& StateTree1 = NewStateTree();
|
|
FGuid Tree1GlobalParameter_ValueID_Int;
|
|
{
|
|
UStateTreeEditorData& EditorData1 = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
|
|
|
|
Tree1GlobalParameter_ValueID_Int = AddInt(GetRootPropertyBag(EditorData1), "Tree1GlobalInt");
|
|
AddDouble(GetRootPropertyBag(EditorData1), "Tree1GlobalDouble");
|
|
|
|
// Global tasks
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask1 = EditorData1.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTask1");
|
|
{
|
|
GlobalTask1.GetInstanceData().Value = -1;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(EditorData1.GetRootParametersGuid(), "Tree1GlobalInt"), FPropertyBindingPath(GlobalTask1.ID, "Value"));
|
|
}
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask2 = EditorData1.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTask2");
|
|
GlobalTask2.GetInstanceData().Value = -2;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(GlobalTask2.ID, "Value"));
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData1.AddSubTree("Tree1StateRoot");
|
|
{
|
|
AddDouble(Root.Parameters.Parameters, "Tree1StateRootParametersDouble");
|
|
AddInt(Root.Parameters.Parameters, "Tree1StateRootParametersInt");
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(EditorData1.GetRootParametersGuid(), "Tree1GlobalInt"), FPropertyBindingPath(Root.Parameters.ID, "Tree1StateRootParametersInt"));
|
|
}
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTask1");
|
|
Task1.GetInstanceData().Value = -1;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(EditorData1.GetRootParametersGuid(), "Tree1GlobalInt"), FPropertyBindingPath(Task1.ID, "Value"));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task2 = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTask2");
|
|
Task2.GetInstanceData().Value = -2;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(Root.Parameters.ID, "Tree1StateRootParametersInt"), FPropertyBindingPath(Task2.ID, "Value"));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task3 = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTask3");
|
|
Task3.GetInstanceData().Value = -3;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(Task3.ID, "Value"));
|
|
}
|
|
{
|
|
UStateTreeState& State1 = Root.AddChildState("Tree1State1", EStateTreeStateType::LinkedAsset);
|
|
State1.SetLinkedStateAsset(&StateTree2);
|
|
State1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
|
|
State1.SetParametersPropertyOverridden(Tree2GlobalParameter_ValueID_Int, true);
|
|
const FInstancedPropertyBag* Parameters = State1.GetDefaultParameters();
|
|
AITEST_TRUE(TEXT("Parameter is invalid"), Parameters != nullptr);
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(State1.Parameters.ID, "Tree2GlobalInt"));
|
|
}
|
|
|
|
FGuid Tree1Sub1Parameter_ValueID_Int;
|
|
UStateTreeState& Sub1 = EditorData1.AddSubTree("Tree1StateSub1");
|
|
{
|
|
Sub1.Type = EStateTreeStateType::Subtree;
|
|
Sub1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::Succeeded);
|
|
|
|
AddDouble(Sub1.Parameters.Parameters, "Tree1StateSub1ParametersDouble");
|
|
Tree1Sub1Parameter_ValueID_Int = AddInt(Sub1.Parameters.Parameters, "Tree1StateSub1ParametersInt");
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Sub1Task1 = Sub1.AddTask<FTestTask_PrintValue>("Tree1StateSub1Task1");
|
|
Sub1Task1.GetInstanceData().Value = -1;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(Sub1.Parameters.ID, "Tree1StateSub1ParametersInt"), FPropertyBindingPath(Sub1Task1.ID, "Value"));
|
|
|
|
{
|
|
UStateTreeState& State3 = Sub1.AddChildState("Tree1State3", EStateTreeStateType::State);
|
|
{
|
|
AddDouble(State3.Parameters.Parameters, "Tree1State3ParametersDouble1");
|
|
AddDouble(State3.Parameters.Parameters, "Tree1State3ParametersDouble2");
|
|
AddInt(State3.Parameters.Parameters, "Tree1State3ParametersInt");
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(Sub1.Parameters.ID, "Tree1StateSub1ParametersInt"), FPropertyBindingPath(State3.Parameters.ID, "Tree1State3ParametersInt"));
|
|
}
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = State3.AddTask<FTestTask_PrintValue>("Tree1State3Task1");
|
|
Task1.GetInstanceData().Value = -1;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(EditorData1.GetRootParametersGuid(), "Tree1GlobalInt"), FPropertyBindingPath(Task1.ID, TEXT("Value")));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task2 = State3.AddTask<FTestTask_PrintValue>("Tree1State3Task2");
|
|
Task2.GetInstanceData().Value = -2;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(Sub1.Parameters.ID, "Tree1StateSub1ParametersInt"), FPropertyBindingPath(Task2.ID, TEXT("Value")));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task3 = State3.AddTask<FTestTask_PrintValue>("Tree1State3Task3");
|
|
Task3.GetInstanceData().Value = -3;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(Task3.ID, "Value"));
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task4 = State3.AddTask<FTestTask_PrintValue>("Tree1State3Task4");
|
|
Task3.GetInstanceData().Value = -4;
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(Task1.ID, "Value"), FPropertyBindingPath(Task4.ID, "Value"));
|
|
|
|
State3.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
|
|
}
|
|
}
|
|
{
|
|
UStateTreeState& State2 = Root.AddChildState("Tree1State2", EStateTreeStateType::Linked);
|
|
State2.SetLinkedState(Sub1.GetLinkToState());
|
|
State2.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::Succeeded);
|
|
|
|
State2.SetParametersPropertyOverridden(Tree1Sub1Parameter_ValueID_Int, true);
|
|
const FInstancedPropertyBag* Parameters = State2.GetDefaultParameters();
|
|
AITEST_TRUE(TEXT("Parameter is invalid"), Parameters != nullptr);
|
|
EditorData1.AddPropertyBinding(FPropertyBindingPath(GlobalTask1.ID, "Value"), FPropertyBindingPath(State2.Parameters.ID, "Tree1StateSub1ParametersInt"));
|
|
}
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler1(Log);
|
|
const bool bResult1 = Compiler1.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree1 should get compiled"), bResult1);
|
|
}
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
}
|
|
{
|
|
FStateTreeReference StateTreeRef;
|
|
StateTreeRef.SetStateTree(&StateTree1);
|
|
StateTreeRef.SetPropertyOverridden(Tree1GlobalParameter_ValueID_Int, true);
|
|
StateTreeRef.GetMutableParameters().SetValueInt32("Tree1GlobalInt", 5);
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Start(FStateTreeExecutionContext::FStartParameters
|
|
{
|
|
.GlobalParameters = &StateTreeRef.GetParameters(),
|
|
});
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree2StateRoot", "Tree2State1"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1GlobalTask1"), Exec.Expect("Tree1GlobalTask1", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1GlobalTask2"), Exec.Expect("Tree1GlobalTask2", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTask1"), Exec.Expect("Tree1StateRootTask1", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTask2"), Exec.Expect("Tree1StateRootTask2", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTask3"), Exec.Expect("Tree1StateRootTask3", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2GlobalTask1"), Exec.Expect("Tree2GlobalTask1", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2GlobalTask2"), Exec.Expect("Tree2GlobalTask2", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2StateRootTask1"), Exec.Expect("Tree2StateRootTask1", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2StateRootTask2"), Exec.Expect("Tree2StateRootTask2", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2StateRootTask3"), Exec.Expect("Tree2StateRootTask3", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2State1Task1"), Exec.Expect("Tree2State1Task1", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2State1Task2"), Exec.Expect("Tree2State1Task2", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2State1Task3"), Exec.Expect("Tree2State1Task3", TEXT("EnterState5")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2State1Task4"), Exec.Expect("Tree2State1Task4", TEXT("EnterState5")));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
FStateTreeReference StateTreeRef;
|
|
StateTreeRef.SetStateTree(&StateTree1);
|
|
StateTreeRef.SetPropertyOverridden(Tree1GlobalParameter_ValueID_Int, true);
|
|
StateTreeRef.GetMutableParameters().SetValueInt32("Tree1GlobalInt", 6);
|
|
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
InstanceData.GetMutableStorage().SetGlobalParameters(StateTreeRef.GetParameters());
|
|
|
|
const EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State2", "Tree1StateSub1", "Tree1State3"));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1GlobalTask1"), Exec.Expect("Tree1GlobalTask1", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1GlobalTask2"), Exec.Expect("Tree1GlobalTask2", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1StateRootTask1"), Exec.Expect("Tree1StateRootTask1", TEXT("Tick6")));
|
|
//@todo Binding on the state are not updated on tick. Is this a bug? See Tree1StateRootParametersInt
|
|
//AITEST_TRUE(TEXT("Tick should tick Tree1StateRootTask2), Exec.Expect("Tree1StateRootTask2", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1StateRootTask3"), Exec.Expect("Tree1StateRootTask3", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2GlobalTask1"), Exec.Expect("Tree2GlobalTask1", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2GlobalTask2"), Exec.Expect("Tree2GlobalTask2", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2StateRootTask1"), Exec.Expect("Tree2StateRootTask1", TEXT("Tick6")));
|
|
//@todo Binding on the state are not updated on tick. Is this a bug? See Tree2StateRootParametersInt
|
|
//AITEST_TRUE(TEXT("Tick should tick Tree2StateRootTask2"), Exec.Expect("Tree2StateRootTask2", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2StateRootTask3"), Exec.Expect("Tree2StateRootTask3", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2State1Task1"), Exec.Expect("Tree2State1Task1", TEXT("Tick6")));
|
|
//@todo Binding on the state are not updated on tick. Is this a bug? See Tree2StateRootParametersInt
|
|
//AITEST_TRUE(TEXT("Tick should tick Tree2State1Task2"), Exec.Expect("Tree2State1Task2", TEXT("Tick6")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2State1Task3"), Exec.Expect("Tree2State1Task3", TEXT("Tick6")));
|
|
//@todo Binding on the state are not updated on tick. Is this a bug? See Tree2State1ParametersInt
|
|
//AITEST_TRUE(TEXT("Tick should tick Tree2State1Task4"), Exec.Expect("Tree2State1Task4", TEXT("Tick6")));
|
|
|
|
AITEST_TRUE(TEXT("Tick should enter Tree1StateSub1Task1"), Exec.Expect("Tree1StateSub1Task1", TEXT("EnterState6")));
|
|
AITEST_TRUE(TEXT("Tick should enter Tree1State3Task1"), Exec.Expect("Tree1State3Task1", TEXT("EnterState6")));
|
|
AITEST_TRUE(TEXT("Tick should enter Tree1State3Task2"), Exec.Expect("Tree1State3Task2", TEXT("EnterState6")));
|
|
AITEST_TRUE(TEXT("Tick should enter Tree1State3Task3"), Exec.Expect("Tree1State3Task3", TEXT("EnterState6")));
|
|
AITEST_TRUE(TEXT("Tick should enter Tree1State3Task4"), Exec.Expect("Tree1State3Task4", TEXT("EnterState6")));
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
Exec.Stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_Linked_GlobalParameter, "System.StateTree.LinkedAsset.GlobalParameter");
|
|
|
|
struct FStateTreeTest_Linked_FinishGlobalTasks : FStateTreeTestBase
|
|
{
|
|
virtual bool InstantTest() override
|
|
{
|
|
//Tree 1
|
|
// Global task and parameter
|
|
// Root
|
|
// StateLinkedTree1 (Tree2) -> Next
|
|
// SubTree2
|
|
// SubTree
|
|
// State3
|
|
//Tree 2
|
|
// Global task and parameter
|
|
// Root
|
|
// State1
|
|
|
|
|
|
// Tree 2
|
|
UStateTree& StateTree2 = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData2 = *Cast<UStateTreeEditorData>(StateTree2.EditorData);
|
|
|
|
// Global tasks
|
|
{
|
|
EditorData2.GlobalTasksCompletion = EStateTreeTaskCompletionType::Any;
|
|
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask1 = EditorData2.AddGlobalTask<FTestTask_PrintValue>("Tree2GlobalTask1");
|
|
GlobalTask1.GetInstanceData().Value = 1;
|
|
|
|
EditorData2.AddGlobalTask<FTestTask_Stand>("Tree2GlobalTask2").GetNode().TicksToCompletion = 99;
|
|
EditorData2.AddGlobalTask<FTestTask_Stand>("Tree2GlobalTask3").GetNode().TicksToCompletion = 99;
|
|
EditorData2.AddGlobalTask<FTestTask_Stand>("Tree2GlobalTask4").GetNode().TicksToCompletion = 99;
|
|
EditorData2.AddGlobalTask<FTestTask_Stand>("Tree2GlobalTask5").GetNode().TicksToCompletion = 99;
|
|
EditorData2.AddGlobalTask<FTestTask_Stand>("Tree2GlobalTask6").GetNode().TicksToCompletion = 99;
|
|
|
|
TStateTreeEditorNode<FTestTask_Stand>& GlobalTask2 = EditorData2.AddGlobalTask<FTestTask_Stand>("Tree2GlobalTask7");
|
|
GlobalTask2.GetNode().TicksToCompletion = 2;
|
|
GlobalTask2.GetNode().TickCompletionResult = EStateTreeRunStatus::Succeeded;
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData2.AddSubTree("Tree2StateRoot");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root.AddTask<FTestTask_PrintValue>("Tree2StateRootTask1");
|
|
Task1.GetInstanceData().Value = 1;
|
|
}
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler2(Log);
|
|
const bool bResult2 = Compiler2.Compile(StateTree2);
|
|
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult2);
|
|
}
|
|
|
|
// Tree 1
|
|
UStateTree& StateTree1 = NewStateTree();
|
|
{
|
|
UStateTreeEditorData& EditorData1 = *Cast<UStateTreeEditorData>(StateTree1.EditorData);
|
|
|
|
// Global tasks
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& GlobalTask1 = EditorData1.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTask1");
|
|
GlobalTask1.GetInstanceData().Value = 1;
|
|
|
|
TStateTreeEditorNode<FTestTask_Stand>& GlobalTask2 = EditorData1.AddGlobalTask<FTestTask_Stand>("Tree1GlobalTask2");
|
|
GlobalTask2.GetNode().TicksToCompletion = 4;
|
|
GlobalTask2.GetNode().TickCompletionResult = EStateTreeRunStatus::Succeeded;
|
|
}
|
|
|
|
UStateTreeState& Root = EditorData1.AddSubTree("Tree1StateRoot");
|
|
{
|
|
TStateTreeEditorNode<FTestTask_PrintValue>& Task1 = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTask1");
|
|
Task1.GetInstanceData().Value = 1;
|
|
}
|
|
{
|
|
UStateTreeState& State1 = Root.AddChildState("Tree1State1", EStateTreeStateType::LinkedAsset);
|
|
State1.SetLinkedStateAsset(&StateTree2);
|
|
State1.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::NextState);
|
|
}
|
|
{
|
|
UStateTreeState& State2 = Root.AddChildState("Tree1State2", EStateTreeStateType::State);
|
|
State2.AddTransition(EStateTreeTransitionTrigger::OnStateSucceeded, EStateTreeTransitionType::Succeeded);
|
|
|
|
TStateTreeEditorNode<FTestTask_Stand>& Task1 = State2.AddTask<FTestTask_Stand>("Tree1State2Task1");
|
|
Task1.GetNode().TicksToCompletion = 10;
|
|
Task1.GetNode().TickCompletionResult = EStateTreeRunStatus::Succeeded;
|
|
}
|
|
|
|
FStateTreeCompilerLog Log;
|
|
FStateTreeCompiler Compiler1(Log);
|
|
const bool bResult1 = Compiler1.Compile(StateTree1);
|
|
AITEST_TRUE(TEXT("StateTree1 should get compiled"), bResult1);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < 4; ++Index)
|
|
{
|
|
Private::FScopedCVarBool CVarGGlobalTasksCompleteOwningFrame = TEXT("StateTree.GlobalTasksCompleteOwningFrame");
|
|
const bool bGlobalTasksCompleteOwningFrame = (Index % 2) == 0;
|
|
CVarGGlobalTasksCompleteOwningFrame.Set(bGlobalTasksCompleteOwningFrame);
|
|
|
|
Private::FScopedCVarBool CVarTickGlobalNodesWithHierarchy = TEXT("StateTree.TickGlobalNodesFollowingTreeHierarchy");
|
|
const bool bTickGlobalNodesWithHierarchy = Index >= 2;
|
|
CVarTickGlobalNodesWithHierarchy.Set(bTickGlobalNodesWithHierarchy);
|
|
|
|
FStateTreeInstanceData InstanceData;
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const bool bInitSucceeded = Exec.IsValid();
|
|
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Start();
|
|
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree2StateRoot"));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1GlobalTask1"), Exec.Expect("Tree1GlobalTask1", TEXT("EnterState1")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1GlobalTask2"), Exec.Expect("Tree1GlobalTask2", TEXT("EnterState")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree1StateRootTask1"), Exec.Expect("Tree1StateRootTask1", TEXT("EnterState1")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2GlobalTask1"), Exec.Expect("Tree2GlobalTask1", TEXT("EnterState1")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2GlobalTask7"), Exec.Expect("Tree2GlobalTask7", TEXT("EnterState")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2StateRootTask1"), Exec.Expect("Tree2StateRootTask1", TEXT("EnterState1")));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree2StateRoot"));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1GlobalTask1"), Exec.Expect("Tree1GlobalTask1", TEXT("Tick1")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1GlobalTask2"), Exec.Expect("Tree1GlobalTask2", TEXT("Tick")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1StateRootTask1"), Exec.Expect("Tree1StateRootTask1", TEXT("Tick1")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2GlobalTask1"), Exec.Expect("Tree2GlobalTask1", TEXT("Tick1")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2GlobalTask7"), Exec.Expect("Tree2GlobalTask7", TEXT("Tick")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2StateRootTask1"), Exec.Expect("Tree2StateRootTask1", TEXT("Tick1")));
|
|
Exec.LogClear();
|
|
}
|
|
if (bGlobalTasksCompleteOwningFrame)
|
|
{
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State2"));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1GlobalTask1"), Exec.Expect("Tree1GlobalTask1", TEXT("Tick1")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1GlobalTask2"), Exec.Expect("Tree1GlobalTask2", TEXT("Tick")));
|
|
if (bTickGlobalNodesWithHierarchy)
|
|
{
|
|
AITEST_TRUE(TEXT("Tick should tick Tree1StateRootTask1"), Exec.Expect("Tree1StateRootTask1", TEXT("Tick1")));
|
|
}
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2GlobalTask1"), Exec.Expect("Tree2GlobalTask1", TEXT("Tick1")));
|
|
AITEST_TRUE(TEXT("Tick should tick Tree2GlobalTask7"), Exec.Expect("Tree2GlobalTask7", TEXT("Tick")));
|
|
if (bTickGlobalNodesWithHierarchy)
|
|
{
|
|
AITEST_FALSE(TEXT("Tick not should tick Tree2StateRootTask1"), Exec.Expect("Tree2StateRootTask1", TEXT("Tick1")));
|
|
}
|
|
|
|
AITEST_TRUE(TEXT("Tick should ExitState Tree2StateRootTask1"), Exec.Expect("Tree2StateRootTask1", TEXT("ExitState1")));
|
|
AITEST_TRUE(TEXT("Tick should ExitState Tree2GlobalTask7"), Exec.Expect("Tree2GlobalTask7", TEXT("ExitState")));
|
|
AITEST_TRUE(TEXT("Tick should ExitState Tree2GlobalTask1"), Exec.Expect("Tree2GlobalTask1", TEXT("ExitState1")));
|
|
AITEST_TRUE(TEXT("Start should enter Tree2State2Task1"), Exec.Expect("Tree1State2Task1", TEXT("EnterState")));
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Succeeded);
|
|
Exec.LogClear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
const EStateTreeRunStatus Status = Exec.Tick(1.0f);
|
|
AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Succeeded);
|
|
Exec.LogClear();
|
|
}
|
|
{
|
|
FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData);
|
|
Exec.Stop();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_Linked_FinishGlobalTasks, "System.StateTree.LinkedAsset.FinishGlobalTasks");
|
|
|
|
} // namespace UE::StateTree::Tests
|
|
|
|
UE_ENABLE_OPTIMIZATION_SHIP
|
|
|
|
#undef LOCTEXT_NAMESPACE
|