diff --git a/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
new file mode 100644
index 000000000..0da87c7ec
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
@@ -0,0 +1,145 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
+
+namespace FlaxEditor.CustomEditors.Editors
+{
+ ///
+ /// Custom editor for and .
+ ///
+ public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor
+ {
+ private ClickableLabel _label;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ _label = layout.ClickableLabel(Path).CustomControl;
+ _label.RightClick += ShowPicker;
+ var button = new Button
+ {
+ Size = new Float2(16.0f),
+ Text = "...",
+ TooltipText = "Edit...",
+ Parent = _label,
+ };
+ button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
+ button.Clicked += ShowPicker;
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ // Update label
+ _label.Text = Path;
+ }
+
+ private string Path
+ {
+ get
+ {
+ var v = Values[0];
+ if (v is BehaviorKnowledgeSelectorAny any)
+ return any.Path;
+ if (v is string str)
+ return str;
+ var pathField = v.GetType().GetField("Path");
+ return pathField.GetValue(v) as string;
+ }
+ set
+ {
+ var v = Values[0];
+ if (v is BehaviorKnowledgeSelectorAny)
+ v = new BehaviorKnowledgeSelectorAny(value);
+ else if (v is string)
+ v = value;
+ else
+ {
+ var pathField = v.GetType().GetField("Path");
+ pathField.SetValue(v, value);
+ }
+ SetValue(v);
+ }
+ }
+
+ private void ShowPicker()
+ {
+ // Get Behavior Knowledge to select from
+ var behaviorTreeWindow = Presenter.Owner as Windows.Assets.BehaviorTreeWindow;
+ var blackboard = behaviorTreeWindow?.Blackboard;
+ if (blackboard == null)
+ return;
+ var typed = ScriptType.Null;
+ var valueType = Values[0].GetType();
+ if (valueType.Name == "BehaviorKnowledgeSelector`1")
+ {
+ // Get typed selector type to show only assignable items
+ typed = new ScriptType(valueType.GenericTypeArguments[0]);
+ }
+ // TODO: add support for selecting goals and sensors
+
+ // Create menu with tree-like structure and search box
+ var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 0, true);
+ var blackboardType = TypeUtils.GetObjectType(blackboard);
+ var items = GenericEditor.GetItemsForType(blackboardType, blackboardType.IsClass, true);
+ var selected = Path;
+ var noneNode = new TreeNode
+ {
+ Text = "",
+ TooltipText = "Deselect value",
+ Parent = tree,
+ };
+ if (string.IsNullOrEmpty(selected))
+ tree.Select(noneNode);
+ var blackboardNode = new TreeNode
+ {
+ Text = "Blackboard",
+ TooltipText = blackboardType.TypeName,
+ Tag = "Blackboard/", // Ability to select whole blackboard data
+ Parent = tree,
+ };
+ if (typed && !typed.IsAssignableFrom(blackboardType))
+ blackboardNode.Tag = null;
+ if (string.Equals(selected, (string)blackboardNode.Tag, StringComparison.Ordinal))
+ tree.Select(blackboardNode);
+ foreach (var item in items)
+ {
+ if (typed && !typed.IsAssignableFrom(item.Info.ValueType))
+ continue;
+ var path = "Blackboard/" + item.Info.Name;
+ var node = new TreeNode
+ {
+ Text = item.DisplayName,
+ TooltipText = item.TooltipText,
+ Tag = path,
+ Parent = blackboardNode,
+ };
+ if (string.Equals(selected, path, StringComparison.Ordinal))
+ tree.Select(node);
+ // TODO: add support for nested items (eg. field from blackboard structure field)
+ }
+ blackboardNode.Expand(true);
+ tree.SelectedChanged += delegate(List before, List after)
+ {
+ if (after.Count == 1)
+ {
+ menu.Hide();
+ Path = after[0].Tag as string;
+ }
+ };
+ menu.Show(_label, new Float2(0, _label.Height));
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index cba2b5a5a..55ac453a9 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors
/// Describes object property/field information for custom editors pipeline.
///
///
- protected class ItemInfo : IComparable
+ public class ItemInfo : IComparable
{
private Options.GeneralOptions.MembersOrder _membersOrder;
@@ -248,7 +248,7 @@ namespace FlaxEditor.CustomEditors.Editors
/// True if use type properties.
/// True if use type fields.
/// The items.
- protected List GetItemsForType(ScriptType type, bool useProperties, bool useFields)
+ public static List GetItemsForType(ScriptType type, bool useProperties, bool useFields)
{
var items = new List();
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index fe801bbaf..e2022d03c 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -11,6 +11,7 @@ using System.Globalization;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
@@ -1060,8 +1061,9 @@ namespace FlaxEditor.Utilities
/// The search box.
/// The tree control.
/// Amount of additional space above the search box to put custom UI.
+ /// Plug automatic tree search delegate.
/// The created menu to setup and show.
- public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0)
+ public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0, bool autoSearch = false)
{
var menu = new ContextMenuBase
{
@@ -1087,9 +1089,35 @@ namespace FlaxEditor.Utilities
{
Parent = panel2,
};
+ if (autoSearch)
+ {
+ var s = searchBox;
+ var t = tree;
+ searchBox.TextChanged += delegate
+ {
+ if (t.IsLayoutLocked)
+ return;
+ t.LockChildrenRecursive();
+ UpdateSearchPopupFilter(t, s.Text);
+ t.UnlockChildrenRecursive();
+ menu.PerformLayout();
+ };
+ }
return menu;
}
+ ///
+ /// Updates (recursively) search popup tree structures based on the filter text.
+ ///
+ public static void UpdateSearchPopupFilter(Tree tree, string filterText)
+ {
+ for (int i = 0; i < tree.Children.Count; i++)
+ {
+ if (tree.Children[i] is TreeNode child)
+ UpdateSearchPopupFilter(child, filterText);
+ }
+ }
+
///
/// Updates (recursively) search popup tree structures based on the filter text.
///
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index f2dde24a8..7c5ff57a4 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -6,8 +6,10 @@ using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI;
+using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Surface;
+using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -19,7 +21,7 @@ namespace FlaxEditor.Windows.Assets
///
///
///
- public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow
+ public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow, IPresenterOwner
{
private readonly SplitPanel _split1;
private readonly SplitPanel _split2;
@@ -44,6 +46,11 @@ namespace FlaxEditor.Windows.Assets
///
public Undo Undo => _undo;
+ ///
+ /// Current instance of the Behavior Knowledge's blackboard type or null.
+ ///
+ public object Blackboard => _knowledgePropertiesEditor.Selection.Count != 0 ? _knowledgePropertiesEditor.Selection[0] : null;
+
///
public BehaviorTreeWindow(Editor editor, BinaryAssetItem item)
: base(editor, item)
@@ -81,11 +88,11 @@ namespace FlaxEditor.Windows.Assets
_surface.SelectionChanged += OnNodeSelectionChanged;
// Properties editors
- _nodePropertiesEditor = new CustomEditorPresenter(null); // Surface handles undo for nodes editing
+ _nodePropertiesEditor = new CustomEditorPresenter(null, null, this); // Surface handles undo for nodes editing
_nodePropertiesEditor.Features = FeatureFlags.UseDefault;
_nodePropertiesEditor.Panel.Parent = _split2.Panel1;
_nodePropertiesEditor.Modified += OnNodePropertyEdited;
- _knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned"); // No undo for knowledge editing
+ _knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned", this); // No undo for knowledge editing
_knowledgePropertiesEditor.Features = FeatureFlags.None;
_knowledgePropertiesEditor.Panel.Parent = _split2.Panel2;
@@ -429,5 +436,13 @@ namespace FlaxEditor.Windows.Assets
///
public VisjectSurface VisjectSurface => _surface;
+
+ ///
+ public EditorViewport PresenterViewport => null;
+
+ ///
+ public void Select(List nodes)
+ {
+ }
}
}
diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp
index f08b733ad..a8eb761a8 100644
--- a/Source/Engine/AI/Behavior.cpp
+++ b/Source/Engine/AI/Behavior.cpp
@@ -5,41 +5,6 @@
#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);
- RelevantNodes.Resize(tree->Graph.NodesCount, false);
- RelevantNodes.SetAll(false);
- if (!Memory && tree->Graph.NodesStatesSize)
- Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
-}
-
-void BehaviorKnowledge::FreeMemory()
-{
- if (Memory)
- {
- // Release any outstanding nodes state and memory
- ASSERT_LOW_LAYER(Tree);
- for (const auto& node : Tree->Graph.Nodes)
- {
- if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex])
- node.Instance->ReleaseState(Behavior, Memory);
- }
- Allocator::Free(Memory);
- Memory = nullptr;
- }
- RelevantNodes.Clear();
- Blackboard.DeleteValue();
- Tree = nullptr;
-}
-
Behavior::Behavior(const SpawnParams& params)
: Script(params)
{
diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp
new file mode 100644
index 000000000..c08a09ffc
--- /dev/null
+++ b/Source/Engine/AI/BehaviorKnowledge.cpp
@@ -0,0 +1,168 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#include "BehaviorKnowledge.h"
+#include "BehaviorTree.h"
+#include "BehaviorTreeNodes.h"
+#include "BehaviorKnowledgeSelector.h"
+#include "Engine/Scripting/Scripting.h"
+#include "Engine/Scripting/BinaryModule.h"
+#include "Engine/Scripting/ManagedCLR/MProperty.h"
+#if USE_CSHARP
+#include "Engine/Scripting/ManagedCLR/MClass.h"
+#include "Engine/Scripting/ManagedCLR/MField.h"
+#include "Engine/Scripting/ManagedCLR/MProperty.h"
+#include "Engine/Scripting/ManagedCLR/MUtils.h"
+#endif
+
+bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& value, bool set)
+{
+ if (member.IsEmpty())
+ {
+ // Whole blackboard value
+ CHECK_RETURN(instance.Type == value.Type, false);
+ if (set)
+ instance = value;
+ else
+ value = instance;
+ return true;
+ }
+ // TODO: support further path for nested value types (eg. structure field access)
+
+ const StringAnsiView typeName(instance.Type.TypeName);
+ const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
+ if (typeHandle)
+ {
+ const ScriptingType& type = typeHandle.GetType();
+ switch (type.Type)
+ {
+ case ScriptingTypes::Structure:
+ {
+ const String memberStr(member);
+ // TODO: let SetField/GetField return boolean status of operation maybe?
+ if (set)
+ type.Struct.SetField(instance.AsBlob.Data, memberStr, value);
+ else
+ type.Struct.GetField(instance.AsBlob.Data, memberStr, value);
+ return true;
+ }
+ default:
+ {
+ if (void* field = typeHandle.Module->FindField(typeHandle, member))
+ {
+ if (set)
+ return !typeHandle.Module->SetFieldValue(field, instance, value);
+ else
+ return !typeHandle.Module->GetFieldValue(field, instance, value);
+ }
+ break;
+ }
+ }
+ }
+#if USE_CSHARP
+ if (const auto mClass = Scripting::FindClass(typeName))
+ {
+ MObject* instanceObject = MUtils::BoxVariant(instance);
+ if (const auto mField = mClass->GetField(member.Get()))
+ {
+ bool failed;
+ if (set)
+ mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed));
+ else
+ value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject));
+ return true;
+ }
+ else if (const auto mProperty = mClass->GetProperty(member.Get()))
+ {
+ if (set)
+ mProperty->SetValue(instanceObject, MUtils::BoxVariant(value), nullptr);
+ else
+ value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr));
+ return true;
+ }
+ }
+#endif
+ else
+ {
+ LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
+ }
+
+ return false;
+}
+
+bool AccessBehaviorKnowledge(BehaviorKnowledge* knowledge, const StringAnsiView& path, Variant& value, bool set)
+{
+ const int32 typeEnd = path.Find('/');
+ if (typeEnd == -1)
+ return false;
+ const StringAnsiView type(path.Get(), typeEnd);
+ if (type == "Blackboard")
+ {
+ const StringAnsiView member(path.Get() + typeEnd + 1, path.Length() - typeEnd - 1);
+ return AccessVariant(knowledge->Blackboard, member, value, set);
+ }
+ // TODO: goals and sensors data access from BehaviorKnowledge via Selector
+ return false;
+}
+
+bool BehaviorKnowledgeSelectorAny::Set(BehaviorKnowledge* knowledge, const Variant& value)
+{
+ return knowledge && knowledge->Set(Path, value);
+}
+
+Variant BehaviorKnowledgeSelectorAny::Get(BehaviorKnowledge* knowledge)
+{
+ Variant value;
+ if (knowledge)
+ knowledge->Get(Path, value);
+ return value;
+}
+
+bool BehaviorKnowledgeSelectorAny::TryGet(BehaviorKnowledge* knowledge, Variant& value)
+{
+ return knowledge && knowledge->Get(Path, value);
+}
+
+BehaviorKnowledge::~BehaviorKnowledge()
+{
+ FreeMemory();
+}
+
+void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
+{
+ ASSERT_LOW_LAYER(!Tree && tree);
+ Tree = tree;
+ Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType);
+ RelevantNodes.Resize(tree->Graph.NodesCount, false);
+ RelevantNodes.SetAll(false);
+ if (!Memory && tree->Graph.NodesStatesSize)
+ Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
+}
+
+void BehaviorKnowledge::FreeMemory()
+{
+ if (Memory)
+ {
+ // Release any outstanding nodes state and memory
+ ASSERT_LOW_LAYER(Tree);
+ for (const auto& node : Tree->Graph.Nodes)
+ {
+ if (node.Instance && node.Instance->_executionIndex != -1 && RelevantNodes[node.Instance->_executionIndex])
+ node.Instance->ReleaseState(Behavior, Memory);
+ }
+ Allocator::Free(Memory);
+ Memory = nullptr;
+ }
+ RelevantNodes.Clear();
+ Blackboard.DeleteValue();
+ Tree = nullptr;
+}
+
+bool BehaviorKnowledge::Get(const StringAnsiView& path, Variant& value)
+{
+ return AccessBehaviorKnowledge(this, path, value, false);
+}
+
+bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
+{
+ return AccessBehaviorKnowledge(this, path, const_cast(value), true);
+}
diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h
index a39e70eea..252f85764 100644
--- a/Source/Engine/AI/BehaviorKnowledge.h
+++ b/Source/Engine/AI/BehaviorKnowledge.h
@@ -51,4 +51,22 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject
/// Releases the memory of the knowledge.
///
void FreeMemory();
+
+ ///
+ /// Gets the knowledge item value via selector path.
+ ///
+ ///
+ /// Selector path.
+ /// Result value (valid only when returned true).
+ /// True if got value, otherwise false.
+ API_FUNCTION() bool Get(const StringAnsiView& path, API_PARAM(Out) Variant& value);
+
+ ///
+ /// Sets the knowledge item value via selector path.
+ ///
+ ///
+ /// Selector path.
+ /// Value to set.
+ /// True if set value, otherwise false.
+ API_FUNCTION() bool Set(const StringAnsiView& path, const Variant& value);
};
diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs
new file mode 100644
index 000000000..7d5698b6e
--- /dev/null
+++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs
@@ -0,0 +1,238 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+
+namespace FlaxEngine
+{
+#if FLAX_EDITOR
+ [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))]
+#endif
+ partial struct BehaviorKnowledgeSelectorAny : IComparable, IComparable
+ {
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The selector path.
+ public BehaviorKnowledgeSelectorAny(string path)
+ {
+ Path = path;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The other selector.
+ public BehaviorKnowledgeSelectorAny(BehaviorKnowledgeSelectorAny other)
+ {
+ Path = other.Path;
+ }
+
+ ///
+ /// Implicit cast operator from selector to string.
+ ///
+ /// Selector
+ /// Path
+ public static implicit operator string(BehaviorKnowledgeSelectorAny value)
+ {
+ return value.Path;
+ }
+
+ ///
+ /// Implicit cast operator from string to selector.
+ ///
+ /// Path
+ /// Selector
+ public static implicit operator BehaviorKnowledgeSelectorAny(string value)
+ {
+ return new BehaviorKnowledgeSelectorAny(value);
+ }
+
+ ///
+ /// Sets the selected knowledge value.
+ ///
+ /// The knowledge container to access.
+ /// The value to set.
+ /// True if set value value, otherwise false.
+ public bool Set(BehaviorKnowledge knowledge, object value)
+ {
+ return knowledge != null && knowledge.Set(Path, value);
+ }
+
+ ///
+ /// Gets the selected knowledge value.
+ ///
+ /// The knowledge container to access.
+ /// The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name).
+ public object Get(BehaviorKnowledge knowledge)
+ {
+ object value = null;
+ if (knowledge != null)
+ knowledge.Get(Path, out value);
+ return value;
+ }
+
+ ///
+ /// Tries to get the selected knowledge value. Returns true if got value, otherwise false.
+ ///
+ /// The knowledge container to access.
+ /// The output value.
+ /// True if got value, otherwise false.
+ public bool TryGet(BehaviorKnowledge knowledge, out object value)
+ {
+ value = null;
+ return knowledge != null && knowledge.Get(Path, out value);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Path;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Path?.GetHashCode() ?? 0;
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ if (obj is BehaviorKnowledgeSelectorAny other)
+ return CompareTo(other);
+ return 0;
+ }
+
+ ///
+ public int CompareTo(BehaviorKnowledgeSelectorAny other)
+ {
+ return string.Compare(Path, other.Path, StringComparison.Ordinal);
+ }
+ }
+
+ ///
+ /// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values.
+ ///
+#if FLAX_EDITOR
+ [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.BehaviorKnowledgeSelectorEditor))]
+#endif
+ public struct BehaviorKnowledgeSelector : IComparable, IComparable, IComparable>
+ {
+ ///
+ /// Selector path that redirects to the specific knowledge value.
+ ///
+ public string Path;
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The selector path.
+ public BehaviorKnowledgeSelector(string path)
+ {
+ Path = path;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The other selector.
+ public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other)
+ {
+ Path = other.Path;
+ }
+
+ ///
+ /// Implicit cast operator from selector to string.
+ ///
+ /// Selector
+ /// Path
+ public static implicit operator string(BehaviorKnowledgeSelector value)
+ {
+ return value.Path;
+ }
+
+ ///
+ /// Implicit cast operator from string to selector.
+ ///
+ /// Path
+ /// Selector
+ public static implicit operator BehaviorKnowledgeSelector(string value)
+ {
+ return new BehaviorKnowledgeSelector(value);
+ }
+
+ ///
+ /// Sets the selected knowledge value.
+ ///
+ /// The knowledge container to access.
+ /// The value to set.
+ /// True if set value value, otherwise false.
+ public bool Set(BehaviorKnowledge knowledge, T value)
+ {
+ return knowledge != null && knowledge.Set(Path, value);
+ }
+
+ ///
+ /// Gets the selected knowledge value.
+ ///
+ /// The knowledge container to access.
+ /// The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name).
+ public T Get(BehaviorKnowledge knowledge)
+ {
+ object value = null;
+ if (knowledge != null)
+ knowledge.Get(Path, out value);
+ return (T)value;
+ }
+
+ ///
+ /// Tries to get the selected knowledge value. Returns true if got value, otherwise false.
+ ///
+ /// The knowledge container to access.
+ /// The output value.
+ /// True if got value, otherwise false.
+ public bool TryGet(BehaviorKnowledge knowledge, out T value)
+ {
+ value = default;
+ object tmp = null;
+ bool result = knowledge != null && knowledge.Get(Path, out tmp);
+ if (result)
+ value = (T)tmp;
+ return result;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Path;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Path?.GetHashCode() ?? 0;
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ if (obj is BehaviorKnowledgeSelectorAny otherAny)
+ return CompareTo(otherAny);
+ if (obj is BehaviorKnowledgeSelector other)
+ return CompareTo(other);
+ return 0;
+ }
+
+ ///
+ public int CompareTo(BehaviorKnowledgeSelectorAny other)
+ {
+ return string.Compare(Path, other.Path, StringComparison.Ordinal);
+ }
+
+ ///
+ public int CompareTo(BehaviorKnowledgeSelector other)
+ {
+ return string.Compare(Path, other.Path, StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h
new file mode 100644
index 000000000..96241a9d3
--- /dev/null
+++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Types/Variant.h"
+#include "Engine/Serialization/SerializationFwd.h"
+
+class BehaviorKnowledge;
+
+///
+/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values.
+///
+API_STRUCT(NoDefault, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelectorAny
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(BehaviorKnowledgeSelectorAny);
+
+ ///
+ /// Selector path that redirects to the specific knowledge value.
+ ///
+ API_FIELD() StringAnsi Path;
+
+ // Sets the selected knowledge value (as Variant).
+ bool Set(BehaviorKnowledge* knowledge, const Variant& value);
+
+ // Gets the selected knowledge value (as Variant).
+ Variant Get(BehaviorKnowledge* knowledge);
+
+ // Tries to get the selected knowledge value (as Variant). Returns true if got value, otherwise false.
+ bool TryGet(BehaviorKnowledge* knowledge, Variant& value);
+
+ FORCE_INLINE bool operator==(const BehaviorKnowledgeSelectorAny& other) const
+ {
+ return Path == other.Path;
+ }
+
+ BehaviorKnowledgeSelectorAny& operator=(const StringAnsiView& other) noexcept
+ {
+ Path = other;
+ return *this;
+ }
+
+ BehaviorKnowledgeSelectorAny& operator=(StringAnsi&& other) noexcept
+ {
+ Path = MoveTemp(other);
+ return *this;
+ }
+
+ operator StringAnsi() const
+ {
+ return Path;
+ }
+};
+
+///
+/// Behavior knowledge value selector that can reference blackboard item, behavior goal or sensor values.
+///
+template
+API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API BehaviorKnowledgeSelector : BehaviorKnowledgeSelectorAny
+{
+ using BehaviorKnowledgeSelectorAny::Set;
+ using BehaviorKnowledgeSelectorAny::Get;
+ using BehaviorKnowledgeSelectorAny::TryGet;
+
+ // Sets the selected knowledge value (typed).
+ FORCE_INLINE void Set(BehaviorKnowledge* knowledge, const T& value)
+ {
+ BehaviorKnowledgeSelectorAny::Set(knowledge, Variant(value));
+ }
+
+ // Gets the selected knowledge value (typed).
+ FORCE_INLINE T Get(BehaviorKnowledge* knowledge)
+ {
+ return (T)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)
+ {
+ Variant variant;
+ if (BehaviorKnowledgeSelectorAny::TryGet(knowledge, variant))
+ {
+ value = (T)variant;
+ return true;
+ }
+ return false;
+ }
+
+ BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept
+ {
+ Path = other;
+ return *this;
+ }
+
+ BehaviorKnowledgeSelector& operator=(StringAnsi&& other) noexcept
+ {
+ Path = MoveTemp(other);
+ return *this;
+ }
+
+ operator StringAnsi() const
+ {
+ return Path;
+ }
+};
+
+inline uint32 GetHash(const BehaviorKnowledgeSelectorAny& key)
+{
+ return GetHash(key.Path);
+}
+
+// @formatter:off
+namespace Serialization
+{
+ inline bool ShouldSerialize(const BehaviorKnowledgeSelectorAny& v, const void* otherObj)
+ {
+ return !otherObj || v.Path != ((BehaviorKnowledgeSelectorAny*)otherObj)->Path;
+ }
+ inline void Serialize(ISerializable::SerializeStream& stream, const BehaviorKnowledgeSelectorAny& v, const void* otherObj)
+ {
+ stream.String(v.Path);
+ }
+ inline void Deserialize(ISerializable::DeserializeStream& stream, BehaviorKnowledgeSelectorAny& v, ISerializeModifier* modifier)
+ {
+ v.Path = stream.GetTextAnsi();
+ }
+}
+// @formatter:on
diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs
index bd342f3d2..d35cf2802 100644
--- a/Source/Engine/AI/BehaviorTree.cs
+++ b/Source/Engine/AI/BehaviorTree.cs
@@ -1,9 +1,9 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if FLAX_EDITOR
-using System;
using FlaxEngine.Utilities;
using FlaxEditor.Scripting;
using FlaxEngine.GUI;
diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs
index 40f9cb5a2..5843f7ec6 100644
--- a/Source/Engine/Serialization/JsonConverters.cs
+++ b/Source/Engine/Serialization/JsonConverters.cs
@@ -128,7 +128,6 @@ namespace FlaxEngine.Json
var result = new SoftTypeReference();
if (reader.TokenType == JsonToken.String)
result.TypeName = (string)reader.Value;
-
return result;
}
@@ -139,6 +138,34 @@ namespace FlaxEngine.Json
}
}
+ ///
+ /// Serialize as path string in internal format.
+ ///
+ ///
+ internal class BehaviorKnowledgeSelectorAnyConverter : JsonConverter
+ {
+ ///
+ public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ writer.WriteValue(((BehaviorKnowledgeSelectorAny)value).Path);
+ }
+
+ ///
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var result = new BehaviorKnowledgeSelectorAny();
+ if (reader.TokenType == JsonToken.String)
+ result.Path = (string)reader.Value;
+ return result;
+ }
+
+ ///
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(BehaviorKnowledgeSelectorAny);
+ }
+ }
+
///
/// Serialize as Guid in internal format.
///
diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs
index 619862b1f..013a52391 100644
--- a/Source/Engine/Serialization/JsonSerializer.cs
+++ b/Source/Engine/Serialization/JsonSerializer.cs
@@ -124,6 +124,7 @@ namespace FlaxEngine.Json
settings.Converters.Add(new SceneReferenceConverter());
settings.Converters.Add(new SoftObjectReferenceConverter());
settings.Converters.Add(new SoftTypeReferenceConverter());
+ settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter());
settings.Converters.Add(new MarginConverter());
settings.Converters.Add(new VersionConverter());
settings.Converters.Add(new LocalizedStringConverter());