Add **Behavior Tree** asset type and editing
This commit is contained in:
18
Source/Engine/AI/BehaviorKnowledge.h
Normal file
18
Source/Engine/AI/BehaviorKnowledge.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
|
||||
/// <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);
|
||||
|
||||
// TODO: blackboard
|
||||
// TODO: sensors data
|
||||
// TODO: goals
|
||||
// TODO: GetGoal/HasGoal
|
||||
};
|
||||
161
Source/Engine/AI/BehaviorTree.cpp
Normal file
161
Source/Engine/AI/BehaviorTree.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "BehaviorTree.h"
|
||||
#include "BehaviorTreeNode.h"
|
||||
#include "BehaviorTreeNodes.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Serialization/JsonSerializer.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false);
|
||||
|
||||
BehaviorTreeGraphNode::~BehaviorTreeGraphNode()
|
||||
{
|
||||
SAFE_DELETE(Instance);
|
||||
}
|
||||
|
||||
bool BehaviorTreeGraph::Load(ReadStream* stream, bool loadMeta)
|
||||
{
|
||||
if (VisjectGraph<BehaviorTreeGraphNode>::Load(stream, loadMeta))
|
||||
return true;
|
||||
|
||||
// Build node instances hierarchy
|
||||
for (Node& node : Nodes)
|
||||
{
|
||||
if (auto* nodeCompound = ScriptingObject::Cast<BehaviorTreeCompoundNode>(node.Instance))
|
||||
{
|
||||
for (const GraphBox* childBox : node.Boxes[1].Connections)
|
||||
{
|
||||
const Node* child = childBox ? (Node*)childBox->Parent : nullptr;
|
||||
if (child && child->Instance)
|
||||
{
|
||||
nodeCompound->Children.Add(child->Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BehaviorTreeGraph::Clear()
|
||||
{
|
||||
VisjectGraph<BehaviorTreeGraphNode>::Clear();
|
||||
|
||||
Root = nullptr;
|
||||
}
|
||||
|
||||
bool BehaviorTreeGraph::onNodeLoaded(Node* n)
|
||||
{
|
||||
if (n->GroupID == 19 && (n->TypeID == 1 || n->TypeID == 2))
|
||||
{
|
||||
// Create node instance object
|
||||
ScriptingTypeHandle type = Scripting::FindScriptingType((StringAnsiView)n->Values[0]);
|
||||
if (!type)
|
||||
type = Scripting::FindScriptingType(StringAnsi((StringView)n->Values[0]));
|
||||
if (type)
|
||||
{
|
||||
n->Instance = (BehaviorTreeNode*)Scripting::NewObject(type);
|
||||
const Variant& data = n->Values[1];
|
||||
if (data.Type == VariantType::Blob)
|
||||
JsonSerializer::LoadFromBytes(n->Instance, Span<byte>((byte*)data.AsBlob.Data, data.AsBlob.Length), FLAXENGINE_VERSION_BUILD);
|
||||
|
||||
// Find root node
|
||||
if (!Root && n->Instance && BehaviorTreeRootNode::TypeInitializer == type)
|
||||
Root = (BehaviorTreeRootNode*)n->Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
const String name = n->Values[0].ToString();
|
||||
if (name.HasChars())
|
||||
LOG(Error, "Missing type '{0}'", name);
|
||||
}
|
||||
}
|
||||
|
||||
return VisjectGraph<BehaviorTreeGraphNode>::onNodeLoaded(n);
|
||||
}
|
||||
|
||||
BehaviorTree::BehaviorTree(const SpawnParams& params, const AssetInfo* info)
|
||||
: BinaryAsset(params, info)
|
||||
{
|
||||
}
|
||||
|
||||
BytesContainer BehaviorTree::LoadSurface()
|
||||
{
|
||||
if (WaitForLoaded())
|
||||
return BytesContainer();
|
||||
ScopeLock lock(Locker);
|
||||
if (!LoadChunks(GET_CHUNK_FLAG(0)))
|
||||
{
|
||||
const auto data = GetChunk(0);
|
||||
BytesContainer result;
|
||||
result.Copy(data->Data);
|
||||
return result;
|
||||
}
|
||||
LOG(Warning, "\'{0}\' surface data is missing.", ToString());
|
||||
return BytesContainer();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool BehaviorTree::SaveSurface(const BytesContainer& data)
|
||||
{
|
||||
// Wait for asset to be loaded or don't if last load failed
|
||||
if (LastLoadFailed())
|
||||
{
|
||||
LOG(Warning, "Saving asset that failed to load.");
|
||||
}
|
||||
else if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Set Visject Surface data
|
||||
GetOrCreateChunk(0)->Data.Copy(data);
|
||||
|
||||
// Save
|
||||
AssetInitData assetData;
|
||||
assetData.SerializedVersion = 1;
|
||||
if (SaveAsset(assetData))
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Asset::LoadResult BehaviorTree::load()
|
||||
{
|
||||
// Load graph
|
||||
const auto surfaceChunk = GetChunk(0);
|
||||
if (surfaceChunk == nullptr)
|
||||
return LoadResult::MissingDataChunk;
|
||||
MemoryReadStream surfaceStream(surfaceChunk->Get(), surfaceChunk->Size());
|
||||
if (Graph.Load(&surfaceStream, true))
|
||||
{
|
||||
LOG(Warning, "Failed to load graph \'{0}\'", ToString());
|
||||
return LoadResult::Failed;
|
||||
}
|
||||
|
||||
return LoadResult::Ok;
|
||||
}
|
||||
|
||||
void BehaviorTree::unload(bool isReloading)
|
||||
{
|
||||
// Clear resources
|
||||
Graph.Clear();
|
||||
}
|
||||
|
||||
AssetChunksFlag BehaviorTree::getChunksToPreload() const
|
||||
{
|
||||
return GET_CHUNK_FLAG(0);
|
||||
}
|
||||
42
Source/Engine/AI/BehaviorTree.cs
Normal file
42
Source/Engine/AI/BehaviorTree.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using FlaxEngine.Utilities;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine.GUI;
|
||||
#endif
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial class BehaviorTreeRootNode
|
||||
{
|
||||
#if FLAX_EDITOR
|
||||
private static bool IsValidBlackboardType(ScriptType type)
|
||||
{
|
||||
if (ScriptType.FlaxObject.IsAssignableFrom(type))
|
||||
return false;
|
||||
if (type.Type != null)
|
||||
{
|
||||
if (type.Type.IsDelegate())
|
||||
return false;
|
||||
if (typeof(Control).IsAssignableFrom(type.Type))
|
||||
return false;
|
||||
if (typeof(Attribute).IsAssignableFrom(type.Type))
|
||||
return false;
|
||||
if (type.Type.FullName.StartsWith("FlaxEditor.", StringComparison.Ordinal))
|
||||
return false;
|
||||
}
|
||||
return !type.IsGenericType &&
|
||||
!type.IsInterface &&
|
||||
!type.IsStatic &&
|
||||
!type.IsAbstract &&
|
||||
!type.IsArray &&
|
||||
!type.IsVoid &&
|
||||
(type.IsClass || type.IsStructure) &&
|
||||
type.IsPublic &&
|
||||
type.CanCreateInstance;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
91
Source/Engine/AI/BehaviorTree.h
Normal file
91
Source/Engine/AI/BehaviorTree.h
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Content/BinaryAsset.h"
|
||||
#include "Engine/Visject/VisjectGraph.h"
|
||||
|
||||
class BehaviorTreeNode;
|
||||
class BehaviorTreeRootNode;
|
||||
|
||||
/// <summary>
|
||||
/// Behavior Tree graph node.
|
||||
/// </summary>
|
||||
class BehaviorTreeGraphNode : public VisjectGraphNode<>
|
||||
{
|
||||
public:
|
||||
// Instance of the graph node.
|
||||
BehaviorTreeNode* Instance = nullptr;
|
||||
|
||||
~BehaviorTreeGraphNode();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Behavior Tree graph.
|
||||
/// </summary>
|
||||
class BehaviorTreeGraph : public VisjectGraph<BehaviorTreeGraphNode>
|
||||
{
|
||||
public:
|
||||
// Instance of the graph root node.
|
||||
BehaviorTreeRootNode* Root = nullptr;
|
||||
|
||||
// [VisjectGraph]
|
||||
bool Load(ReadStream* stream, bool loadMeta) override;
|
||||
void Clear() override;
|
||||
bool onNodeLoaded(Node* n) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Behavior Tree graph executor runtime.
|
||||
/// </summary>
|
||||
class BehaviorTreeExecutor : public VisjectExecutor
|
||||
{
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Behavior Tree asset with AI logic graph.
|
||||
/// </summary>
|
||||
/// <seealso cref="BinaryAsset" />
|
||||
API_CLASS(NoSpawn, Sealed) class FLAXENGINE_API BehaviorTree : public BinaryAsset
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(BehaviorTree, 1);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The Behavior Tree graph.
|
||||
/// </summary>
|
||||
BehaviorTreeGraph Graph;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load surface graph from the asset.
|
||||
/// </summary>
|
||||
/// <returns>The surface data or empty if failed to load it.</returns>
|
||||
API_FUNCTION() BytesContainer LoadSurface();
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Updates the graph surface (save new one, discard cached data, reload asset).
|
||||
/// </summary>
|
||||
/// <param name="data">Stream with graph data.</param>
|
||||
/// <returns>True if cannot save it, otherwise false.</returns>
|
||||
API_FUNCTION() bool SaveSurface(const BytesContainer& data);
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
#if USE_EDITOR
|
||||
void GetReferences(Array<Guid>& output) const override
|
||||
{
|
||||
// Base
|
||||
BinaryAsset::GetReferences(output);
|
||||
|
||||
Graph.GetReferences(output);
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// [BinaryAsset]
|
||||
LoadResult load() override;
|
||||
void unload(bool isReloading) override;
|
||||
AssetChunksFlag getChunksToPreload() const override;
|
||||
};
|
||||
29
Source/Engine/AI/BehaviorTreeNode.h
Normal file
29
Source/Engine/AI/BehaviorTreeNode.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/SerializableScriptingObject.h"
|
||||
#include "BehaviorTypes.h"
|
||||
|
||||
/// <summary>
|
||||
/// Base class for Behavior Tree nodes.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeNode : public SerializableScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeNode, SerializableScriptingObject);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Node user name (eg. Follow Enemy, or Pick up Weapon).
|
||||
/// </summary>
|
||||
API_FIELD() String Name;
|
||||
|
||||
// TODO: decorators/conditionals
|
||||
// TODO: init methods
|
||||
// TODO: instance data ctor/dtor
|
||||
// TODO: start/stop methods
|
||||
// TODO: update method
|
||||
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
};
|
||||
20
Source/Engine/AI/BehaviorTreeNodes.cpp
Normal file
20
Source/Engine/AI/BehaviorTreeNodes.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "BehaviorTreeNodes.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
void BehaviorTreeNode::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
SerializableScriptingObject::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(BehaviorTreeNode);
|
||||
SERIALIZE(Name);
|
||||
}
|
||||
|
||||
void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
SerializableScriptingObject::Deserialize(stream, modifier);
|
||||
|
||||
Name.Clear(); // Missing Name is assumes as unnamed node
|
||||
DESERIALIZE(Name);
|
||||
}
|
||||
41
Source/Engine/AI/BehaviorTreeNodes.h
Normal file
41
Source/Engine/AI/BehaviorTreeNodes.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BehaviorTreeNode.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Base class for compound Behavior Tree nodes that composite child nodes.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract) class FLAXENGINE_API BehaviorTreeCompoundNode : public BehaviorTreeNode
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeCompoundNode, BehaviorTreeNode);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// List with all child nodes.
|
||||
/// </summary>
|
||||
API_FIELD(Readonly) Array<BehaviorTreeNode*, InlinedAllocation<8>> Children;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sequence node updates all its children 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);
|
||||
};
|
||||
|
||||
/// <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
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeRootNode, BehaviorTreeCompoundNode);
|
||||
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;
|
||||
};
|
||||
35
Source/Engine/AI/BehaviorTypes.h
Normal file
35
Source/Engine/AI/BehaviorTypes.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
class BehaviorTree;
|
||||
class BehaviorTreeNode;
|
||||
class BehaviorKnowledge;
|
||||
|
||||
/// <summary>
|
||||
/// Behavior update context state.
|
||||
/// </summary>
|
||||
API_STRUCT() struct FLAXENGINE_API BehaviorUpdateContext
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorUpdateContext);
|
||||
|
||||
/// <summary>
|
||||
/// Simulation time delta (in seconds) since the last update.
|
||||
/// </summary>
|
||||
float DeltaTime;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Behavior update result.
|
||||
/// </summary>
|
||||
API_ENUM() enum class BehaviorUpdateResult
|
||||
{
|
||||
// Action completed successfully.
|
||||
Success,
|
||||
// Action is still running and active.
|
||||
Running,
|
||||
// Action failed.
|
||||
Failed,
|
||||
};
|
||||
Reference in New Issue
Block a user