Add Decorators support to BT graph

This commit is contained in:
2023-08-24 13:05:54 +02:00
parent 0c206564be
commit 69ab69c5cc
7 changed files with 566 additions and 121 deletions
+22 -1
View File
@@ -65,7 +65,7 @@ void BehaviorTreeGraph::Clear()
bool BehaviorTreeGraph::onNodeLoaded(Node* n)
{
if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2))
if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2 || n->TypeID == 3))
{
// Create node instance object
ScriptingTypeHandle type = Scripting::FindScriptingType((StringAnsiView)n->Values[0]);
@@ -102,6 +102,26 @@ void BehaviorTreeGraph::LoadRecursive(Node& node)
NodesStatesSize += node.Instance->GetStateSize();
NodesCount++;
if (node.TypeID == 1 && node.Values.Count() >= 3)
{
// Load node decorators
const auto& decoratorIds = node.Values[2];
if (decoratorIds.Type.Type == VariantType::Blob && decoratorIds.AsBlob.Length)
{
const Span<uint32> ids((uint32*)decoratorIds.AsBlob.Data, decoratorIds.AsBlob.Length / sizeof(uint32));
for (int32 i = 0; i < ids.Length(); i++)
{
Node* decorator = GetNode(ids[i]);
if (decorator && decorator->Instance)
{
ASSERT_LOW_LAYER(decorator->Instance->Is<BehaviorTreeDecorator>());
node.Instance->_decorators.Add((BehaviorTreeDecorator*)decorator->Instance);
decorator->Instance->_parent = node.Instance;
LoadRecursive(*decorator);
}
}
}
}
if (auto* nodeCompound = ScriptingObject::Cast<BehaviorTreeCompoundNode>(node.Instance))
{
auto& children = node.Boxes[1].Connections;
@@ -116,6 +136,7 @@ void BehaviorTreeGraph::LoadRecursive(Node& node)
if (child && child->Instance)
{
nodeCompound->Children.Add(child->Instance);
child->Instance->_parent = nodeCompound;
LoadRecursive(*child);
}
}
+36 -3
View File
@@ -2,6 +2,7 @@
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Engine/Scripting/SerializableScriptingObject.h"
#include "BehaviorTypes.h"
@@ -21,6 +22,11 @@ protected:
API_FIELD(ReadOnly) int32 _memoryOffset = 0;
// Execution index of the node within tree.
API_FIELD(ReadOnly) int32 _executionIndex = -1;
// Parent node that owns this node (parent composite or decorator attachment node).
API_FIELD(ReadOnly) BehaviorTreeNode* _parent = nullptr;
private:
Array<class BehaviorTreeDecorator*, InlinedAllocation<8>> _decorators;
public:
/// <summary>
@@ -74,6 +80,10 @@ public:
// Helper utility to update node with state creation/cleanup depending on node relevancy.
BehaviorUpdateResult InvokeUpdate(const BehaviorUpdateContext& context);
// Helper utility to make node relevant and init it state.
void BecomeRelevant(const BehaviorUpdateContext& context);
// Helper utility to make node irrelevant and release its state (including any nested nodes).
virtual void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly = false);
// [SerializableScriptingObject]
void Serialize(SerializeStream& stream, const void* otherObj) override;
@@ -87,8 +97,31 @@ public:
ASSERT((int32)sizeof(T) <= GetStateSize());
return reinterpret_cast<T*>((byte*)memory + _memoryOffset);
}
};
protected:
virtual void InvokeReleaseState(const BehaviorUpdateContext& context);
};
/// <summary>
/// Base class for Behavior Tree node decorators. Decorators can implement conditional filtering or override node logic and execution flow.
/// </summary>
API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeDecorator : public BehaviorTreeNode
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeDecorator, BehaviorTreeNode);
/// <summary>
/// Checks if the node can be updated (eg. decorator can block it depending on the gameplay conditions or its state).
/// </summary>
/// <param name="context">Behavior update context data.</param>
/// <returns>True if can update, otherwise false to block it.</returns>
API_FUNCTION() virtual bool CanUpdate(BehaviorUpdateContext context)
{
return true;
}
/// <summary>
/// Called after node update to post-process result or perform additional action.
/// </summary>
/// <param name="context">Behavior update context data.</param>
/// <param name="result">The node update result. Can be modified by the decorator (eg. to force success).</param>
API_FUNCTION() virtual void PostUpdate(BehaviorUpdateContext context, API_PARAM(ref) BehaviorUpdateResult& result)
{
}
};
+59 -19
View File
@@ -42,26 +42,72 @@ bool IsAssignableFrom(const StringAnsiView& to, const StringAnsiView& from)
BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext& context)
{
ASSERT_LOW_LAYER(_executionIndex != -1);
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
if (relevantNodes.Get(_executionIndex) == false)
const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes;
// Check decorators if node can be executed
for (BehaviorTreeDecorator* decorator : _decorators)
{
// Node becomes relevant so initialize it's state
relevantNodes.Set(_executionIndex, true);
InitState(context.Behavior, context.Memory);
ASSERT_LOW_LAYER(decorator->_executionIndex != -1);
if (relevantNodes.Get(decorator->_executionIndex) == false)
decorator->BecomeRelevant(context);
if (!decorator->CanUpdate(context))
{
return BehaviorUpdateResult::Failed;
}
}
// Make node relevant before update
if (relevantNodes.Get(_executionIndex) == false)
BecomeRelevant(context);
// Node-specific update
const BehaviorUpdateResult result = Update(context);
for (BehaviorTreeDecorator* decorator : _decorators)
{
decorator->Update(context);
}
BehaviorUpdateResult result = Update(context);
for (BehaviorTreeDecorator* decorator : _decorators)
{
decorator->PostUpdate(context, result);
}
// Check if node is not relevant anymore
if (result != BehaviorUpdateResult::Running)
{
InvokeReleaseState(context);
}
BecomeIrrelevant(context);
return result;
}
void BehaviorTreeNode::BecomeRelevant(const BehaviorUpdateContext& context)
{
// Initialize state
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
ASSERT_LOW_LAYER(relevantNodes.Get(_executionIndex) == false);
relevantNodes.Set(_executionIndex, true);
InitState(context.Behavior, context.Memory);
}
void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly)
{
// Release state
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
ASSERT_LOW_LAYER(relevantNodes.Get(_executionIndex) == true);
relevantNodes.Set(_executionIndex, false);
ReleaseState(context.Behavior, context.Memory);
if (nodeOnly)
return;
// Release decorators
for (BehaviorTreeDecorator* decorator : _decorators)
{
if (relevantNodes.Get(decorator->_executionIndex) == true)
{
decorator->BecomeIrrelevant(context);
}
}
}
void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj)
{
SerializableScriptingObject::Serialize(stream, otherObj);
@@ -78,13 +124,6 @@ void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier
DESERIALIZE(Name);
}
void BehaviorTreeNode::InvokeReleaseState(const BehaviorUpdateContext& context)
{
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
relevantNodes.Set(_executionIndex, false);
ReleaseState(context.Behavior, context.Memory);
}
void BehaviorTreeCompoundNode::Init(BehaviorTree* tree)
{
for (BehaviorTreeNode* child : Children)
@@ -102,18 +141,19 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont
return result;
}
void BehaviorTreeCompoundNode::InvokeReleaseState(const BehaviorUpdateContext& context)
void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly)
{
// Make any nested nodes irrelevant as well
const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes;
for (BehaviorTreeNode* child : Children)
{
if (relevantNodes.Get(child->_executionIndex) == true)
{
child->InvokeReleaseState(context);
child->BecomeIrrelevant(context);
}
}
BehaviorTreeNode::InvokeReleaseState(context);
BehaviorTreeNode::BecomeIrrelevant(context, nodeOnly);
}
int32 BehaviorTreeSequenceNode::GetStateSize() const
+1 -1
View File
@@ -28,7 +28,7 @@ public:
protected:
// [BehaviorTreeNode]
void InvokeReleaseState(const BehaviorUpdateContext& context) override;
void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) override;
};
/// <summary>