Add Nested node to BT
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user