Add Time Limit and Cooldown decorators to BT
This commit is contained in:
@@ -31,6 +31,7 @@ void Behavior::StopLogic(BehaviorUpdateResult result)
|
||||
if (_result != BehaviorUpdateResult::Running || result == BehaviorUpdateResult::Running)
|
||||
return;
|
||||
_accumulatedTime = 0.0f;
|
||||
_totalTime = 0;
|
||||
_result = result;
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ void Behavior::ResetLogic()
|
||||
// Reset state
|
||||
_knowledge.FreeMemory();
|
||||
_accumulatedTime = 0.0f;
|
||||
_totalTime = 0;
|
||||
_result = BehaviorUpdateResult::Success;
|
||||
|
||||
if (isActive)
|
||||
@@ -73,6 +75,7 @@ void Behavior::OnLateUpdate()
|
||||
if (_accumulatedTime < updateDeltaTime)
|
||||
return;
|
||||
_accumulatedTime -= updateDeltaTime;
|
||||
_totalTime += updateDeltaTime;
|
||||
|
||||
// Update tree
|
||||
BehaviorUpdateContext context;
|
||||
@@ -81,6 +84,7 @@ void Behavior::OnLateUpdate()
|
||||
context.Memory = _knowledge.Memory;
|
||||
context.RelevantNodes = &_knowledge.RelevantNodes;
|
||||
context.DeltaTime = updateDeltaTime;
|
||||
context.Time = _totalTime;
|
||||
const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context);
|
||||
if (result != BehaviorUpdateResult::Running)
|
||||
_result = result;
|
||||
|
||||
@@ -19,6 +19,7 @@ API_CLASS() class FLAXENGINE_API Behavior : public Script
|
||||
private:
|
||||
BehaviorKnowledge _knowledge;
|
||||
float _accumulatedTime = 0.0f;
|
||||
float _totalTime = 0.0f;
|
||||
BehaviorUpdateResult _result = BehaviorUpdateResult::Success;
|
||||
void* _memory = nullptr;
|
||||
|
||||
|
||||
@@ -112,9 +112,8 @@ void BehaviorTreeGraph::LoadRecursive(Node& node)
|
||||
for (int32 i = 0; i < ids.Length(); i++)
|
||||
{
|
||||
Node* decorator = GetNode(ids[i]);
|
||||
if (decorator && decorator->Instance)
|
||||
if (decorator && decorator->Instance && decorator->Instance->Is<BehaviorTreeDecorator>())
|
||||
{
|
||||
ASSERT_LOW_LAYER(decorator->Instance->Is<BehaviorTreeDecorator>());
|
||||
node.Instance->_decorators.Add((BehaviorTreeDecorator*)decorator->Instance);
|
||||
decorator->Instance->_parent = node.Instance;
|
||||
LoadRecursive(*decorator);
|
||||
|
||||
@@ -81,9 +81,9 @@ 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);
|
||||
virtual 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);
|
||||
virtual void BecomeIrrelevant(const BehaviorUpdateContext& context);
|
||||
|
||||
// [SerializableScriptingObject]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
|
||||
@@ -60,12 +60,21 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
|
||||
if (relevantNodes.Get(_executionIndex) == false)
|
||||
BecomeRelevant(context);
|
||||
|
||||
// Node-specific update
|
||||
// Update decorators
|
||||
bool decoratorFailed = false;
|
||||
for (BehaviorTreeDecorator* decorator : _decorators)
|
||||
{
|
||||
decorator->Update(context);
|
||||
decoratorFailed |= decorator->Update(context) == BehaviorUpdateResult::Failed;
|
||||
}
|
||||
BehaviorUpdateResult result = Update(context);
|
||||
|
||||
// Node-specific update
|
||||
BehaviorUpdateResult result;
|
||||
if (decoratorFailed)
|
||||
result = BehaviorUpdateResult::Failed;
|
||||
else
|
||||
result = Update(context);
|
||||
|
||||
// Post-process result from decorators
|
||||
for (BehaviorTreeDecorator* decorator : _decorators)
|
||||
{
|
||||
decorator->PostUpdate(context, result);
|
||||
@@ -87,7 +96,7 @@ void BehaviorTreeNode::BecomeRelevant(const BehaviorUpdateContext& context)
|
||||
InitState(context.Behavior, context.Memory);
|
||||
}
|
||||
|
||||
void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly)
|
||||
void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context)
|
||||
{
|
||||
// Release state
|
||||
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
|
||||
@@ -95,9 +104,6 @@ void BehaviorTreeNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bo
|
||||
relevantNodes.Set(_executionIndex, false);
|
||||
ReleaseState(context.Behavior, context.Memory);
|
||||
|
||||
if (nodeOnly)
|
||||
return;
|
||||
|
||||
// Release decorators
|
||||
for (BehaviorTreeDecorator* decorator : _decorators)
|
||||
{
|
||||
@@ -141,7 +147,7 @@ BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext cont
|
||||
return result;
|
||||
}
|
||||
|
||||
void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly)
|
||||
void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& context)
|
||||
{
|
||||
// Make any nested nodes irrelevant as well
|
||||
const BitArray<>& relevantNodes = *(const BitArray<>*)context.RelevantNodes;
|
||||
@@ -153,7 +159,7 @@ void BehaviorTreeCompoundNode::BecomeIrrelevant(const BehaviorUpdateContext& con
|
||||
}
|
||||
}
|
||||
|
||||
BehaviorTreeNode::BecomeIrrelevant(context, nodeOnly);
|
||||
BehaviorTreeNode::BecomeIrrelevant(context);
|
||||
}
|
||||
|
||||
int32 BehaviorTreeSequenceNode::GetStateSize() const
|
||||
@@ -237,7 +243,7 @@ void BehaviorTreeDelayNode::InitState(Behavior* behavior, void* memory)
|
||||
auto state = GetState<State>(memory);
|
||||
if (!WaitTimeSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft))
|
||||
state->TimeLeft = WaitTime;
|
||||
state->TimeLeft = Random::RandRange(Math::Max(WaitTime - RandomDeviation, 0.0f), WaitTime + RandomDeviation);
|
||||
state->TimeLeft = Random::RandRange(Math::Max(state->TimeLeft - RandomDeviation, 0.0f), state->TimeLeft + RandomDeviation);
|
||||
}
|
||||
|
||||
BehaviorUpdateResult BehaviorTreeDelayNode::Update(BehaviorUpdateContext context)
|
||||
@@ -272,7 +278,7 @@ void BehaviorTreeSubTreeNode::ReleaseState(Behavior* behavior, void* memory)
|
||||
{
|
||||
for (const auto& node : tree->Graph.Nodes)
|
||||
{
|
||||
if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes[node.Instance->_executionIndex])
|
||||
if (node.Instance && node.Instance->_executionIndex != -1 && state->RelevantNodes.HasItems() && state->RelevantNodes[node.Instance->_executionIndex])
|
||||
node.Instance->ReleaseState(behavior, state->Memory.Get());
|
||||
}
|
||||
}
|
||||
@@ -354,10 +360,69 @@ void BehaviorTreeLoopDecorator::PostUpdate(BehaviorUpdateContext context, Behavi
|
||||
state->Loops--;
|
||||
if (state->Loops > 0)
|
||||
{
|
||||
// Keep running in a loop but reset node's state (preserve decorators state including Loops counter)
|
||||
// Keep running in a loop but reset node's state (preserve self state)
|
||||
result = BehaviorUpdateResult::Running;
|
||||
_parent->BecomeIrrelevant(context, true);
|
||||
_parent->BecomeRelevant(context);
|
||||
BitArray<>& relevantNodes = *(BitArray<>*)context.RelevantNodes;
|
||||
relevantNodes.Set(_executionIndex, false);
|
||||
_parent->BecomeIrrelevant(context);
|
||||
relevantNodes.Set(_executionIndex, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 BehaviorTreeTimeLimitDecorator::GetStateSize() const
|
||||
{
|
||||
return sizeof(State);
|
||||
}
|
||||
|
||||
void BehaviorTreeTimeLimitDecorator::InitState(Behavior* behavior, void* memory)
|
||||
{
|
||||
auto state = GetState<State>(memory);
|
||||
if (!MaxDurationSelector.TryGet(behavior->GetKnowledge(), state->TimeLeft))
|
||||
state->TimeLeft = MaxDuration;
|
||||
state->TimeLeft = Random::RandRange(Math::Max(state->TimeLeft - RandomDeviation, 0.0f), state->TimeLeft + RandomDeviation);
|
||||
}
|
||||
|
||||
BehaviorUpdateResult BehaviorTreeTimeLimitDecorator::Update(BehaviorUpdateContext context)
|
||||
{
|
||||
auto state = GetState<State>(context.Memory);
|
||||
state->TimeLeft -= context.DeltaTime;
|
||||
return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Failed : BehaviorUpdateResult::Success;
|
||||
}
|
||||
|
||||
int32 BehaviorTreeCooldownDecorator::GetStateSize() const
|
||||
{
|
||||
return sizeof(State);
|
||||
}
|
||||
|
||||
void BehaviorTreeCooldownDecorator::InitState(Behavior* behavior, void* memory)
|
||||
{
|
||||
auto state = GetState<State>(memory);
|
||||
state->EndTime = 0; // Allow to entry on start
|
||||
}
|
||||
|
||||
void BehaviorTreeCooldownDecorator::ReleaseState(Behavior* behavior, void* memory)
|
||||
{
|
||||
// Preserve the decorator's state to keep cooldown
|
||||
BitArray<>& relevantNodes = behavior->GetKnowledge()->RelevantNodes;
|
||||
relevantNodes.Set(_executionIndex, true);
|
||||
}
|
||||
|
||||
bool BehaviorTreeCooldownDecorator::CanUpdate(BehaviorUpdateContext context)
|
||||
{
|
||||
auto state = GetState<State>(context.Memory);
|
||||
return state->EndTime <= context.Time;
|
||||
}
|
||||
|
||||
void BehaviorTreeCooldownDecorator::PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result)
|
||||
{
|
||||
if (result != BehaviorUpdateResult::Running)
|
||||
{
|
||||
// Initialize cooldown
|
||||
auto state = GetState<State>(context.Memory);
|
||||
if (!MinDurationSelector.TryGet(context.Knowledge, state->EndTime))
|
||||
state->EndTime = MinDuration;
|
||||
state->EndTime = Random::RandRange(Math::Max(state->EndTime - RandomDeviation, 0.0f), state->EndTime + RandomDeviation);
|
||||
state->EndTime += context.Time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
|
||||
protected:
|
||||
// [BehaviorTreeNode]
|
||||
void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) override;
|
||||
void BecomeIrrelevant(const BehaviorUpdateContext& context) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -226,3 +226,69 @@ public:
|
||||
int32 Loops;
|
||||
};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Limits maximum duration of the node execution time (in seconds). Node will fail if it runs out of time.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeTimeLimitDecorator : public BehaviorTreeDecorator
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeTimeLimitDecorator, BehaviorTreeDecorator);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
// Maximum node execution time (in seconds). Unused if MaxDurationSelector is used.
|
||||
API_FIELD(Attributes="EditorOrder(10), Limit(0)")
|
||||
float MaxDuration = 3.0;
|
||||
|
||||
// Duration time randomization range to deviate original value.
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0)")
|
||||
float RandomDeviation = 0.0f;
|
||||
|
||||
// Maximum node execution time (in seconds) from behavior's knowledge (blackboard, goal or sensor). If set, overrides MaxDuration but still uses RandomDeviation.
|
||||
API_FIELD(Attributes="EditorOrder(20)")
|
||||
BehaviorKnowledgeSelector<float> MaxDurationSelector;
|
||||
|
||||
public:
|
||||
// [BehaviorTreeNode]
|
||||
int32 GetStateSize() const override;
|
||||
void InitState(Behavior* behavior, void* memory) override;
|
||||
BehaviorUpdateResult Update(BehaviorUpdateContext context) override;
|
||||
|
||||
struct State
|
||||
{
|
||||
float TimeLeft;
|
||||
};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Adds cooldown in between node executions.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeCooldownDecorator : public BehaviorTreeDecorator
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeCooldownDecorator, BehaviorTreeDecorator);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
// Minimum cooldown time (in seconds). Unused if MinDurationSelector is used.
|
||||
API_FIELD(Attributes="EditorOrder(10), Limit(0)")
|
||||
float MinDuration = 3.0;
|
||||
|
||||
// Duration time randomization range to deviate original value.
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0)")
|
||||
float RandomDeviation = 0.0f;
|
||||
|
||||
// Minimum cooldown time (in seconds) from behavior's knowledge (blackboard, goal or sensor). If set, overrides MinDuration but still uses RandomDeviation.
|
||||
API_FIELD(Attributes="EditorOrder(20)")
|
||||
BehaviorKnowledgeSelector<float> MinDurationSelector;
|
||||
|
||||
public:
|
||||
// [BehaviorTreeNode]
|
||||
int32 GetStateSize() const override;
|
||||
void InitState(Behavior* behavior, void* memory) override;
|
||||
void ReleaseState(Behavior* behavior, void* memory) override;
|
||||
bool CanUpdate(BehaviorUpdateContext context) override;
|
||||
void PostUpdate(BehaviorUpdateContext context, BehaviorUpdateResult& result) override;
|
||||
|
||||
struct State
|
||||
{
|
||||
float EndTime;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -40,6 +40,11 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API BehaviorUpdateContext
|
||||
/// Simulation time delta (in seconds) since the last update.
|
||||
/// </summary>
|
||||
API_FIELD() float DeltaTime;
|
||||
|
||||
/// <summary>
|
||||
/// Simulation time (in seconds) since the first update of the Behavior (sum of all deltas since the start).
|
||||
/// </summary>
|
||||
API_FIELD() float Time;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user