Add Decorators support to BT graph

This commit is contained in:
Wojtek Figat
2023-08-24 13:05:54 +02:00
parent 0c206564be
commit 69ab69c5cc
7 changed files with 566 additions and 121 deletions

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
@@ -17,26 +19,16 @@ namespace FlaxEditor.Surface.Archetypes
public static class BehaviorTree
{
/// <summary>
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree node.
/// Base class for Behavior Tree nodes wrapped inside <see cref="SurfaceNode" />.
/// </summary>
internal class Node : SurfaceNode
internal class NodeBase : SurfaceNode
{
private const float ConnectionAreaMargin = 12.0f;
private const float ConnectionAreaHeight = 12.0f;
private ScriptType _type;
private InputBox _input;
private OutputBox _output;
protected ScriptType _type;
internal bool _isValueEditing;
public BehaviorTreeNode Instance;
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
{
return new Node(id, context, nodeArch, groupArch);
}
internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
protected NodeBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
@@ -48,11 +40,13 @@ namespace FlaxEditor.Surface.Archetypes
title = title.Substring(12);
if (title.EndsWith("Node"))
title = title.Substring(0, title.Length - 4);
if (title.EndsWith("Decorator"))
title = title.Substring(0, title.Length - 9);
title = Utilities.Utils.GetPropertyNameUI(title);
return title;
}
private void UpdateTitle()
protected virtual void UpdateTitle()
{
string title = null;
if (Instance != null)
@@ -66,34 +60,18 @@ namespace FlaxEditor.Surface.Archetypes
var typeName = (string)Values[0];
title = "Missing Type " + typeName;
}
if (title != Title)
{
Title = title;
ResizeAuto();
}
Title = title;
}
/// <inheritdoc />
public override void OnLoaded()
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded();
// Setup boxes
_input = (InputBox)GetBox(0);
_output = (OutputBox)GetBox(1);
base.OnLoaded(action);
// Setup node type and data
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
var flags = Archetype.Flags & ~flagsRoot;
var typeName = (string)Values[0];
_type = TypeUtils.GetType(typeName);
if (_type != null)
{
bool isRoot = _type.Type == typeof(BehaviorTreeRootNode);
_input.Enabled = _input.Visible = !isRoot;
_output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type);
if (isRoot)
flags |= flagsRoot;
TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type);
try
{
@@ -112,6 +90,259 @@ namespace FlaxEditor.Surface.Archetypes
{
Instance = null;
}
UpdateTitle();
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
// Skip updating instance when it's being edited by user via UI
if (!_isValueEditing)
{
try
{
if (Instance != null)
{
// Reload node instance from data
var instanceData = (byte[])Values[1];
if (instanceData == null || instanceData.Length == 0)
{
// Recreate instance data to default state if previous state was empty
var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType
instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance);
}
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
}
}
catch (Exception ex)
{
Editor.LogError("Failed to load Behavior Tree node of type " + _type);
Editor.LogWarning(ex);
}
}
UpdateTitle();
}
public override void OnSpawned(SurfaceNodeActions action)
{
base.OnSpawned(action);
ResizeAuto();
}
public override void OnDestroy()
{
if (IsDisposing)
return;
_type = ScriptType.Null;
Object.Destroy(ref Instance);
base.OnDestroy();
}
}
/// <summary>
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree node.
/// </summary>
internal class Node : NodeBase
{
private const float ConnectionAreaMargin = 12.0f;
private const float ConnectionAreaHeight = 12.0f;
private const float DecoratorsMarginX = 5.0f;
private const float DecoratorsMarginY = 2.0f;
private InputBox _input;
private OutputBox _output;
internal List<Decorator> _decorators;
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
{
return new Node(id, context, nodeArch, groupArch);
}
internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
public unsafe List<uint> DecoratorIds
{
get
{
var result = new List<uint>();
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
if (ids != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
int count = ids.Length / sizeof(uint);
for (int i = 0; i < count; i++)
result.Add(ptr[i]);
}
}
return result;
}
set
{
var ids = new byte[sizeof(uint) * value.Count];
if (value != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
for (var i = 0; i < value.Count; i++)
ptr[i] = value[i];
}
}
SetValue(2, ids);
}
}
public unsafe List<Decorator> Decorators
{
get
{
if (_decorators == null)
{
_decorators = new List<Decorator>();
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
if (ids != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
int count = ids.Length / sizeof(uint);
for (int i = 0; i < count; i++)
{
var decorator = Surface.FindNode(ptr[i]) as Decorator;
if (decorator != null)
_decorators.Add(decorator);
}
}
}
}
return _decorators;
}
set
{
_decorators = null;
var ids = new byte[sizeof(uint) * value.Count];
if (value != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
for (var i = 0; i < value.Count; i++)
ptr[i] = value[i].ID;
}
}
SetValue(2, ids);
}
}
public override unsafe SurfaceNode[] SealedNodes
{
get
{
SurfaceNode[] result = null;
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
if (ids != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
int count = ids.Length / sizeof(uint);
result = new SurfaceNode[count];
for (int i = 0; i < count; i++)
{
var decorator = Surface.FindNode(ptr[i]) as Decorator;
if (decorator != null)
result[i] = decorator;
}
}
}
return result;
}
}
public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
{
base.OnShowSecondaryContextMenu(menu, location);
if (!Surface.CanEdit)
return;
menu.AddSeparator();
var nodeTypes = Editor.Instance.CodeEditing.BehaviorTreeNodes.Get();
if (_input.Enabled) // Root node cannot have decorators
{
var decorators = menu.AddChildMenu("Add Decorator");
var decoratorType = new ScriptType(typeof(BehaviorTreeDecorator));
foreach (var nodeType in nodeTypes)
{
if (nodeType != decoratorType && decoratorType.IsAssignableFrom(nodeType))
{
var button = decorators.ContextMenu.AddButton(GetTitle(nodeType));
button.Tag = nodeType;
button.TooltipText = Editor.Instance.CodeDocs.GetTooltip(nodeType);
button.ButtonClicked += OnAddDecoratorButtonClicked;
}
}
}
}
private void OnAddDecoratorButtonClicked(ContextMenuButton button)
{
var nodeType = (ScriptType)button.Tag;
// Spawn decorator
var decorator = Context.SpawnNode(19, 3, Location, new object[]
{
nodeType.TypeName,
Utils.GetEmptyArray<byte>(),
});
// Add decorator to the node
var decorators = Decorators;
decorators.Add((Decorator)decorator);
Decorators = decorators;
}
public override void OnValuesChanged()
{
// Reject cached value
_decorators = null;
base.OnValuesChanged();
ResizeAuto();
}
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded(action);
// Setup boxes
_input = (InputBox)GetBox(0);
_output = (OutputBox)GetBox(1);
// Setup node type and data
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
var flags = Archetype.Flags & ~flagsRoot;
if (_type != null)
{
bool isRoot = _type.Type == typeof(BehaviorTreeRootNode);
_input.Enabled = _input.Visible = !isRoot;
_output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type);
if (isRoot)
flags |= flagsRoot;
}
if (Archetype.Flags != flags)
{
// Apply custom flags
@@ -119,18 +350,60 @@ namespace FlaxEditor.Surface.Archetypes
Archetype.Flags = flags;
}
UpdateTitle();
}
/// <inheritdoc />
public override void OnSpawned()
{
base.OnSpawned();
ResizeAuto();
}
/// <inheritdoc />
public override unsafe void OnPasted(Dictionary<uint, uint> idsMapping)
{
base.OnPasted(idsMapping);
// Update decorators
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
if (ids != null)
{
_decorators = null;
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
int count = ids.Length / sizeof(uint);
for (int i = 0; i < count; i++)
{
if (idsMapping.TryGetValue(ptr[i], out var id))
{
// Fix previous parent node to re-apply layout (in case it was forced by spawned decorator)
var decorator = Surface.FindNode(ptr[i]) as Decorator;
var decoratorNode = decorator?.Node;
if (decoratorNode != null)
decoratorNode.ResizeAuto();
// Update mapping to the new node
ptr[i] = id;
}
}
}
Values[2] = ids;
ResizeAuto();
}
}
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
ResizeAuto();
Surface.NodeDeleted += OnNodeDeleted;
}
private void OnNodeDeleted(SurfaceNode node)
{
if (node is Decorator decorator && Decorators.Contains(decorator))
{
// Decorator was spawned (eg. via undo)
_decorators = null;
ResizeAuto();
}
}
public override void ResizeAuto()
{
if (Surface == null)
@@ -144,74 +417,134 @@ namespace FlaxEditor.Surface.Archetypes
height += ConnectionAreaHeight;
if (_output != null && _output.Visible)
height += ConnectionAreaHeight;
var decorators = Decorators;
foreach (var decorator in decorators)
{
decorator.ResizeAuto();
height += decorator.Height + DecoratorsMarginY;
width = Mathf.Max(width, decorator.Width + 2 * DecoratorsMarginX);
}
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize);
if (_input != null && _input.Visible)
_input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
if (_output != null && _output.Visible)
_output.Bounds = new Rectangle(ConnectionAreaMargin, Height - ConnectionAreaHeight, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
UpdateRectangles();
}
/// <inheritdoc />
protected override void UpdateRectangles()
{
base.UpdateRectangles();
// Update boxes placement
Rectangle bounds = Bounds;
if (_input != null && _input.Visible)
{
_input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
bounds.Location.Y += _input.Height;
}
var decorators = Decorators;
var indexInParent = IndexInParent;
foreach (var decorator in decorators)
{
decorator.Bounds = new Rectangle(bounds.Location.X + DecoratorsMarginX, bounds.Location.Y, bounds.Width - 2 * DecoratorsMarginX, decorator.Height);
bounds.Location.Y += decorator.Height + DecoratorsMarginY;
if (decorator.IndexInParent < indexInParent)
decorator.IndexInParent = indexInParent + 1; // Push elements above the node
}
const float footerSize = FlaxEditor.Surface.Constants.NodeFooterSize;
const float headerSize = FlaxEditor.Surface.Constants.NodeHeaderSize;
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
if (_input != null && _input.Visible)
_headerRect.Y += ConnectionAreaHeight;
_footerRect = new Rectangle(0, _headerRect.Bottom, Width, footerSize);
_closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
_headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize);
_closeButtonRect = new Rectangle(bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
_footerRect = new Rectangle(0, _headerRect.Bottom, bounds.Width, footerSize);
if (_output != null && _output.Visible)
_output.Bounds = new Rectangle(ConnectionAreaMargin, bounds.Height - ConnectionAreaHeight, bounds.Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
}
/// <inheritdoc />
public override void OnValuesChanged()
protected override void OnLocationChanged()
{
base.OnValuesChanged();
base.OnLocationChanged();
if (_isValueEditing)
{
// Skip updating instance when it's being edited by user via UI
UpdateTitle();
return;
}
// Sync attached elements placement
UpdateRectangles();
}
}
try
/// <summary>
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree decorator.
/// </summary>
internal class Decorator : NodeBase
{
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
{
return new Decorator(id, context, nodeArch, groupArch);
}
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
public Node Node
{
get
{
if (Instance != null)
foreach (var node in Surface.Nodes)
{
// Reload node instance from data
var instanceData = (byte[])Values[1];
if (instanceData == null || instanceData.Length == 0)
{
// Recreate instance data to default state if previous state was empty
var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType
instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance);
}
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
UpdateTitle();
if (node is Node n && n.DecoratorIds.Contains(ID))
return n;
}
return null;
}
}
protected override Color FooterColor => Color.Transparent;
protected override Float2 CalculateNodeSize(float width, float height)
{
return new Float2(width, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
}
protected override void UpdateRectangles()
{
base.UpdateRectangles();
_footerRect = Rectangle.Empty;
}
protected override void UpdateTitle()
{
var title = Title;
base.UpdateTitle();
// Update parent node on title change
if (title != Title)
Node?.ResizeAuto();
}
public override void Draw()
{
base.Draw();
// Outline
if (!_isSelected)
{
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
Render2D.DrawRectangle(rect, style.BorderHighlighted);
}
}
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
if (action == SurfaceNodeActions.Undo)
{
// Update parent node layout when restoring decorator from undo
var node = Node;
if (node != null)
{
node._decorators = null;
node.ResizeAuto();
}
}
catch (Exception ex)
{
Editor.LogError("Failed to load Behavior Tree node of type " + _type);
Editor.LogWarning(ex);
}
}
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
_type = ScriptType.Null;
Object.Destroy(ref Instance);
base.OnDestroy();
}
}
@@ -222,13 +555,14 @@ namespace FlaxEditor.Surface.Archetypes
{
new NodeArchetype
{
TypeID = 1,
TypeID = 1, // Task Node
Create = Node.Create,
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
DefaultValues = new object[]
{
string.Empty, // Type Name
Utils.GetEmptyArray<byte>(), // Instance Data
null, // List of Decorator Nodes IDs
},
Size = new Float2(100, 0),
Elements = new[]
@@ -239,7 +573,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
TypeID = 2,
TypeID = 2, // Root Node
Create = Node.Create,
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
DefaultValues = new object[]
@@ -254,6 +588,18 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
}
},
new NodeArchetype
{
TypeID = 3, // Decorator Node
Create = Decorator.Create,
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove,
DefaultValues = new object[]
{
string.Empty, // Type Name
Utils.GetEmptyArray<byte>(), // Instance Data
},
Size = new Float2(100, 0),
},
};
}
}

View File

@@ -71,6 +71,10 @@ namespace FlaxEditor.Surface
scriptType == typeof(BehaviorTreeRootNode))
return;
// Nodes-only
if (new ScriptType(typeof(BehaviorTreeDecorator)).IsAssignableFrom(scriptType))
return;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>("Behavior Tree", 19);
if (!cache.TryGetValue(groupKey, out var group))
@@ -171,6 +175,7 @@ namespace FlaxEditor.Surface
{
typeof(BehaviorTreeSubTreeNode).FullName,
FlaxEngine.Json.JsonSerializer.SaveToBytes(instance),
null,
});
FlaxEngine.Object.Destroy(instance);
}

View File

@@ -135,7 +135,7 @@ namespace FlaxEditor.Windows.Assets
{
for (var i = 0; i < nodes.Count; i++)
{
if (nodes[i] is Surface.Archetypes.BehaviorTree.Node node && node.IsSelected && node.Instance)
if (nodes[i] is Surface.Archetypes.BehaviorTree.NodeBase node && node.IsSelected && node.Instance)
selection.Add(node.Instance);
}
}
@@ -153,7 +153,7 @@ namespace FlaxEditor.Windows.Assets
// Sync instance data with surface node value storage
for (var j = 0; j < nodes.Count; j++)
{
if (nodes[j] is Surface.Archetypes.BehaviorTree.Node node && node.Instance == instance)
if (nodes[j] is Surface.Archetypes.BehaviorTree.NodeBase node && node.Instance == instance)
{
node._isValueEditing = true;
node.SetValue(1, FlaxEngine.Json.JsonSerializer.SaveToBytes(instance));

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);
}
}

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)
{
}
};

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

View File

@@ -28,7 +28,7 @@ public:
protected:
// [BehaviorTreeNode]
void InvokeReleaseState(const BehaviorUpdateContext& context) override;
void BecomeIrrelevant(const BehaviorUpdateContext& context, bool nodeOnly) override;
};
/// <summary>