Add concept of Goals to Behavior Knowledge
This commit is contained in:
@@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
base.Refresh();
|
||||
|
||||
// Update label
|
||||
_label.Text = Path;
|
||||
_label.Text = _label.TooltipText = Path;
|
||||
}
|
||||
|
||||
private string Path
|
||||
@@ -78,8 +78,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
// Get Behavior Knowledge to select from
|
||||
var behaviorTreeWindow = Presenter.Owner as Windows.Assets.BehaviorTreeWindow;
|
||||
var blackboard = behaviorTreeWindow?.Blackboard;
|
||||
if (blackboard == null)
|
||||
var rootNode = behaviorTreeWindow?.RootNode;
|
||||
if (rootNode == null)
|
||||
return;
|
||||
var typed = ScriptType.Null;
|
||||
var valueType = Values[0].GetType();
|
||||
@@ -88,49 +88,10 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
// 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 = "<none>",
|
||||
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<TreeNode> before, List<TreeNode> after)
|
||||
{
|
||||
if (after.Count == 1)
|
||||
@@ -139,7 +100,79 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
Path = after[0].Tag as string;
|
||||
}
|
||||
};
|
||||
|
||||
// Empty
|
||||
var noneNode = new TreeNode
|
||||
{
|
||||
Text = "<none>",
|
||||
TooltipText = "Deselect value",
|
||||
Parent = tree,
|
||||
};
|
||||
if (string.IsNullOrEmpty(selected))
|
||||
tree.Select(noneNode);
|
||||
|
||||
// Blackboard
|
||||
SetupPickerTypeItems(tree, typed, selected, "Blackboard", "Blackboard/", rootNode.BlackboardType);
|
||||
|
||||
// Goals
|
||||
var goalTypes = rootNode.GoalTypes;
|
||||
if (goalTypes?.Length != 0)
|
||||
{
|
||||
var goalsNode = new TreeNode
|
||||
{
|
||||
Text = "Goal",
|
||||
TooltipText = "List of goal types defined in Blackboard Tree",
|
||||
Parent = tree,
|
||||
};
|
||||
foreach (var goalTypeName in goalTypes)
|
||||
{
|
||||
var goalType = TypeUtils.GetType(goalTypeName);
|
||||
if (goalType == null)
|
||||
continue;
|
||||
var goalTypeNode = SetupPickerTypeItems(tree, typed, selected, goalType.Name, "Goal/" + goalTypeName + "/", goalTypeName);
|
||||
goalTypeNode.Parent = goalsNode;
|
||||
}
|
||||
goalsNode.ExpandAll(true);
|
||||
}
|
||||
|
||||
menu.Show(_label, new Float2(0, _label.Height));
|
||||
}
|
||||
|
||||
private TreeNode SetupPickerTypeItems(Tree tree, ScriptType typed, string selected, string text, string typePath, string typeName)
|
||||
{
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type == null)
|
||||
return null;
|
||||
var items = GenericEditor.GetItemsForType(type, type.IsClass, true);
|
||||
var typeNode = new TreeNode
|
||||
{
|
||||
Text = text,
|
||||
TooltipText = type.TypeName,
|
||||
Tag = typePath, // Ability to select whole item type data (eg. whole blackboard value)
|
||||
Parent = tree,
|
||||
};
|
||||
if (typed && !typed.IsAssignableFrom(type))
|
||||
typeNode.Tag = null;
|
||||
if (string.Equals(selected, (string)typeNode.Tag, StringComparison.Ordinal))
|
||||
tree.Select(typeNode);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (typed && !typed.IsAssignableFrom(item.Info.ValueType))
|
||||
continue;
|
||||
var itemPath = typePath + item.Info.Name;
|
||||
var node = new TreeNode
|
||||
{
|
||||
Text = item.DisplayName,
|
||||
TooltipText = item.TooltipText,
|
||||
Tag = itemPath,
|
||||
Parent = typeNode,
|
||||
};
|
||||
if (string.Equals(selected, itemPath, StringComparison.Ordinal))
|
||||
tree.Select(node);
|
||||
// TODO: add support for nested items (eg. field from blackboard structure field)
|
||||
}
|
||||
typeNode.Expand(true);
|
||||
return typeNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
public object Blackboard => _knowledgePropertiesEditor.Selection.Count != 0 ? _knowledgePropertiesEditor.Selection[0] : null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets instance of the root node of the graph. Returns null if not added (or graph not yet loaded).
|
||||
/// </summary>
|
||||
public BehaviorTreeRootNode RootNode => (_surface.FindNode(19, 2) as Surface.Archetypes.BehaviorTree.Node)?.Instance as BehaviorTreeRootNode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public BehaviorTreeWindow(Editor editor, BinaryAssetItem item)
|
||||
: base(editor, item)
|
||||
|
||||
@@ -100,7 +100,20 @@ bool AccessBehaviorKnowledge(BehaviorKnowledge* knowledge, const StringAnsiView&
|
||||
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
|
||||
if (type == "Goal")
|
||||
{
|
||||
const StringAnsiView subPath(path.Get() + typeEnd + 1, path.Length() - typeEnd - 1);
|
||||
const int32 goalTypeEnd = subPath.Find('/');
|
||||
const StringAnsiView goalType(subPath.Get(), goalTypeEnd);
|
||||
const StringAnsiView member(subPath.Get() + goalTypeEnd + 1, subPath.Length() - goalTypeEnd - 1);
|
||||
for (Variant& goal : knowledge->Goals)
|
||||
{
|
||||
if (goalType == goal.Type.GetTypeName())
|
||||
{
|
||||
return AccessVariant(goal, member, value, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -161,6 +174,9 @@ void BehaviorKnowledge::FreeMemory()
|
||||
}
|
||||
RelevantNodes.Clear();
|
||||
Blackboard.DeleteValue();
|
||||
for (Variant& goal : Goals)
|
||||
goal.DeleteValue();
|
||||
Goals.Resize(0);
|
||||
Tree = nullptr;
|
||||
}
|
||||
|
||||
@@ -174,6 +190,43 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
|
||||
return AccessBehaviorKnowledge(this, path, const_cast<Variant&>(value), true);
|
||||
}
|
||||
|
||||
bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const
|
||||
{
|
||||
for (int32 i = 0; i < Goals.Count(); i++)
|
||||
{
|
||||
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
|
||||
if (goalType == type)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BehaviorKnowledge::AddGoal(Variant&& goal)
|
||||
{
|
||||
int32 i = 0;
|
||||
for (; i < Goals.Count(); i++)
|
||||
{
|
||||
if (Goals[i].Type == goal.Type)
|
||||
break;
|
||||
}
|
||||
if (i == Goals.Count())
|
||||
Goals.AddDefault();
|
||||
Goals.Get()[i] = MoveTemp(goal);
|
||||
}
|
||||
|
||||
void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type)
|
||||
{
|
||||
for (int32 i = 0; i < Goals.Count(); i++)
|
||||
{
|
||||
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
|
||||
if (goalType == type)
|
||||
{
|
||||
Goals.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BehaviorKnowledge::CompareValues(float a, float b, BehaviorValueComparison comparison)
|
||||
{
|
||||
switch (comparison)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/Variant.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/BitArray.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
|
||||
@@ -43,6 +44,12 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject
|
||||
/// </summary>
|
||||
API_FIELD() Variant Blackboard;
|
||||
|
||||
/// <summary>
|
||||
/// List of all active goals of the behaviour (structure or class).
|
||||
/// </summary>
|
||||
Array<Variant> Goals;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes the knowledge for a certain tree.
|
||||
/// </summary>
|
||||
@@ -71,6 +78,46 @@ API_CLASS() class FLAXENGINE_API BehaviorKnowledge : public ScriptingObject
|
||||
/// <returns>True if set value, otherwise false.</returns>
|
||||
API_FUNCTION() bool Set(const StringAnsiView& path, const Variant& value);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Checks if knowledge has a given goal (exact type match without base class check).
|
||||
/// </summary>
|
||||
/// <param name="type">The goal type.</param>
|
||||
/// <returns>True if has a given goal, otherwise false.</returns>
|
||||
API_FUNCTION() bool HasGoal(ScriptingTypeHandle type) const;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if knowledge has a given goal (exact type match without base class check).
|
||||
/// </summary>
|
||||
/// <returns>True if has a given goal, otherwise false.</returns>
|
||||
template<typename T>
|
||||
FORCE_INLINE bool HasGoal()
|
||||
{
|
||||
return HasGoal(T::TypeInitializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the goal to the knowledge. If goal of that type already exists then it's value is updated.
|
||||
/// </summary>
|
||||
/// <param name="goal">The goal value to add/set.</param>
|
||||
API_FUNCTION() void AddGoal(Variant&& goal);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the goal from the knowledge. Does nothing if goal of the given type doesn't exist in the knowledge.
|
||||
/// </summary>
|
||||
/// <param name="type">The goal type.</param>
|
||||
API_FUNCTION() void RemoveGoal(ScriptingTypeHandle type);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the goal from the knowledge. Does nothing if goal of the given type doesn't exist in the knowledge.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
FORCE_INLINE void RemoveGoal()
|
||||
{
|
||||
RemoveGoal(T::TypeInitializer);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Compares two values and returns the comparision result.
|
||||
/// </summary>
|
||||
|
||||
@@ -179,10 +179,9 @@ namespace FlaxEngine
|
||||
/// <returns>The output value or null (if cannot read it - eg. missing goal or no blackboard entry of that name).</returns>
|
||||
public T Get(BehaviorKnowledge knowledge)
|
||||
{
|
||||
object value = null;
|
||||
if (knowledge != null)
|
||||
knowledge.Get(Path, out value);
|
||||
return (T)value;
|
||||
if (knowledge != null && knowledge.Get(Path, out var value))
|
||||
return (T)value;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,30 @@ using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial class BehaviorKnowledge
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if knowledge has a given goal (exact type match without base class check).
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> goal type.</typeparam>
|
||||
/// <returns>True if ahs a given goal, otherwise false.</returns>
|
||||
[Unmanaged]
|
||||
public bool HasGoal<T>()
|
||||
{
|
||||
return HasGoal(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the goal from the knowledge. Does nothing if goal of the given type doesn't exist in the knowledge.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> goal type.</typeparam>
|
||||
[Unmanaged]
|
||||
public void RemoveGoal<T>()
|
||||
{
|
||||
RemoveGoal(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
partial class BehaviorTreeRootNode
|
||||
{
|
||||
#if FLAX_EDITOR
|
||||
|
||||
@@ -86,8 +86,12 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTre
|
||||
API_FIELD(Attributes="EditorOrder(0), TypeReference(\"\", \"IsValidBlackboardType\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.TypeNameEditor\")")
|
||||
StringAnsi BlackboardType;
|
||||
|
||||
// List of full typenames of the behavior goals (structure or class).
|
||||
API_FIELD(Attributes="EditorOrder(10), Collection(OverrideEditorTypeName=\"FlaxEditor.CustomEditors.Editors.TypeNameEditor\")")
|
||||
Array<StringAnsi> GoalTypes;
|
||||
|
||||
// The target amount of the behavior logic updates per second.
|
||||
API_FIELD(Attributes="EditorOrder(10)")
|
||||
API_FIELD(Attributes="EditorOrder(100)")
|
||||
float UpdateFPS = 10.0f;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user