Add GetDebugInfo to BT nodes for debugging
This commit is contained in:
@@ -29,6 +29,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
protected const float DecoratorsMarginX = 5.0f;
|
||||
protected const float DecoratorsMarginY = 2.0f;
|
||||
|
||||
protected string _debugInfo;
|
||||
protected Float2 _debugInfoSize;
|
||||
protected ScriptType _type;
|
||||
internal bool _isValueEditing;
|
||||
|
||||
@@ -52,6 +54,21 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return title;
|
||||
}
|
||||
|
||||
public virtual void UpdateDebug(Behavior behavior)
|
||||
{
|
||||
BehaviorTreeNode instance = null;
|
||||
if (behavior)
|
||||
{
|
||||
// Try to use instance from the currently debugged behavior
|
||||
// TODO: support nodes from nested trees
|
||||
instance = behavior.Tree.GetNodeInstance(ID);
|
||||
}
|
||||
var size = _debugInfoSize;
|
||||
UpdateDebugInfo(instance, behavior);
|
||||
if (size != _debugInfoSize)
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle()
|
||||
{
|
||||
string title = null;
|
||||
@@ -69,6 +86,21 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Title = title;
|
||||
}
|
||||
|
||||
protected virtual void UpdateDebugInfo(BehaviorTreeNode instance = null, Behavior behavior = null)
|
||||
{
|
||||
_debugInfo = null;
|
||||
_debugInfoSize = Float2.Zero;
|
||||
if (!instance)
|
||||
instance = Instance;
|
||||
if (instance)
|
||||
{
|
||||
// Get debug description for the node based on the current settings
|
||||
_debugInfo = Behavior.GetNodeDebugInfo(instance, behavior);
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
_debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
@@ -97,6 +129,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
@@ -129,6 +162,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
@@ -139,10 +173,23 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Debug Info
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
{
|
||||
var style = Style.Current;
|
||||
Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_debugInfo = null;
|
||||
_type = ScriptType.Null;
|
||||
FlaxEngine.Object.Destroy(ref Instance);
|
||||
|
||||
@@ -258,6 +305,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
get
|
||||
{
|
||||
// Return decorator nodes attached to this node to be moved/copied/pasted as a one
|
||||
SurfaceNode[] result = null;
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
@@ -425,6 +473,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var titleLabelFont = Style.Current.FontLarge;
|
||||
width = Mathf.Max(width, 100.0f);
|
||||
width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30);
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
if (_input != null && _input.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
if (_output != null && _output.Visible)
|
||||
@@ -463,10 +516,13 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_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);
|
||||
_footerRect = new Rectangle(0, bounds.Height - footerSize, bounds.Width, footerSize);
|
||||
if (_output != null && _output.Visible)
|
||||
{
|
||||
_footerRect.Y -= ConnectionAreaHeight;
|
||||
_output.Bounds = new Rectangle(ConnectionAreaMargin, bounds.Height - ConnectionAreaHeight, bounds.Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLocationChanged()
|
||||
{
|
||||
@@ -589,7 +645,12 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
protected override Float2 CalculateNodeSize(float width, float height)
|
||||
{
|
||||
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize + 2 * DecoratorsMarginX, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize * 2 + DecoratorsMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
@@ -603,15 +664,22 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
protected override void UpdateTitle()
|
||||
{
|
||||
var title = Title;
|
||||
|
||||
base.UpdateTitle();
|
||||
|
||||
// Update parent node on title change
|
||||
var title = Title;
|
||||
base.UpdateTitle();
|
||||
if (title != Title)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
protected override void UpdateDebugInfo(BehaviorTreeNode instance, Behavior behavior)
|
||||
{
|
||||
// Update parent node on debug text change
|
||||
var debugInfoSize = _debugInfoSize;
|
||||
base.UpdateDebugInfo(instance, behavior);
|
||||
if (debugInfoSize != _debugInfoSize)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
// Add drag button to reorder decorator
|
||||
|
||||
@@ -449,6 +449,18 @@ namespace FlaxEditor.Windows.Assets
|
||||
UpdateKnowledge();
|
||||
}
|
||||
|
||||
private void UpdateDebugInfos(bool playMode)
|
||||
{
|
||||
var behavior = playMode ? (Behavior)_behaviorPicker.Value : null;
|
||||
if (!behavior)
|
||||
behavior = null;
|
||||
foreach (var e in _surface.Nodes)
|
||||
{
|
||||
if (e is Surface.Archetypes.BehaviorTree.NodeBase node)
|
||||
node.UpdateDebug(behavior);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnPlayBegin()
|
||||
{
|
||||
@@ -461,6 +473,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
public override void OnPlayEnd()
|
||||
{
|
||||
SetCanEdit(true);
|
||||
UpdateDebugInfos(false);
|
||||
|
||||
base.OnPlayEnd();
|
||||
}
|
||||
@@ -520,6 +533,12 @@ namespace FlaxEditor.Windows.Assets
|
||||
// Update behavior debugging
|
||||
SetCanDebug(Editor.IsPlayMode && _behaviorPicker.Value);
|
||||
|
||||
// Update debug info texts on all nodes
|
||||
if (Editor.IsPlayMode)
|
||||
{
|
||||
UpdateDebugInfos(true);
|
||||
}
|
||||
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -166,3 +166,25 @@ void Behavior::OnDisable()
|
||||
{
|
||||
BehaviorServiceInstance.UpdateList.Remove(this);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
String Behavior::GetNodeDebugInfo(BehaviorTreeNode* node, Behavior* behavior)
|
||||
{
|
||||
if (!node)
|
||||
return String::Empty;
|
||||
BehaviorUpdateContext context;
|
||||
Platform::MemoryClear(&context, sizeof(context));
|
||||
if (behavior && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex))
|
||||
{
|
||||
// Pass behavior and knowledge data only for relevant nodes to properly access it
|
||||
context.Behavior = behavior;
|
||||
context.Knowledge = &behavior->_knowledge;
|
||||
context.Memory = behavior->_knowledge.Memory;
|
||||
context.RelevantNodes = &behavior->_knowledge.RelevantNodes;
|
||||
context.Time = behavior->_totalTime;
|
||||
}
|
||||
return node->GetDebugInfo(context);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,6 @@ private:
|
||||
float _accumulatedTime = 0.0f;
|
||||
float _totalTime = 0.0f;
|
||||
BehaviorUpdateResult _result = BehaviorUpdateResult::Success;
|
||||
void* _memory = nullptr;
|
||||
|
||||
void UpdateAsync();
|
||||
|
||||
@@ -91,4 +90,10 @@ public:
|
||||
// [Script]
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
|
||||
private:
|
||||
#if USE_EDITOR
|
||||
// Editor-only utility to debug nodes state.
|
||||
API_FUNCTION(Internal) static String GetNodeDebugInfo(BehaviorTreeNode* node, Behavior* behavior);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -119,12 +119,12 @@ bool AccessBehaviorKnowledge(BehaviorKnowledge* knowledge, const StringAnsiView&
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BehaviorKnowledgeSelectorAny::Set(BehaviorKnowledge* knowledge, const Variant& value)
|
||||
bool BehaviorKnowledgeSelectorAny::Set(BehaviorKnowledge* knowledge, const Variant& value) const
|
||||
{
|
||||
return knowledge && knowledge->Set(Path, value);
|
||||
}
|
||||
|
||||
Variant BehaviorKnowledgeSelectorAny::Get(BehaviorKnowledge* knowledge)
|
||||
Variant BehaviorKnowledgeSelectorAny::Get(const BehaviorKnowledge* knowledge) const
|
||||
{
|
||||
Variant value;
|
||||
if (knowledge)
|
||||
@@ -132,7 +132,7 @@ Variant BehaviorKnowledgeSelectorAny::Get(BehaviorKnowledge* knowledge)
|
||||
return value;
|
||||
}
|
||||
|
||||
bool BehaviorKnowledgeSelectorAny::TryGet(BehaviorKnowledge* knowledge, Variant& value)
|
||||
bool BehaviorKnowledgeSelectorAny::TryGet(const BehaviorKnowledge* knowledge, Variant& value) const
|
||||
{
|
||||
return knowledge && knowledge->Get(Path, value);
|
||||
}
|
||||
@@ -182,9 +182,9 @@ void BehaviorKnowledge::FreeMemory()
|
||||
Tree = nullptr;
|
||||
}
|
||||
|
||||
bool BehaviorKnowledge::Get(const StringAnsiView& path, Variant& value)
|
||||
bool BehaviorKnowledge::Get(const StringAnsiView& path, Variant& value) const
|
||||
{
|
||||
return AccessBehaviorKnowledge(this, path, value, false);
|
||||
return AccessBehaviorKnowledge(const_cast<BehaviorKnowledge*>(this), path, value, false);
|
||||
}
|
||||
|
||||
bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
/// <param name="path">Selector path.</param>
|
||||
/// <param name="value">Result value (valid only when returned true).</param>
|
||||
/// <returns>True if got value, otherwise false.</returns>
|
||||
API_FUNCTION() bool Get(const StringAnsiView& path, API_PARAM(Out) Variant& value);
|
||||
API_FUNCTION() bool Get(const StringAnsiView& path, API_PARAM(Out) Variant& value) const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the knowledge item value via selector path.
|
||||
|
||||
@@ -21,13 +21,13 @@ API_STRUCT(NoDefault, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowle
|
||||
API_FIELD() StringAnsi Path;
|
||||
|
||||
// Sets the selected knowledge value (as Variant).
|
||||
bool Set(BehaviorKnowledge* knowledge, const Variant& value);
|
||||
bool Set(BehaviorKnowledge* knowledge, const Variant& value) const;
|
||||
|
||||
// Gets the selected knowledge value (as Variant).
|
||||
Variant Get(BehaviorKnowledge* knowledge);
|
||||
Variant Get(const BehaviorKnowledge* knowledge) const;
|
||||
|
||||
// Tries to get the selected knowledge value (as Variant). Returns true if got value, otherwise false.
|
||||
bool TryGet(BehaviorKnowledge* knowledge, Variant& value);
|
||||
bool TryGet(const BehaviorKnowledge* knowledge, Variant& value) const;
|
||||
|
||||
FORCE_INLINE bool operator==(const BehaviorKnowledgeSelectorAny& other) const
|
||||
{
|
||||
@@ -63,19 +63,19 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi
|
||||
using BehaviorKnowledgeSelectorAny::TryGet;
|
||||
|
||||
// Sets the selected knowledge value (typed).
|
||||
FORCE_INLINE void Set(BehaviorKnowledge* knowledge, const T& value)
|
||||
FORCE_INLINE void Set(BehaviorKnowledge* knowledge, const T& value) const
|
||||
{
|
||||
BehaviorKnowledgeSelectorAny::Set(knowledge, Variant(value));
|
||||
}
|
||||
|
||||
// Gets the selected knowledge value (typed).
|
||||
FORCE_INLINE T Get(BehaviorKnowledge* knowledge)
|
||||
FORCE_INLINE T Get(const BehaviorKnowledge* knowledge) const
|
||||
{
|
||||
return TVariantValueCast<T>::Cast(BehaviorKnowledgeSelectorAny::Get(knowledge));
|
||||
}
|
||||
|
||||
// Tries to get the selected knowledge value (typed). Returns true if got value, otherwise false.
|
||||
FORCE_INLINE bool TryGet(BehaviorKnowledge* knowledge, T& value)
|
||||
FORCE_INLINE bool TryGet(const BehaviorKnowledge* knowledge, T& value)
|
||||
{
|
||||
Variant variant;
|
||||
if (BehaviorKnowledgeSelectorAny::TryGet(knowledge, variant))
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false);
|
||||
|
||||
#define IS_BT_NODE(n) (n.GroupID == 19 && (n.TypeID == 1 || n.TypeID == 2 || n.TypeID == 3))
|
||||
|
||||
bool SortBehaviorTreeChildren(GraphBox* const& a, GraphBox* const& b)
|
||||
{
|
||||
// Sort by node X coordinate on surface
|
||||
@@ -45,7 +47,8 @@ void BehaviorTreeGraph::Clear()
|
||||
|
||||
bool BehaviorTreeGraph::onNodeLoaded(Node* n)
|
||||
{
|
||||
if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2 || n->TypeID == 3))
|
||||
const Node& node = *n;
|
||||
if (IS_BT_NODE(node))
|
||||
{
|
||||
// Create node instance object
|
||||
ScriptingTypeHandle type = Scripting::FindScriptingType((StringAnsiView)n->Values[0]);
|
||||
@@ -151,6 +154,16 @@ BehaviorTree::BehaviorTree(const SpawnParams& params, const AssetInfo* info)
|
||||
{
|
||||
}
|
||||
|
||||
BehaviorTreeNode* BehaviorTree::GetNodeInstance(uint32 id)
|
||||
{
|
||||
for (const auto& node : Graph.Nodes)
|
||||
{
|
||||
if (node.ID == id && node.Instance && IS_BT_NODE(node))
|
||||
return node.Instance;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BytesContainer BehaviorTree::LoadSurface()
|
||||
{
|
||||
if (WaitForLoaded())
|
||||
|
||||
@@ -66,6 +66,13 @@ public:
|
||||
/// </summary>
|
||||
BehaviorTreeGraph Graph;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific node instance object from Behavior Tree.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique node identifier (Visject surface).</param>
|
||||
/// <returns>The node instance or null if cannot get it.</returns>
|
||||
API_FUNCTION() BehaviorTreeNode* GetNodeInstance(uint32 id);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load surface graph from the asset.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject);
|
||||
friend class Behavior;
|
||||
friend class BehaviorTreeGraph;
|
||||
friend class BehaviorKnowledge;
|
||||
friend class BehaviorTreeSubTreeNode;
|
||||
@@ -76,6 +77,18 @@ public:
|
||||
return BehaviorUpdateResult::Success;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Gets the node debug state text (multiline). Used in Editor-only to display nodes state. Can be called without valid Behavior/Knowledge/Memory to display default debug info (eg. node properties).
|
||||
/// </summary>
|
||||
/// <param name="context">Behavior context data.</param>
|
||||
/// <returns>Debug info text (multiline).</returns>
|
||||
API_FUNCTION() virtual String GetDebugInfo(const BehaviorUpdateContext& context) const
|
||||
{
|
||||
return String::Empty;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Core/Random.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#if USE_CSHARP
|
||||
#include "Engine/Core/Utilities.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#endif
|
||||
#include "Engine/Engine/Engine.h"
|
||||
@@ -264,6 +265,28 @@ BehaviorUpdateResult BehaviorTreeDelayNode::Update(const BehaviorUpdateContext&
|
||||
return state->TimeLeft <= 0.0f ? BehaviorUpdateResult::Success : BehaviorUpdateResult::Running;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
String BehaviorTreeDelayNode::GetDebugInfo(const BehaviorUpdateContext& context) const
|
||||
{
|
||||
if (context.Memory)
|
||||
{
|
||||
const auto state = GetState<State>(context.Memory);
|
||||
return String::Format(TEXT("Time Left: {}s"), Utilities::RoundTo2DecimalPlaces(state->TimeLeft));
|
||||
}
|
||||
|
||||
String delay;
|
||||
if (WaitTimeSelector.Path.HasChars())
|
||||
delay = String(WaitTimeSelector.Path);
|
||||
else
|
||||
delay = StringUtils::ToString(WaitTime);
|
||||
if (RandomDeviation > 0.0f)
|
||||
delay += String::Format(TEXT("+/-{}"), RandomDeviation);
|
||||
return String::Format(TEXT("Delay: {}s"), delay);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int32 BehaviorTreeSubTreeNode::GetStateSize() const
|
||||
{
|
||||
return sizeof(State);
|
||||
@@ -481,6 +504,35 @@ BehaviorUpdateResult BehaviorTreeMoveToNode::Update(const BehaviorUpdateContext&
|
||||
return state->Result;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
String BehaviorTreeMoveToNode::GetDebugInfo(const BehaviorUpdateContext& context) const
|
||||
{
|
||||
if (context.Memory)
|
||||
{
|
||||
const auto state = GetState<State>(context.Memory);
|
||||
if (state->Agent)
|
||||
{
|
||||
const String agent = state->Agent->GetNamePath();
|
||||
String goal;
|
||||
const Actor* target = Target.Get(context.Knowledge);
|
||||
if (target)
|
||||
goal = target->GetNamePath();
|
||||
else
|
||||
goal = state->GoalLocation.ToString();
|
||||
const Vector3 agentLocation = state->Agent->GetPosition();
|
||||
const Vector3 agentLocationOnPath = agentLocation + state->AgentOffset;
|
||||
float distanceLeft = state->Path.Count() > state->TargetPathIndex ? Vector3::Distance(state->Path[state->TargetPathIndex], agentLocationOnPath) : 0;
|
||||
for (int32 i = state->TargetPathIndex; i < state->Path.Count(); i++)
|
||||
distanceLeft += Vector3::Distance(state->Path[i - 1], state->Path[i]);
|
||||
return String::Format(TEXT("Agent: '{}'\nGoal: '{}'\nDistance: {}"), agent, goal, (int32)distanceLeft);
|
||||
}
|
||||
}
|
||||
return String::Empty;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void BehaviorTreeMoveToNode::State::OnUpdate()
|
||||
{
|
||||
if (Result != BehaviorUpdateResult::Running)
|
||||
|
||||
@@ -122,6 +122,9 @@ public:
|
||||
int32 GetStateSize() const override;
|
||||
void InitState(const BehaviorUpdateContext& context) override;
|
||||
BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override;
|
||||
#if USE_EDITOR
|
||||
String GetDebugInfo(const BehaviorUpdateContext& context) const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct State
|
||||
@@ -233,6 +236,9 @@ public:
|
||||
void InitState(const BehaviorUpdateContext& context) override;
|
||||
void ReleaseState(const BehaviorUpdateContext& context) override;
|
||||
BehaviorUpdateResult Update(const BehaviorUpdateContext& context) override;
|
||||
#if USE_EDITOR
|
||||
String GetDebugInfo(const BehaviorUpdateContext& context) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
struct State
|
||||
|
||||
Reference in New Issue
Block a user