diff --git a/Assets/Samples/BehaviorTreeList.cs b/Assets/Samples/BehaviorTreeList.cs new file mode 100644 index 0000000..a5db252 --- /dev/null +++ b/Assets/Samples/BehaviorTreeList.cs @@ -0,0 +1,30 @@ +using CleverCrow.Fluid.BTs.Trees; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace CleverCrow.Fluid.BTs.Samples +{ + public class BehaviorTreeList : MonoBehaviour + { + protected void Awake() + { + for (int i = 0; i < 3; i++) + { + trees.Add(new NestedTree() + { + tree = new BehaviorTreeBuilder(gameObject).Name($"Tree{i}").Sequence() + .WaitTime(1) + .ReturnSuccess() + .End().Build() + }); + } + } + public List trees = new List(); + } + [Serializable] + public class NestedTree + { + public BehaviorTree tree; + } +} diff --git a/Assets/Samples/BehaviorTreeList.cs.meta b/Assets/Samples/BehaviorTreeList.cs.meta new file mode 100644 index 0000000..46a0ce0 --- /dev/null +++ b/Assets/Samples/BehaviorTreeList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d249f0b66a31f2c41b4014c03856ae10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Samples/TreeSample.unity b/Assets/Samples/TreeSample.unity index ac13b2e..f756e95 100644 --- a/Assets/Samples/TreeSample.unity +++ b/Assets/Samples/TreeSample.unity @@ -38,12 +38,11 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 12 m_GIWorkflowMode: 1 m_GISettings: serializedVersion: 2 @@ -98,13 +97,13 @@ LightmapSettings: m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} - m_UseShadowmask: 1 + m_LightingSettings: {fileID: 1443705540} --- !u!196 &4 NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +116,9 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} @@ -197,6 +198,7 @@ Light: m_UseColorTemperature: 0 m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 --- !u!4 &294120168 @@ -206,12 +208,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 294120166} + serializedVersion: 2 m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} --- !u!1 &322793818 GameObject: @@ -253,9 +256,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -289,12 +300,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 322793818} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &343368638 GameObject: @@ -333,12 +345,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 343368638} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &533478364 GameObject: @@ -369,7 +382,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 035df0b7568e1804f9b2f6dfb0bd0645, type: 3} m_Name: m_EditorClassIdentifier: - _toggle: 0 + _isTaskSuccess: 0 --- !u!4 &533478366 Transform: m_ObjectHideFlags: 0 @@ -377,10 +390,129 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 533478364} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!850595691 &1443705540 +LightingSettings: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Settings.lighting + serializedVersion: 6 + m_GIWorkflowMode: 1 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 1 + m_RealtimeEnvironmentLighting: 1 + m_BounceScale: 1 + m_AlbedoBoost: 1 + m_IndirectOutputScale: 1 + m_UsingShadowmask: 1 + m_BakeBackend: 1 + m_LightmapMaxSize: 1024 + m_BakeResolution: 40 + m_Padding: 2 + m_LightmapCompression: 3 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAO: 0 + m_MixedBakeMode: 2 + m_LightmapsBakeMode: 1 + m_FilterMode: 1 + m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_RealtimeResolution: 2 + m_ForceWhiteAlbedo: 0 + m_ForceUpdates: 0 + m_FinalGather: 0 + m_FinalGatherRayCount: 256 + m_FinalGatherFiltering: 1 + m_PVRCulling: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_LightProbeSampleCountMultiplier: 4 + m_PVRBounces: 2 + m_PVRMinBounces: 2 + m_PVREnvironmentImportanceSampling: 1 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_PVRTiledBaking: 0 + m_NumRaysToShootPerTexel: -1 + m_RespectSceneVisibilityWhenBakingGI: 0 +--- !u!1 &1786752064 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1786752065} + - component: {fileID: 1786752066} + m_Layer: 0 + m_Name: BehaviorTreeList + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1786752065 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1786752064} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1786752066 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1786752064} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d249f0b66a31f2c41b4014c03856ae10, type: 3} + m_Name: + m_EditorClassIdentifier: + trees: [] +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 322793821} + - {fileID: 294120168} + - {fileID: 343368640} + - {fileID: 533478366} + - {fileID: 1786752065} diff --git a/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/BehaviorTreeDrawer.cs b/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/BehaviorTreeDrawer.cs index 2cd5e14..762d11d 100644 --- a/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/BehaviorTreeDrawer.cs +++ b/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/BehaviorTreeDrawer.cs @@ -1,21 +1,91 @@ +using System; +using System.Collections; +using System.Reflection; using UnityEditor; using UnityEngine; -namespace CleverCrow.Fluid.BTs.Trees.Editors { +namespace CleverCrow.Fluid.BTs.Trees.Editors +{ [CustomPropertyDrawer(typeof(BehaviorTree))] - public class BehaviorTreeDrawer : PropertyDrawer { - public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { + public class BehaviorTreeDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { EditorGUI.BeginProperty(position, label, property); - + position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); GUI.enabled = Application.isPlaying; - if (GUI.Button(position, "View Tree")) { - var tree = fieldInfo.GetValue(property.serializedObject.targetObject) as IBehaviorTree; - BehaviorTreeWindow.ShowTree(tree, tree.Name ?? property.displayName); + if (GUI.Button(position, "View Tree")) + { + object value = GetTargetObjectOfProperty(property); + if (value is IBehaviorTree tree) + { + BehaviorTreeWindow.ShowTree(tree, tree.Name ?? property.displayName); + } + else + { + Debug.LogWarning($"BehaviorTreeDrawer: cannot resolve runtime value for property '{property.propertyPath}'."); + } } GUI.enabled = true; - + EditorGUI.EndProperty(); } + public object GetTargetObjectOfProperty(SerializedProperty prop) + { + if (prop == null) return null; + + object obj = prop.serializedObject.targetObject; + if (obj == null) return null; + + string path = prop.propertyPath.Replace(".Array.data[", "["); + string[] elements = path.Split('.'); + + foreach (string element in elements) + { + if (element.Contains("[")) + { + int indexStart = element.IndexOf("[", StringComparison.Ordinal); + string memberName = element.Substring(0, indexStart); + string indexStr = element.Substring(indexStart) + .Trim('[', ']'); + + obj = GetMemberValue(obj, memberName); + + if (obj is IList list && int.TryParse(indexStr, out int index) && index >= 0 && index < list.Count) + { + obj = list[index]; + } + else + { + return null; + } + } + else + { + obj = GetMemberValue(obj, element); + } + + if (obj == null) + return null; + } + + return obj; + } + + private object GetMemberValue(object source, string name) + { + if (source == null) + return null; + var type = source.GetType(); + + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + + FieldInfo field = type.GetField(name, flags); + if (field != null) + return field.GetValue(source); + + return null; + } } } diff --git a/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/NodePrintController.cs b/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/NodePrintController.cs index cf3c5a4..a4a3ae3 100644 --- a/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/NodePrintController.cs +++ b/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/NodePrintController.cs @@ -3,8 +3,10 @@ using UnityEditor; using UnityEngine; -namespace CleverCrow.Fluid.BTs.Trees.Editors { - public class NodePrintController { +namespace CleverCrow.Fluid.BTs.Trees.Editors +{ + public class NodePrintController + { private readonly VisualTask _node; private readonly IGraphBox _box; private readonly IGraphBox _divider; @@ -19,25 +21,29 @@ public class NodePrintController { private static GuiStyleCollection Styles => BehaviorTreePrinter.SharedStyles; private static Color LineColor => EditorGUIUtility.isProSkin ? Color.white : Color.black; - public NodePrintController (VisualTask node) { + public NodePrintController(VisualTask node) + { _node = node; _box = node.Box; _divider = node.Divider; _iconMain = new TextureLoader(_node.Task.IconPath); } - public void Print (bool taskIsActive) { + public void Print(bool taskIsActive) + { if (!(_node.Task is TaskRoot)) PaintVerticalTop(); PaintBody(); - if (_node.Children.Count > 0) { + if (_node.Children.Count > 0) + { PaintDivider(); PaintVerticalBottom(); } } - private void PaintBody () { + private void PaintBody() + { var prevBackgroundColor = GUI.backgroundColor; var rect = new Rect( @@ -46,13 +52,16 @@ private void PaintBody () { _box.Width - _box.PaddingX, _box.Height - _box.PaddingY); - if (_node.Task.HasBeenActive) { + if (_node.Task.HasBeenActive) + { GUI.backgroundColor = _faders.BackgroundFader.CurrentColor; GUI.Box(rect, GUIContent.none, Styles.BoxActive.Style); GUI.backgroundColor = prevBackgroundColor; PrintLastStatus(rect); - } else { + } + else + { GUI.Box(rect, GUIContent.none, Styles.BoxInactive.Style); } @@ -62,7 +71,8 @@ private void PaintBody () { GUI.Label(rect, _node.Task.Name, Styles.Title); } - private void PrintLastStatus (Rect rect) { + private void PrintLastStatus(Rect rect) + { const float sidePadding = 1.5f; var icon = BehaviorTreePrinter.StatusIcons.GetIcon(_node.Task.LastStatus); @@ -74,7 +84,8 @@ private void PrintLastStatus (Rect rect) { new Color(1, 1, 1, 0.7f)); } - private void PrintIcon () { + private void PrintIcon() + { const float iconWidth = 35; const float iconHeight = 35; _iconMain.Paint( @@ -86,12 +97,17 @@ private void PrintIcon () { _faders.MainIconFader.CurrentColor); } - private void PaintDivider () { + private void PaintDivider() + { const int graphicSizeIncrease = 5; - if (_dividerGraphic == null) { + int width = (int)_divider.Width + graphicSizeIncrease; + if (_dividerGraphic == null) + { + if (width > SystemInfo.maxTextureSize) + width = SystemInfo.maxTextureSize; _dividerGraphic = CreateTexture( - (int)_divider.Width + graphicSizeIncrease, + width > SystemInfo.maxTextureSize ? SystemInfo.maxTextureSize : width, 1, LineColor); } @@ -103,11 +119,27 @@ private void PaintDivider () { // @NOTE I have no clue why 3 works here... 3); - GUI.Label(position, _dividerGraphic); + if (width > SystemInfo.maxTextureSize) + { + int count = width / SystemInfo.maxTextureSize; + int remain = SystemInfo.maxTextureSize - (width - count * SystemInfo.maxTextureSize); + for (int i = 0; i < count; i++) + { + GUI.Label(position, _dividerGraphic); + position.x = position.x + SystemInfo.maxTextureSize; + } + int overlay = SystemInfo.maxTextureSize - remain; + position.x = position.x - overlay; + GUI.Label(position, _dividerGraphic); + } + else + GUI.Label(position, _dividerGraphic); } - private void PaintVerticalBottom () { - if (_verticalBottom == null) { + private void PaintVerticalBottom() + { + if (_verticalBottom == null) + { _verticalBottom = CreateTexture(1, (int)_box.PaddingY, LineColor); } @@ -120,8 +152,10 @@ private void PaintVerticalBottom () { GUI.Label(position, _verticalBottom); } - private void PaintVerticalTop () { - if (_verticalTop == null) { + private void PaintVerticalTop() + { + if (_verticalTop == null) + { _verticalTop = CreateTexture(1, Mathf.RoundToInt(_box.PaddingY / 2), LineColor); } @@ -134,7 +168,8 @@ private void PaintVerticalTop () { GUI.Label(position, _verticalTop); } - private static Texture2D CreateTexture (int width, int height, Color color) { + private static Texture2D CreateTexture(int width, int height, Color color) + { var texture = new Texture2D(width, height, TextureFormat.ARGB32, false); texture.SetPixels(Enumerable.Repeat(color, width * height).ToArray()); texture.Apply(); @@ -142,7 +177,8 @@ private static Texture2D CreateTexture (int width, int height, Color color) { return texture; } - public void SyncFade (bool taskIsActive) { + public void SyncFade(bool taskIsActive) + { _faders.Update(taskIsActive); } } diff --git a/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/VisualTask.cs b/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/VisualTask.cs index d839410..316ac1a 100644 --- a/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/VisualTask.cs +++ b/Assets/com.fluid.behavior-tree/Editor/BehaviorTree/Printer/VisualTask.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using CleverCrow.Fluid.BTs.Tasks; -namespace CleverCrow.Fluid.BTs.Trees.Editors { - public class VisualTask { +namespace CleverCrow.Fluid.BTs.Trees.Editors +{ + public class VisualTask + { private readonly List _children = new List(); private readonly NodePrintController _printer; private bool _taskActive; @@ -17,7 +19,8 @@ public class VisualTask { public IGraphBox Divider { get; private set; } public float DividerLeftOffset { get; private set; } - public VisualTask (ITask task, IGraphContainer parentContainer) { + public VisualTask(ITask task, IGraphContainer parentContainer) + { Task = task; BindTask(); @@ -25,9 +28,11 @@ public VisualTask (ITask task, IGraphContainer parentContainer) { AddBox(container); - if (task.Children != null) { + if (task.Children != null) + { var childContainer = new GraphContainerHorizontal(); - foreach (var child in task.Children) { + foreach (var child in task.Children) + { _children.Add(new VisualTask(child, childContainer)); } @@ -40,29 +45,35 @@ public VisualTask (ITask task, IGraphContainer parentContainer) { _printer = new NodePrintController(this); } - private void BindTask () { + private void BindTask() + { Task.EditorUtils.EventActive.AddListener(UpdateTaskActiveStatus); } - public void RecursiveTaskUnbind () { + public void RecursiveTaskUnbind() + { Task.EditorUtils.EventActive.RemoveListener(UpdateTaskActiveStatus); - foreach (var child in _children) { + foreach (var child in _children) + { child.RecursiveTaskUnbind(); } } - private void UpdateTaskActiveStatus () { + private void UpdateTaskActiveStatus() + { _taskActive = true; } - private void AddDivider (IGraphContainer parent, IGraphContainer children) { - Divider = new GraphBox { + private void AddDivider(IGraphContainer parent, IGraphContainer children) + { + Divider = new GraphBox + { SkipCentering = true, }; - DividerLeftOffset = children.ChildContainers[0].Width / 2; - var dividerRightOffset = children.ChildContainers[children.ChildContainers.Count - 1].Width / 2; + DividerLeftOffset = children.ChildContainers.Count > 0 ? children.ChildContainers[0].Width / 2 : 0; + var dividerRightOffset = children.ChildContainers.Count > 0 ? children.ChildContainers[children.ChildContainers.Count - 1].Width / 2 : 0; var width = children.Width - DividerLeftOffset - dividerRightOffset; Divider.SetSize(width, 1); @@ -70,26 +81,31 @@ private void AddDivider (IGraphContainer parent, IGraphContainer children) { parent.AddBox(Divider); } - private void AddBox (IGraphContainer parent) { + private void AddBox(IGraphContainer parent) + { Box = new GraphBox(); Box.SetSize(Width, Height); Box.SetPadding(10, 10); parent.AddBox(Box); } - public void Print () { + public void Print() + { _printer.Print(_taskActive); - foreach (var child in _children) { + foreach (var child in _children) + { child.Print(); } } - public void UpdateFaders () { + public void UpdateFaders() + { _printer.SyncFade(_taskActive); _taskActive = false; - foreach (var child in _children) { + foreach (var child in _children) + { child.UpdateFaders(); } }