Add Nested node to BT

This commit is contained in:
Wojtek Figat
2023-08-21 17:38:48 +02:00
parent a6e503d21b
commit cc5cde5bc7
5 changed files with 132 additions and 4 deletions

View File

@@ -79,6 +79,7 @@ void Behavior::OnLateUpdate()
context.Behavior = this;
context.Knowledge = &_knowledge;
context.Memory = _knowledge.Memory;
context.RelevantNodes = &_knowledge.RelevantNodes;
context.DeltaTime = updateDeltaTime;
const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context);
if (result != BehaviorUpdateResult::Running)

View File

@@ -13,6 +13,7 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableS
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject);
friend class BehaviorTreeGraph;
friend class BehaviorKnowledge;
friend class BehaviorTreeSubTreeNode;
protected:
// Raw memory byte offset from the start of the behavior memory block.

View File

@@ -4,15 +4,49 @@
#include "Behavior.h"
#include "BehaviorKnowledge.h"
#include "Engine/Core/Random.h"
#include "Engine/Scripting/Scripting.h"
#if USE_CSHARP
#include "Engine/Scripting/ManagedCLR/MClass.h"
#endif
#include "Engine/Serialization/Serialization.h"
bool IsAssignableFrom(const StringAnsiView& to, const StringAnsiView& from)
{
// Special case of null
if (to.IsEmpty())
return from.IsEmpty();
if (from.IsEmpty())
return false;
// Exact typename math
if (to == from)
return true;
// Scripting Type match
const ScriptingTypeHandle typeHandleTo = Scripting::FindScriptingType(to);
const ScriptingTypeHandle typeHandleFrom = Scripting::FindScriptingType(from);
if (typeHandleTo && typeHandleFrom)
return typeHandleTo.IsAssignableFrom(typeHandleFrom);
#if USE_CSHARP
// MClass match
const auto mclassTo = Scripting::FindClass(to);
const auto mclassFrom = Scripting::FindClass(from);
if (mclassTo && mclassFrom)
return mclassTo == mclassFrom || mclassFrom->IsSubClassOf(mclassTo);
#endif
return false;
}
BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context)
{
ASSERT_LOW_LAYER(_executionIndex != -1);
if (context.Knowledge->RelevantNodes.Get(_executionIndex) == false)
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
if (relevantNodes.Get(_executionIndex) == false)
{
// Node becomes relevant so initialize it's state
context.Knowledge->RelevantNodes.Set(_executionIndex, true);
relevantNodes.Set(_executionIndex, true);
InitState(context.Behavior, context.Memory);
}
@@ -22,7 +56,7 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
// Check if node is not relevant anymore
if (result != BehaviorUpdateResult::Running)
{
context.Knowledge->RelevantNodes.Set(_executionIndex, false);
relevantNodes.Set(_executionIndex, false);
ReleaseState(context.Behavior, context.Memory);
}
@@ -152,3 +186,63 @@ BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context
state->TimeLeft -= context.DeltaTime;
return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Success : BehaviorUpdateResult::Running;
}
int32 BehaviorTreeSubTreeNode::GetStateSize() const
{
return sizeof(State);
}
void BehaviorTreeSubTreeNode::InitState(Behavior* behavior, void* memory)
{
auto state = GetState<State>(memory);
new(state)State();
const BehaviorTree* tree = Tree.Get();
if (!tree || tree->WaitForLoaded())
return;
state->Memory.Resize(tree->Graph.NodesStatesSize);
state->RelevantNodes.Resize(tree->Graph.NodesCount, false);
state->RelevantNodes.SetAll(false);
}
void BehaviorTreeSubTreeNode::ReleaseState(Behavior* behavior, void* memory)
{
auto state = GetState<State>(memory);
const BehaviorTree* tree = Tree.Get();
if (tree && tree->IsLoaded())
{
for (const auto& node : tree->Graph.Nodes)
{
if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes[node.Instance->_executionIndex])
node.Instance->ReleaseState(behavior, state->Memory.Get());
}
}
state->~State();
}
BehaviorUpdateResult BehaviorTreeSubTreeNode::Update(BehaviorUpdateContext context)
{
const BehaviorTree* tree = Tree.Get();
if (!tree || !tree->Graph.Root)
return BehaviorUpdateResult::Failed;
const StringAnsiView treeBlackboardType = tree->Graph.Root->BlackboardType;
if (treeBlackboardType.HasChars())
{
// Validate if nested tree blackboard data matches (the same type or base type)
const VariantType& blackboardType = context.Knowledge->Blackboard.Type;
if (IsAssignableFrom(treeBlackboardType, StringAnsiView(blackboardType.GetTypeName())))
{
LOG(Error, "Cannot use nested '{}' with Blackboard of type '{}' inside '{}' with Blackboard of type '{}'",
tree->ToString(), String(treeBlackboardType),
context.Knowledge->Tree->ToString(), blackboardType.ToString());
return BehaviorUpdateResult::Failed;
}
}
// Override memory with custom one for the subtree
auto state = GetState<State>(context.Memory);
context.Memory = state->Memory.Get();
context.RelevantNodes = &state->RelevantNodes;
// Run nested tree
return tree->Graph.Root->InvokeUpdate(context);
}

View File

@@ -6,6 +6,7 @@
#include "BehaviorTreeNode.h"
#include "BehaviorKnowledgeSelector.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Content/AssetReference.h"
/// <summary>
/// Base class for compound Behavior Tree nodes that composite child nodes.
@@ -114,3 +115,29 @@ private:
float TimeLeft;
};
};
/// <summary>
/// Sub-tree node runs a nested Behavior Tree within this tree.
/// </summary>
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeSubTreeNode : public BehaviorTreeNode
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSubTreeNode, BehaviorTreeNode);
API_AUTO_SERIALIZATION();
// Nested behavior tree to execute within this node.
API_FIELD(Attributes="EditorOrder(10), Limit(0)")
AssetReference<BehaviorTree> Tree;
public:
// [BehaviorTreeNode]
int32 GetStateSize() const override;
void InitState(Behavior* behavior, void* memory) override;
void ReleaseState(Behavior* behavior, void* memory) override;
BehaviorUpdateResult Update(BehaviorUpdateContext context) override;
struct State
{
Array<byte> Memory;
BitArray<> RelevantNodes;
};
};

View File

@@ -12,7 +12,7 @@ class BehaviorKnowledge;
/// <summary>
/// Behavior update context state.
/// </summary>
API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext
API_STRUCT(NoDefault) struct FLAXENGINE_API BehaviorUpdateContext
{
DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext);
@@ -31,6 +31,11 @@ API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext
/// </summary>
API_FIELD() void* Memory;
/// <summary>
/// Pointer to array with per-node bit indicating whether node is relevant (active in graph with state created).
/// </summary>
API_FIELD() void* RelevantNodes;
/// <summary>
/// Simulation time delta (in seconds) since the last update.
/// </summary>