Add initial Behavior simulation
This commit is contained in:
127
Source/Engine/AI/Behavior.cpp
Normal file
127
Source/Engine/AI/Behavior.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Behavior.h"
|
||||
#include "BehaviorKnowledge.h"
|
||||
#include "BehaviorTreeNodes.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
|
||||
BehaviorKnowledge::~BehaviorKnowledge()
|
||||
{
|
||||
FreeMemory();
|
||||
}
|
||||
|
||||
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
|
||||
{
|
||||
ASSERT_LOW_LAYER(!Tree && tree);
|
||||
Tree = tree;
|
||||
Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType);
|
||||
if (!Memory && tree->Graph.NodesStatesSize)
|
||||
{
|
||||
Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
|
||||
for (const auto& node : tree->Graph.Nodes)
|
||||
{
|
||||
if (node.Instance)
|
||||
node.Instance->InitState(Behavior, Memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorKnowledge::FreeMemory()
|
||||
{
|
||||
if (Memory)
|
||||
{
|
||||
ASSERT_LOW_LAYER(Tree);
|
||||
for (const auto& node : Tree->Graph.Nodes)
|
||||
{
|
||||
if (node.Instance)
|
||||
node.Instance->ReleaseState(Behavior, Memory);
|
||||
}
|
||||
Allocator::Free(Memory);
|
||||
Memory = nullptr;
|
||||
}
|
||||
Blackboard.DeleteValue();
|
||||
Tree = nullptr;
|
||||
}
|
||||
|
||||
Behavior::Behavior(const SpawnParams& params)
|
||||
: Script(params)
|
||||
{
|
||||
_tickLateUpdate = 1; // TODO: run Behavior via Job System (use Engine::UpdateGraph)
|
||||
_knowledge.Behavior = this;
|
||||
Tree.Changed.Bind<Behavior, &Behavior::ResetLogic>(this);
|
||||
}
|
||||
|
||||
void Behavior::StartLogic()
|
||||
{
|
||||
// Ensure to have tree loaded on begin play
|
||||
CHECK(Tree && !Tree->WaitForLoaded());
|
||||
BehaviorTree* tree = Tree.Get();
|
||||
CHECK(tree->Graph.Root);
|
||||
|
||||
_result = BehaviorUpdateResult::Running;
|
||||
|
||||
// Init knowledge
|
||||
_knowledge.InitMemory(tree);
|
||||
}
|
||||
|
||||
void Behavior::StopLogic()
|
||||
{
|
||||
if (_result != BehaviorUpdateResult::Running)
|
||||
return;
|
||||
_accumulatedTime = 0.0f;
|
||||
_result = BehaviorUpdateResult::Success;
|
||||
}
|
||||
|
||||
void Behavior::ResetLogic()
|
||||
{
|
||||
const bool isActive = _result == BehaviorUpdateResult::Running;
|
||||
if (isActive)
|
||||
StopLogic();
|
||||
|
||||
// Reset state
|
||||
_knowledge.FreeMemory();
|
||||
_accumulatedTime = 0.0f;
|
||||
_result = BehaviorUpdateResult::Success;
|
||||
|
||||
if (isActive)
|
||||
StartLogic();
|
||||
}
|
||||
|
||||
void Behavior::OnEnable()
|
||||
{
|
||||
if (AutoStart)
|
||||
StartLogic();
|
||||
}
|
||||
|
||||
void Behavior::OnLateUpdate()
|
||||
{
|
||||
if (_result != BehaviorUpdateResult::Running)
|
||||
return;
|
||||
const BehaviorTree* tree = Tree.Get();
|
||||
if (!tree || !tree->Graph.Root)
|
||||
{
|
||||
_result = BehaviorUpdateResult::Failed;
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update timer
|
||||
_accumulatedTime += Time::Update.DeltaTime.GetTotalSeconds();
|
||||
const float updateDeltaTime = 1.0f / Math::Max(tree->Graph.Root->UpdateFPS * UpdateRateScale, ZeroTolerance);
|
||||
if (_accumulatedTime < updateDeltaTime)
|
||||
return;
|
||||
_accumulatedTime -= updateDeltaTime;
|
||||
|
||||
// Update tree
|
||||
BehaviorUpdateContext context;
|
||||
context.Behavior = this;
|
||||
context.Knowledge = &_knowledge;
|
||||
context.Memory = _knowledge.Memory;
|
||||
context.DeltaTime = updateDeltaTime;
|
||||
const BehaviorUpdateResult result = tree->Graph.Root->Update(context);
|
||||
if (result != BehaviorUpdateResult::Running)
|
||||
{
|
||||
_result = result;
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
84
Source/Engine/AI/Behavior.h
Normal file
84
Source/Engine/AI/Behavior.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BehaviorTree.h"
|
||||
#include "BehaviorKnowledge.h"
|
||||
#include "BehaviorTypes.h"
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
|
||||
/// <summary>
|
||||
/// Behavior instance script that runs Behavior Tree execution.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API Behavior : public Script
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCRIPTING_TYPE(Behavior);
|
||||
|
||||
private:
|
||||
BehaviorKnowledge _knowledge;
|
||||
float _accumulatedTime = 0.0f;
|
||||
BehaviorUpdateResult _result = BehaviorUpdateResult::Success;
|
||||
void* _memory = nullptr;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Behavior Tree asset to use for logic execution.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(0)")
|
||||
AssetReference<BehaviorTree> Tree;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, auto starts the logic on begin play.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10)")
|
||||
bool AutoStart = true;
|
||||
|
||||
/// <summary>
|
||||
/// The behavior logic update rate scale (multiplies the UpdateFPS defined in Behavior Tree root node). Can be used to improve performance via LOD to reduce updates frequency (eg. by 0.5) for behaviors far from player.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0, 10, 0.01f)")
|
||||
float UpdateRateScale = 1.0f;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the current behavior knowledge instance. Empty if not started.
|
||||
/// </summary>
|
||||
API_PROPERTY() BehaviorKnowledge* GetKnowledge()
|
||||
{
|
||||
return &_knowledge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last behavior tree execution result.
|
||||
/// </summary>
|
||||
API_PROPERTY() BehaviorUpdateResult GetResult() const
|
||||
{
|
||||
return _result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event called when behavior tree execution ends with a result.
|
||||
/// </summary>
|
||||
API_EVENT() Action Finished;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the logic.
|
||||
/// </summary>
|
||||
API_FUNCTION() void StartLogic();
|
||||
|
||||
/// <summary>
|
||||
/// Stops the logic.
|
||||
/// </summary>
|
||||
API_FUNCTION() void StopLogic();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the behavior logic by clearing knowledge (clears blackboard and removes goals) and resetting execution state (goes back to root).
|
||||
/// </summary>
|
||||
API_FUNCTION() void ResetLogic();
|
||||
|
||||
// [Script]
|
||||
void OnEnable() override;
|
||||
void OnLateUpdate() override;
|
||||
};
|
||||
@@ -4,15 +4,48 @@
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
|
||||
class Behavior;
|
||||
class BehaviorTree;
|
||||
|
||||
/// <summary>
|
||||
/// Behavior logic component knowledge data container. Contains blackboard values, sensors data and goals storage for Behavior Tree execution.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorKnowledge, ScriptingObject);
|
||||
~BehaviorKnowledge();
|
||||
|
||||
/// <summary>
|
||||
/// Owning Behavior instance (constant).
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) Behavior* Behavior = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// Used Behavior Tree asset (defines blackboard and memory constraints).
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) BehaviorTree* Tree = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// Raw memory chunk with all Behavior Tree nodes state.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) void* Memory = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of the behaviour blackboard (structure or class).
|
||||
/// </summary>
|
||||
API_FIELD() Variant Blackboard;
|
||||
|
||||
// TODO: blackboard
|
||||
// TODO: sensors data
|
||||
// TODO: goals
|
||||
// TODO: GetGoal/HasGoal
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the knowledge for a certain tree.
|
||||
/// </summary>
|
||||
void InitMemory(BehaviorTree* tree);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the memory of the knowledge.
|
||||
/// </summary>
|
||||
void FreeMemory();
|
||||
};
|
||||
|
||||
@@ -36,6 +36,12 @@ bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.Instance)
|
||||
{
|
||||
// Count total states memory size
|
||||
node.Instance->_memoryOffset = NodesStatesSize;
|
||||
NodesStatesSize += node.Instance->GetStateSize();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -46,6 +52,7 @@ void BehaviorTreeGraph::Clear()
|
||||
VisjectGraph<BehaviorTreeGraphNode>::Clear();
|
||||
|
||||
Root = nullptr;
|
||||
NodesStatesSize = 0;
|
||||
}
|
||||
|
||||
bool BehaviorTreeGraph::onNodeLoaded(Node* n)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using FlaxEngine.Utilities;
|
||||
@@ -39,4 +41,55 @@ namespace FlaxEngine
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
unsafe partial class BehaviorTreeNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the size in bytes of the given typed node state structure.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetStateSize<T>()
|
||||
{
|
||||
// C# nodes state is stored via pinned GCHandle to support holding managed references (eg. string or Vector3[])
|
||||
return sizeof(IntPtr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node state at the given memory address.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AllocState(IntPtr memory, object state)
|
||||
{
|
||||
var handle = GCHandle.Alloc(state);
|
||||
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
|
||||
Unsafe.Write<IntPtr>(ptr, (IntPtr)handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the typed node state at the given memory address.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref T GetState<T>(IntPtr memory) where T : struct
|
||||
{
|
||||
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
|
||||
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
|
||||
var state = handle.Target;
|
||||
#if !BUILD_RELEASE
|
||||
if (state == null)
|
||||
throw new NullReferenceException();
|
||||
#endif
|
||||
return ref Unsafe.Unbox<T>(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated node state at the given memory address.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void FreeState(IntPtr memory)
|
||||
{
|
||||
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
|
||||
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Engine/Content/BinaryAsset.h"
|
||||
#include "Engine/Visject/VisjectGraph.h"
|
||||
|
||||
class BehaviorKnowledge;
|
||||
class BehaviorTreeNode;
|
||||
class BehaviorTreeRootNode;
|
||||
|
||||
@@ -28,6 +29,8 @@ class BehaviorTreeGraph : public VisjectGraph<BehaviorTreeGraphNode>
|
||||
public:
|
||||
// Instance of the graph root node.
|
||||
BehaviorTreeRootNode* Root = nullptr;
|
||||
// Total size of the nodes states memory.
|
||||
int32 NodesStatesSize = 0;
|
||||
|
||||
// [VisjectGraph]
|
||||
bool Load(ReadStream* stream, bool loadMeta) override;
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject);
|
||||
friend class BehaviorTreeGraph;
|
||||
|
||||
protected:
|
||||
// Raw memory byte offset from the start of the behavior memory block.
|
||||
API_FIELD(ReadOnly) int32 _memoryOffset = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -18,11 +23,6 @@ public:
|
||||
/// </summary>
|
||||
API_FIELD() String Name;
|
||||
|
||||
// TODO: decorators/conditionals
|
||||
// TODO: instance data ctor/dtor
|
||||
// TODO: start/stop methods
|
||||
// TODO: update method
|
||||
|
||||
/// <summary>
|
||||
/// Initializes node state. Called after whole tree is loaded and nodes hierarchy is setup.
|
||||
/// </summary>
|
||||
@@ -31,6 +31,52 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node instance state size. A chunk of the valid memory is passed via InitState to setup that memory chunk (one per-behavior).
|
||||
/// </summary>
|
||||
API_FUNCTION() virtual int32 GetStateSize() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes node instance state. Called when starting logic simulation for a given behavior.
|
||||
/// </summary>
|
||||
/// <param name="behavior">Behavior to simulate.</param>
|
||||
/// <param name="memory">Pointer to pre-allocated memory for this node to use (call constructor of the state container).</param>
|
||||
API_FUNCTION() virtual void InitState(Behavior* behavior, void* memory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups node instance state. Called when stopping logic simulation for a given behavior.
|
||||
/// </summary>
|
||||
/// <param name="behavior">Behavior to simulate.</param>
|
||||
/// <param name="memory">Pointer to pre-allocated memory for this node to use (call destructor of the state container).</param>
|
||||
API_FUNCTION() virtual void ReleaseState(Behavior* behavior, void* memory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates node logic.
|
||||
/// </summary>
|
||||
/// <param name="context">Behavior update context data.</param>
|
||||
/// <returns>Operation result enum.</returns>
|
||||
API_FUNCTION() virtual BehaviorUpdateResult Update(BehaviorUpdateContext context)
|
||||
{
|
||||
return BehaviorUpdateResult::Success;
|
||||
}
|
||||
|
||||
// [SerializableScriptingObject]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
|
||||
public:
|
||||
// Returns the typed node state at the given memory address.
|
||||
template<typename T>
|
||||
T* GetState(void* memory) const
|
||||
{
|
||||
ASSERT((int32)sizeof(T) <= GetStateSize());
|
||||
return reinterpret_cast<T*>((byte*)memory + _memoryOffset);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,3 +24,51 @@ void BehaviorTreeCompoundNode::Init(BehaviorTree* tree)
|
||||
for (BehaviorTreeNode* child : Children)
|
||||
child->Init(tree);
|
||||
}
|
||||
|
||||
BehaviorUpdateResult BehaviorTreeCompoundNode::Update(BehaviorUpdateContext context)
|
||||
{
|
||||
auto result = BehaviorUpdateResult::Success;
|
||||
for (int32 i = 0; i < Children.Count() && result == BehaviorUpdateResult::Success; i++)
|
||||
{
|
||||
BehaviorTreeNode* child = Children[i];
|
||||
result = child->Update(context);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 BehaviorTreeSequenceNode::GetStateSize() const
|
||||
{
|
||||
return sizeof(State);
|
||||
}
|
||||
|
||||
void BehaviorTreeSequenceNode::InitState(Behavior* behavior, void* memory)
|
||||
{
|
||||
auto state = GetState<State>(memory);
|
||||
new(state)State();
|
||||
}
|
||||
|
||||
BehaviorUpdateResult BehaviorTreeSequenceNode::Update(BehaviorUpdateContext context)
|
||||
{
|
||||
auto state = GetState<State>(context.Memory);
|
||||
|
||||
if (state->CurrentChildIndex >= Children.Count())
|
||||
return BehaviorUpdateResult::Success;
|
||||
if (state->CurrentChildIndex == -1)
|
||||
return BehaviorUpdateResult::Failed;
|
||||
|
||||
auto result = Children[state->CurrentChildIndex]->Update(context);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case BehaviorUpdateResult::Success:
|
||||
state->CurrentChildIndex++; // Move to the next node
|
||||
if (state->CurrentChildIndex < Children.Count())
|
||||
result = BehaviorUpdateResult::Running; // Keep on running to the next child on the next update
|
||||
break;
|
||||
case BehaviorUpdateResult::Failed:
|
||||
state->CurrentChildIndex = -1; // Mark whole sequence as failed
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -20,25 +20,42 @@ API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeCompoundNode : public Behav
|
||||
public:
|
||||
// [BehaviorTreeNode]
|
||||
void Init(BehaviorTree* tree) override;
|
||||
BehaviorUpdateResult Update(BehaviorUpdateContext context) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sequence node updates all its children as long as they return success. If any child fails, the sequence is failed.
|
||||
/// Sequence node updates all its children (from left to right) as long as they return success. If any child fails, the sequence is failed.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API BehaviorTreeSequenceNode : public BehaviorTreeCompoundNode
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeSequenceNode, BehaviorTreeCompoundNode);
|
||||
|
||||
public:
|
||||
// [BehaviorTreeNode]
|
||||
int32 GetStateSize() const override;
|
||||
void InitState(Behavior* behavior, void* memory) override;
|
||||
BehaviorUpdateResult Update(BehaviorUpdateContext context) override;
|
||||
|
||||
private:
|
||||
struct State
|
||||
{
|
||||
int32 CurrentChildIndex = 0;
|
||||
};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Root node of the behavior tree. Contains logic properties and definitions for the runtime.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTreeCompoundNode
|
||||
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTreeSequenceNode
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeRootNode, BehaviorTreeCompoundNode);
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeRootNode, BehaviorTreeSequenceNode);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
// Full typename of the blackboard data type (structure or class). Spawned for each instance of the behavior.
|
||||
API_FIELD(Attributes="EditorOrder(0), TypeReference(\"\", \"IsValidBlackboardType\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.TypeNameEditor\")")
|
||||
StringAnsi BlackboardType;
|
||||
|
||||
// The target amount of the behavior logic updates per second.
|
||||
API_FIELD(Attributes="EditorOrder(10)")
|
||||
float UpdateFPS = 10.0f;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
class Behavior;
|
||||
class BehaviorTree;
|
||||
class BehaviorTreeNode;
|
||||
class BehaviorKnowledge;
|
||||
@@ -15,10 +16,25 @@ API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext);
|
||||
|
||||
/// <summary>
|
||||
/// Behavior to simulate.
|
||||
/// </summary>
|
||||
API_FIELD() Behavior* Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// Behavior's logic knowledge container (data, goals and sensors).
|
||||
/// </summary>
|
||||
API_FIELD() BehaviorKnowledge* Knowledge;
|
||||
|
||||
/// <summary>
|
||||
/// Current instance memory buffer location (updated while moving down the tree).
|
||||
/// </summary>
|
||||
API_FIELD() void* Memory;
|
||||
|
||||
/// <summary>
|
||||
/// Simulation time delta (in seconds) since the last update.
|
||||
/// </summary>
|
||||
float DeltaTime;
|
||||
API_FIELD() float DeltaTime;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user