diff --git a/Core/BehaviourNode.cs b/Core/BehaviourNode.cs index dc654d3..216bc7a 100644 --- a/Core/BehaviourNode.cs +++ b/Core/BehaviourNode.cs @@ -162,7 +162,7 @@ protected GameObject Actor public bool IsComposite() { - return MaxChildCount() > 1; + return MaxChildCount() == int.MaxValue; } public bool IsDecorator() @@ -174,6 +174,11 @@ public bool IsTask() { return MaxChildCount() == 0; } + + public bool IsComparator() + { + return MaxChildCount() == 2; + } /// /// A summary description of the node. diff --git a/Core/BehaviourTree.cs b/Core/BehaviourTree.cs index 0bd004b..1bd3ddf 100644 --- a/Core/BehaviourTree.cs +++ b/Core/BehaviourTree.cs @@ -358,7 +358,24 @@ public static BehaviourTree Clone(BehaviourTree sourceTree) else if (copyNode.IsDecorator() && nodeSource.ChildCount() == 1) { var copyDecorator = copyNode as Decorator; - copyDecorator.SetChild(GetInstanceVersion(cloneBt, nodeSource.GetChildAt(0))); ; + copyDecorator.SetChild(GetInstanceVersion(cloneBt, nodeSource.GetChildAt(0))); + } + + else if (copyNode.IsComparator() && nodeSource.ChildCount() > 0) + { + var copyComparator = copyNode as Comparator; + switch (nodeSource.ChildCount()) + { + case 1: + copyComparator.SetChild(GetInstanceVersion(cloneBt, nodeSource.GetChildAt(0))); + break; + case 2: + copyComparator.SetChilds( + GetInstanceVersion(cloneBt, nodeSource.GetChildAt(0)), + GetInstanceVersion(cloneBt, nodeSource.GetChildAt(1)) + ); + break; + } } } @@ -406,6 +423,12 @@ private void ClearChildrenStructure(BehaviourNode node) var decorator = node as Decorator; decorator.SetChild(null); } + + else if (node.IsComparator()) + { + var decorator = node as Comparator; + decorator.SetChilds(null, null); + } } #if UNITY_EDITOR @@ -421,6 +444,15 @@ void AddBlackboardAsset() } } + public void OnValidate() + { + var hasNullable = allNodes.Any(node => node == null); + if (hasNullable) + { + allNodes = allNodes.Where(node => node != null).ToArray(); + } + } + [HideInInspector] public Vector2 panPosition = Vector2.zero; diff --git a/Core/Comparator.cs b/Core/Comparator.cs new file mode 100644 index 0000000..bb42a38 --- /dev/null +++ b/Core/Comparator.cs @@ -0,0 +1,70 @@ +using System; +using UnityEngine; + +namespace Bonsai.Core +{ + /// + /// The base class for two getter nodes. + /// + public abstract class Comparator : BehaviourNode + { + public sealed override int MaxChildCount() + { + return 2; + } + + public abstract void SetChild(object children); + public abstract void SetChilds(object childrenX, object childrenY); + } + + public abstract class Comparator : Comparator + { + [SerializeField, HideInInspector] private GetterNode childrenX; + [SerializeField, HideInInspector] private GetterNode childrenY; + + protected abstract bool Compare(T x, T y); + + public sealed override Status Run() + { + var x = childrenX.Get(); + var y = childrenY.Get(); + return Compare(x, y) ? Status.Success : Status.Failure; + } + + public sealed override void SetChild(object child) + { + if (childrenX == null) + { + childrenX = child as GetterNode; + return; + } + + childrenY = child as GetterNode; + } + + public sealed override void SetChilds(object childX, object childY) + { + childrenX = childX as GetterNode; + childrenY = childY as GetterNode; + } + + public sealed override int ChildCount() + { + var count = 0; + if (childrenX) count++; + if (childrenY) count++; + + return count; + } + + public sealed override BehaviourNode GetChildAt(int index) + { + return index switch + { + 0 => childrenX ? childrenX : childrenY, + 1 => childrenY, + _ => throw new ArgumentOutOfRangeException(nameof(index), index, null) + }; + } + } +} \ No newline at end of file diff --git a/Core/Comparator.cs.meta b/Core/Comparator.cs.meta new file mode 100644 index 0000000..7a1367a --- /dev/null +++ b/Core/Comparator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5e521880d62d4489bc10a586280bab8d +timeCreated: 1692880278 \ No newline at end of file diff --git a/Core/GetterNode.cs b/Core/GetterNode.cs new file mode 100644 index 0000000..f75c6aa --- /dev/null +++ b/Core/GetterNode.cs @@ -0,0 +1,33 @@ +namespace Bonsai.Core +{ + public abstract class GetterNode : BehaviourNode + { + public sealed override Status Run() + { + return Status.Success; + } + + public sealed override BehaviourNode GetChildAt(int index) + { + return null; + } + + public sealed override int ChildCount() + { + return 0; + } + + public sealed override int MaxChildCount() + { + return 0; + } + } + + /// + /// Base class for a node that takes some values. + /// + public abstract class GetterNode : GetterNode + { + public abstract T Get(); + } +} \ No newline at end of file diff --git a/Core/GetterNode.cs.meta b/Core/GetterNode.cs.meta new file mode 100644 index 0000000..ceaf8e5 --- /dev/null +++ b/Core/GetterNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e30ed613a3874862a1b97824e52e3073 +timeCreated: 1692880268 \ No newline at end of file diff --git a/Editor/BonsaiEditor.cs b/Editor/BonsaiEditor.cs index 6554f37..8f89fd3 100644 --- a/Editor/BonsaiEditor.cs +++ b/Editor/BonsaiEditor.cs @@ -478,6 +478,11 @@ public static Type CoreType(BehaviourNode behaviour) { return typeof(Decorator); } + + if (behaviour is Comparator) + { + return typeof(Comparator); + } return typeof(Task); } diff --git a/Editor/BonsaiNode.cs b/Editor/BonsaiNode.cs index c867086..74c0a62 100644 --- a/Editor/BonsaiNode.cs +++ b/Editor/BonsaiNode.cs @@ -177,6 +177,21 @@ public void SetParent(BonsaiNode newParent) newParent.OrphanChildren(); newParent.children.Add(this); } + + else if (newParent.behaviour is Comparator) + { + if (newParent.children.Count == 2) + { + var firstChildren = newParent.children[0]; + newParent.OrphanChildren(); + newParent.children.Add(firstChildren); + newParent.children.Add(this); + } + else + { + newParent.children.Add(this); + } + } // else: Tasks cannot have children added. } diff --git a/Editor/BonsaiPreferences.cs b/Editor/BonsaiPreferences.cs index 208d051..a203806 100644 --- a/Editor/BonsaiPreferences.cs +++ b/Editor/BonsaiPreferences.cs @@ -32,9 +32,11 @@ public class BonsaiPreferences : ScriptableObject [Header("Node Colors")] public Color compositeColor; public Color decoratorColor; + public Color comparatorColor; public Color conditionalColor; public Color serviceColor; public Color taskColor; + public Color getterColor; [Header("Status Colors")] public Color defaultNodeBackgroundColor; diff --git a/Editor/BonsaiSaver.cs b/Editor/BonsaiSaver.cs index b11b192..d262120 100644 --- a/Editor/BonsaiSaver.cs +++ b/Editor/BonsaiSaver.cs @@ -162,6 +162,7 @@ private void SaveTree(TreeMetaData meta, BonsaiCanvas canvas) // Set parent-child connections matching those in the canvas. Only consider decorators and composites. SetCompositeChildren(canvas); SetDecoratorChildren(canvas); + SetComparatorChildren(canvas); // Re-add nodes to tree. if (canvas.Root != null) @@ -200,6 +201,29 @@ private void SetDecoratorChildren(BonsaiCanvas canvas) decoratorBehaviour.SetChild(node.GetChildAt(0).Behaviour); } } + + private void SetComparatorChildren(BonsaiCanvas canvas) + { + IEnumerable comparatorNodes = canvas.Nodes + .Where(n => n.Behaviour.IsComparator() && n.ChildCount() > 0); + + foreach (BonsaiNode node in comparatorNodes) + { + var comparatorBehaviour = node.Behaviour as Comparator; + switch (node.Children.Count) + { + case 1: + comparatorBehaviour.SetChild(node.GetChildAt(0).Behaviour); + break; + case 2: + comparatorBehaviour.SetChilds( + node.GetChildAt(0).Behaviour, + node.GetChildAt(1).Behaviour + ); + break; + } + } + } private void AddNewNodeAssets( BehaviourTree treeAsset, diff --git a/Editor/Drawer.cs b/Editor/Drawer.cs index 5a6e727..75b0cb3 100644 --- a/Editor/Drawer.cs +++ b/Editor/Drawer.cs @@ -145,6 +145,16 @@ private static Color NodeTypeColor(BonsaiNode node) { return BonsaiPreferences.Instance.decoratorColor; } + + else if (node.Behaviour is Core.Comparator) + { + return BonsaiPreferences.Instance.comparatorColor; + } + + else if (node.Behaviour is Core.GetterNode) + { + return BonsaiPreferences.Instance.getterColor; + } return BonsaiPreferences.Instance.compositeColor; } diff --git a/Editor/Resources/DefaultBonsaiPreferences.asset b/Editor/Resources/DefaultBonsaiPreferences.asset index 6e2cb6f..b9a0183 100644 --- a/Editor/Resources/DefaultBonsaiPreferences.asset +++ b/Editor/Resources/DefaultBonsaiPreferences.asset @@ -20,15 +20,16 @@ MonoBehaviour: gridTexture: {fileID: 2800000, guid: 4ab4e605fe422164da65569c46a161d2, type: 3} failureSymbol: {fileID: 2800000, guid: 8db2b847ab6b6434c9da1e0cf721fd8e, type: 3} successSymbol: {fileID: 2800000, guid: 39cfff267dd954642b26f231c7a997ad, type: 3} - nodeBackgroundTexture: {fileID: 2800000, guid: 6c5508e99038cdf46bb29a177e477d6d, - type: 3} + nodeBackgroundTexture: {fileID: 2800000, guid: 6c5508e99038cdf46bb29a177e477d6d, type: 3} nodeGradient: {fileID: 2800000, guid: c453f51bbb9d4134d86679c771689df6, type: 3} portTexture: {fileID: 2800000, guid: dfda76d4e735e7a47bcf756d5d90c44d, type: 3} compositeColor: {r: 0.3773585, g: 0.3773585, b: 0.3773585, a: 1} decoratorColor: {r: 0.4553469, g: 0.38719293, b: 0.7264151, a: 1} + comparatorColor: {r: 0.33397114, g: 0.4972861, b: 0.745283, a: 1} conditionalColor: {r: 0.36663404, g: 0.7264151, b: 0.72088, a: 1} serviceColor: {r: 0.990566, g: 0.6200383, b: 0.13550198, a: 1} taskColor: {r: 0.59607846, g: 0.6431373, b: 0.5372549, a: 1} + getterColor: {r: 0.631871, g: 0.745283, b: 0.2988163, a: 1} defaultNodeBackgroundColor: {r: 0.2924528, g: 0.2924528, b: 0.2924528, a: 1} selectedColor: {r: 0.3443396, g: 0.62303853, b: 1, a: 1} runningColor: {r: 0.2971698, g: 1, b: 0.61701846, a: 1} diff --git a/Standard/Comparators.meta b/Standard/Comparators.meta new file mode 100644 index 0000000..0403f67 --- /dev/null +++ b/Standard/Comparators.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9c0609dbc7ff4aef88a6029f4080a542 +timeCreated: 1692954683 \ No newline at end of file diff --git a/Standard/Comparators/DistanceBetween.cs b/Standard/Comparators/DistanceBetween.cs new file mode 100644 index 0000000..c770af1 --- /dev/null +++ b/Standard/Comparators/DistanceBetween.cs @@ -0,0 +1,38 @@ +using System; +using Bonsai; +using Bonsai.Core; +using UnityEngine; + +namespace Bonsai.Standard +{ + /// + /// Compares the distance between two Vector3 points. + /// + [BonsaiNode("Comparators/", "DistanceBetween")] + public class DistanceBetween : Comparator + { + private enum Type + { + Equal, + Less, + Greater + }; + + [SerializeField] private Type _type = Type.Less; + [SerializeField] private float _value = 2; + + protected override bool Compare(Vector3 x, Vector3 y) + { + var distance = Vector3.Distance(x, y); + var isSuccess = _type switch + { + Type.Equal => Math.Abs(distance - _value) < 0.01f, + Type.Less => distance < _value, + Type.Greater => distance > _value, + _ => throw new ArgumentOutOfRangeException() + }; + + return isSuccess; + } + } +} \ No newline at end of file diff --git a/Standard/Comparators/DistanceBetween.cs.meta b/Standard/Comparators/DistanceBetween.cs.meta new file mode 100644 index 0000000..8d57bb9 --- /dev/null +++ b/Standard/Comparators/DistanceBetween.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 529fe917f03d4a098ab64f0062c696cb +timeCreated: 1692832217 \ No newline at end of file