花时间手撸了个行为树,现在通过做笔记的方式巩固一下学习的内容
总体来说,脚本 可以分为俩大类
为ScriptableObject(行为树)服务的脚本
为视图服务的Editor脚本
类图如下
行为树脚本 控制人物行为的主要脚本,主要也可以分为两种
行为树主干
BehaviourTreeRunner
:挂载到实例上,选择行为树实例,运行行为树
BehaviourTree
:控制行为树的行为。如:当根节点退出(即失败或成功)时结束整个行为树;创建、删除节点;添加、删除、获取子项,这些功能是为视图服务,在视图中调用这个方法就实现在更改视图的同时,更改ScriptableObject中的内容
节点
Node
:抽象类,一个进口,没有出口,后面将采用(1,0)的方式表达 ,所有节点都继承该类。存储一些数据:该实例节点当前状态、guid、该实例节点在视图中的位置坐标
ActionNode
:抽象类**(1,0)**。特点是没有出口
CompositeNode
:抽象类**(1,n)**。特点是多个出口
DecoratorNode
:抽象类**(1,1)**。特点是一个出口
RootNode
:根节点**(0,1)**。持续不断的运行子节点
以上的最基础的节点,其中Node
是所有节点的父类。ActionNode
、SequencerNode
、DecoratorNode
都是抽象类,只是为了区分接口有多少个出口,没有其他特别的作用。RootNode
是根节点,所有行为树都必有的一个,后续一系列的运行将根节点开始。
如果有想要实现的节点都可以继承三大最基本的抽象类,即ActionNode
、SequencerNode
、DecoratorNode
,如:
DebugLogNode
:继承ActionNode
**(1,0)**,打印字符串
WaitNode
:继承ActionNode
**(1,0)**,延时
SequencerNode
:继承SequencerNode
**(1,n)**,定序器,将按照出口节点的顺序执行
RepeatNode
:继承DecoratorNode
**(1,1)**,重复执行出口的节点
小结 行为树脚本中,每个脚本并不是特别复杂。但是需要注意两点
类与类之间继承的很频繁,需要搞清楚他们之间的关系
在运行时脚本直接的跳转,这一点使用文字不是特别好表达,最好的办法还是启动unity在程序中执行一遍,大致的方向是BehaviourTreeRunner
==>BehaviourTree
==>RootNode
==>根节点的子节点==>跟节点的子节点的子节点……,在程序运行时可以使用调试或者打印的方法查看程序运行的情况
视图脚本 将ScriptableObject中的内容展示到视图上,并且用户在操作视图中的内容时,ScriptableObject也会对应着改变
为了区分行为树中的节点与视图窗口中的节点,在本篇文章中我们将上面的行为树节点称为“节点”,将下方的视图脚本中的节点称为“节点元素”
BehaviourTreeEditor
行为树窗口三个作用:
OpenWindow
、CreateGUI
:创建一个窗口,用来显示行为树的视图
OnSelectionChange
方法:选择Hierarchy
和Project
中的行为树后,将行为树的内容展示到BehaviourTreeView
视图中
OnNodeSelectionChanged
方法:选择BehaviourTreeView
视图中的节点元素后,将该节点的Inspector
窗口的内容映射到BehaviourTreeEditor
行为树窗口左边的InspectorView
视图中
SplitView
继承TwoPaneSplitView
,将窗口分为两个可滑动的视图。如果你将所有脚本都放在了一个新的命名空间中时,创建完该脚本后会报错,原因是uxml脚本中找不到BehaviourTreeView
和InspectorView
这两个类,可以打开uxml文件,手动更改,在前面加上命名空间,具体可看最后的源码
InspectorView
节点信息视图摆放在行为树窗口的左侧,将选中的节点元素的Inspector
窗口中的内容映射到该视图上
UpdateSelection
:显示当前选中的节点元素的Inspector内容
BehaviourTreeView
行为树视图行为树窗口的主体部分,将选中的ScriptableObject行为树内容展示到视图中,并且在该视图中对各个节点元素的操作也会更改到ScriptableObject中存储起来
BehaviourTreeView
:设置视图背景,为了达到透明网格的背景效果,可以直接复制uss中的代码,具体可看最后的源码 ;定义一些基础功能:使该视图可缩放、拖动、选择、方框选择
PopulateView
:将ScriptableObject中的内容显示到视图中,并注册委托:在视图中发生更改时执行OnGraphViewChanged
方法
FindNodeView
:通过节点的guid查找与其对应的节点元素
GetCompatiblePorts
:重写父类方法。该方法能让视图中的节点相互连接,如果不写该方法将无法连接
OnGraphViewChanged
:在打开新的ScriptableObject时委托注册了该方法,该委托会在视图中有任何改变时调用该方法,功能如下
BuildContextualMenu
:重写父类方法。添加右键菜单功能
var types = TypeCache.GetTypesDerivedFrom<ActionNode>()
返回的是ActionNode
所有的派生类
CreateNode
:调用方法BehaviourTree.CreateNode
创建节点CreateNodeView
创建节点元素
CreateNodeView
:创建节点元素
NodeView
节点元素控制各个节点的基础信息
NodeView
、CreateinputPorts
、CreateOutputPorts
:创建节点元素时设置其标题、guid、位置、进口和出口的类型
SetPosition
:重写父类方法。在移动该节点元素后,将其新的位置信息存储到ScriptableObject节点中
OnSelected
:重写父类方法。定义一个委托,在窗口中选择该节点元素后,执行这个委托。每个节点元素在创建的时候就会注册这个委托了,但是注册这个委托的又是另外一个委托(我们称前一个委托为委托A,后一个委托为委托B),委托B是在创建行为树窗口时就注册了,注册实现的内容是:将该节点元素的节点Inspector信息映射到左侧的InspeView视图中。看起来比较绕,但其实就是为了保持代码的可维护性,没有让InspectorView的实例到出些,而为了让NodeView中的方法与BehaviourTreeEditor中的InspectorView实例联系起来而用了两个委托
小结 这样就成功手撸出了一个简单的行为树了,还有可以增添一些小功能,比如新增选择节点、判断节点;右键创建节点元素时,让其生成在鼠标所在的位置;让每个节点元素展示不同的外观方便区分;时节点元素之间的连线律动起来,实时展示当前怪物的行为树进行到哪一步了
到目前为止,也只是实现了一个框架,我们最终的目的是要将这个行为树功能套用到怪物身上的,所以还并没有结束
【Unity教程搬运】使用UI Builder、GraphView和脚本化对象创建行为树
源码 BehaviourTreeRunner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public class BehaviourTreeRunner : MonoBehaviour { public BehaviourTree tree; private void Start () { tree = tree.Clone(); } private void Update () { tree.Update(); } } }
BehaviourTree
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 using System.Collections;using System.Collections.Generic;using Unity.VisualScripting;using UnityEditor;using UnityEngine;namespace BehaviourTree { [CreateAssetMenu() ] public class BehaviourTree : ScriptableObject { public Node rootNode; public Node.State treeState = Node.State.Running; public List<Node> nodes = new List<Node>(); public Node.State Update () { if (rootNode.state == Node.State.Running) { treeState = rootNode.Update(); } return treeState; } public BehaviourTree Clone () { BehaviourTree tree = Instantiate(this ); tree.rootNode = tree.rootNode.Clone(); return tree; } #region 为视图服务的方法 public Node CreateNode (System.Type type ) { Node node = ScriptableObject.CreateInstance(type) as Node; node.name = type.Name; node.guid = GUID.Generate().ToString(); nodes.Add(node); AssetDatabase.AddObjectToAsset(node, this ); AssetDatabase.SaveAssets(); return node; } public void DeleteNode (Node node ) { nodes.Remove(node); AssetDatabase.RemoveObjectFromAsset(node); AssetDatabase.SaveAssets(); } public void AddChild (Node parent, Node child ) { RootNode rootNode = parent as RootNode; if (rootNode) { rootNode.child = child; } CompositeNode composite = parent as CompositeNode; if (composite) { composite.children.Add(child); } DecoratorNode decorator = parent as DecoratorNode; if (decorator) { decorator.child = child; } } public void RemoveChild (Node parent, Node child ) { RootNode rootNode = parent as RootNode; if (rootNode) { rootNode.child = null ; } CompositeNode composite = parent as CompositeNode; if (composite) { composite.children.Remove(child); } DecoratorNode decorator = parent as DecoratorNode; if (decorator) { decorator.child = null ; } } public List<Node> GetChildren (Node parent ) { List<Node> children = new List<Node>(); RootNode rootNode = parent as RootNode; if (rootNode && rootNode.child != null ) { children.Add(rootNode.child); } CompositeNode composite = parent as CompositeNode; if (composite) { children = composite.children; } DecoratorNode decorator = parent as DecoratorNode; if (decorator && decorator.child != null ) { children.Add(decorator.child); } return children; } #endregion } }
Node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 using UnityEngine;namespace BehaviourTree { public abstract class Node : ScriptableObject { public enum State { Running, Failure, Success } private bool started = false ; [HideInInspector ] public State state = State.Running; [HideInInspector ] public string guid; [HideInInspector ] public Vector2 position; public State Update () { if (!started) { OnStart(); started = true ; } state = OnUpdate(); if (state == State.Failure || state == State.Success) { OnStop(); started = false ; } return state; } public virtual Node Clone () { return Instantiate(this ); } protected abstract void OnStart () ; protected abstract void OnStop () ; protected abstract State OnUpdate () ; } }
ActionNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public abstract class ActionNode : Node { } }
CompositeNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public abstract class CompositeNode : Node { public List<Node> children = new List<Node>(); public override Node Clone () { CompositeNode node = Instantiate(this ); node.children = children.ConvertAll(c => c.Clone()); return node; } } }
DecoratorNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using System.Collections;using System.Collections.Generic;using UnityEngine;using static Unity.VisualScripting.Metadata;namespace BehaviourTree { public abstract class DecoratorNode : Node { public Node child; public override Node Clone () { DecoratorNode node = Instantiate(this ); node.child = child.Clone(); return node; } } }
RootNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public class RootNode : Node { [HideInInspector ] public Node child; protected override void OnStart () { } protected override void OnStop () { } protected override State OnUpdate () { return child.Update(); } public override Node Clone () { RootNode node = Instantiate(this ); node.child = child.Clone(); return node; } } }
DebugLogNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public class DebugLogNode : ActionNode { public string startMessage; public string stopMessage; public string updateMessage; protected override void OnStart () { Debug.Log($"OnStart{startMessage} " ); } protected override void OnStop () { Debug.Log($"OnStop{stopMessage} " ); } protected override State OnUpdate () { Debug.Log($"OnUpdate{updateMessage} " ); return State.Success; } } }
WaitNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public class WaitNode : ActionNode { [SerializeField ] private float duration = 1 ; float startTime; protected override void OnStart () { startTime = Time.time; } protected override void OnStop () { } protected override State OnUpdate () { if (Time.time - startTime > duration) { return State.Success; } return State.Running; } } }
SequencerNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 using UnityEngine;namespace BehaviourTree { public class SequencerNode : CompositeNode { private int current; protected override void OnStart () { current = 0 ; } protected override void OnStop () { } protected override State OnUpdate () { var child = children[current]; switch (child.Update()) { case State.Running: return State.Running; case State.Failure: return State.Failure; case State.Success: current++; break ; default : break ; } return current == children.Count ? State.Success : State.Running; } } }
RepeatNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System.Collections;using System.Collections.Generic;using UnityEngine;namespace BehaviourTree { public class RepeatNode : DecoratorNode { protected override void OnStart () { } protected override void OnStop () { } protected override State OnUpdate () { child.Update(); return Node.State.Running; } } }
BehaviourTreeEditor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System;using UnityEditor;using UnityEngine;using UnityEngine.UIElements;namespace BehaviourTree { public class BehaviourTreeEditor : EditorWindow { BehaviourTreeView treeView; InspectorView inspectorView; [SerializeField ] private VisualTreeAsset m_VisualTreeAsset = default ; [MenuItem("BehaviourTreeEditor/Editor ..." ) ] public static void OpenWindow () { BehaviourTreeEditor wnd = GetWindow<BehaviourTreeEditor>(); wnd.titleContent = new GUIContent("BehaviourTreeEditor" ); } public void CreateGUI () { VisualElement root = rootVisualElement; m_VisualTreeAsset.CloneTree(root); var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/BehaviourTree/BehaviourTreeEditor.uss" ); root.styleSheets.Add(styleSheet); treeView = root.Q<BehaviourTreeView>(); inspectorView = root.Q<InspectorView>(); treeView.OnNodeSelected = OnNodeSelectionChanged; OnSelectionChange(); } private void OnSelectionChange () { BehaviourTree tree = Selection.activeObject as BehaviourTree; if (tree && AssetDatabase.CanOpenAssetInEditor(tree.GetInstanceID())) { treeView.PopulateView(tree); } } private void OnNodeSelectionChanged (NodeView nodeView ) { inspectorView.UpdateSelection(nodeView); } } }
SplitView
1 2 3 4 5 6 7 8 9 10 11 using UnityEngine.UIElements;public class SplitView : TwoPaneSplitView { public new class UxmlFactory : UxmlFactory <SplitView , TwoPaneSplitView.UxmlTraits > { } }
InspectorView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using UnityEditor;using UnityEngine.UIElements;namespace BehaviourTree { public class InspectorView : VisualElement { public new class UxmlFactory : UxmlFactory <InspectorView , VisualElement.UxmlTraits > { } private Editor editor; public InspectorView () { } internal void UpdateSelection (NodeView nodeView ) { Clear(); UnityEngine.Object.DestroyImmediate(editor); editor = Editor.CreateEditor(nodeView.node); IMGUIContainer container = new IMGUIContainer(() => { editor.OnInspectorGUI(); }); Add(container); } } }
BehaviourTreeView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 using UnityEditor;using UnityEngine.UIElements;using UnityEditor.Experimental.GraphView;using System.Linq;using System.Collections.Generic;using UnityEngine;using System;namespace BehaviourTree { public class BehaviourTreeView : GraphView { public Action<NodeView> OnNodeSelected; public new class UxmlFactory : UxmlFactory <BehaviourTreeView , GraphView.UxmlTraits > { } BehaviourTree tree; public BehaviourTreeView () { Insert(0 , new GridBackground()); this .AddManipulator(new ContentZoomer()); this .AddManipulator(new ContentDragger()); this .AddManipulator(new SelectionDragger()); this .AddManipulator(new RectangleSelector()); var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/BehaviourTree/BehaviourTreeEditor.uss" ); styleSheets.Add(styleSheet); } internal void PopulateView (BehaviourTree tree ) { this .tree = tree; graphViewChanged -= OnGraphViewChanged; DeleteElements(graphElements); graphViewChanged += OnGraphViewChanged; if (tree.rootNode == null ) { tree.rootNode = tree.CreateNode(typeof (RootNode)) as RootNode; EditorUtility.SetDirty(tree); AssetDatabase.SaveAssets(); } tree.nodes.ForEach(n => CreateNodeView(n)); tree.nodes.ForEach(n => { var children = tree.GetChildren(n); NodeView parentView = FindNodeView(n); children.ForEach(c => { NodeView childView = FindNodeView(c); Edge edge = parentView.output.ConnectTo(childView.input); AddElement(edge); }); }); } private NodeView FindNodeView (Node node ) { return GetNodeByGuid(node.guid) as NodeView; } public override List<Port> GetCompatiblePorts (Port startPort, NodeAdapter nodeAdapter ) { return ports.ToList().Where(endPort => endPort.direction != startPort.direction && endPort.node != startPort.node).ToList(); } private GraphViewChange OnGraphViewChanged (GraphViewChange graphViewChange ) { if (graphViewChange.elementsToRemove != null ) { graphViewChange.elementsToRemove.ForEach(elem => { NodeView nodeView = elem as NodeView; if (nodeView != null ) { tree.DeleteNode(nodeView.node); } Edge edge = elem as Edge; if (edge != null ) { NodeView parentView = edge.output.node as NodeView; NodeView childView = edge.input.node as NodeView; tree.RemoveChild(parentView.node, childView.node); } }); } if (graphViewChange.edgesToCreate != null ) { graphViewChange.edgesToCreate.ForEach(edge => { NodeView parentView = edge.output.node as NodeView; NodeView childView = edge.input.node as NodeView; tree.AddChild(parentView.node, childView.node); }); } return graphViewChange; } public override void BuildContextualMenu (ContextualMenuPopulateEvent evt ) { { var types = TypeCache.GetTypesDerivedFrom<ActionNode>(); foreach (var type in types) { evt.menu.AppendAction($"[{type.BaseType.Name} ] {type.Name} " , (a) => CreateNode(type)); } } { var types = TypeCache.GetTypesDerivedFrom<CompositeNode>(); foreach (var type in types) { evt.menu.AppendAction($"[{type.BaseType.Name} ] {type.Name} " , (a) => CreateNode(type)); } } { var types = TypeCache.GetTypesDerivedFrom<DecoratorNode>(); foreach (var type in types) { evt.menu.AppendAction($"[{type.BaseType.Name} ] {type.Name} " , (a) => CreateNode(type)); } } } private void CreateNode (System.Type type ) { Node node = tree.CreateNode(type); CreateNodeView(node); } private void CreateNodeView (Node node ) { NodeView nodeView = new NodeView(node); nodeView.OnNodeSelected = OnNodeSelected; AddElement(nodeView); } } }
NodeView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 using UnityEngine;using UnityEditor.Experimental.GraphView;using System;namespace BehaviourTree { public class NodeView : UnityEditor.Experimental.GraphView.Node { public Action<NodeView> OnNodeSelected; public Node node; public Port input; public Port output; public NodeView (Node node ) { this .node = node; this .title = node.name; this .viewDataKey = node.guid; style.left = node.position.x; style.top = node.position.y; CreateInputPorts(); CreateOutputPorts(); } private void CreateInputPorts () { if (node is ActionNode) { input = InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof (bool )); } else if (node is CompositeNode) { input = InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof (bool )); } else if (node is DecoratorNode) { input = InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof (bool )); } else if (node is RootNode) { } if (input != null ) { input.portName = "" ; inputContainer.Add(input); } } private void CreateOutputPorts () { if (node is ActionNode) { } else if (node is CompositeNode) { output = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof (bool )); } else if (node is DecoratorNode) { output = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof (bool )); } else if (node is RootNode) { output = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof (bool )); } if (output != null ) { output.portName = "" ; outputContainer.Add(output); } } public override void SetPosition (Rect newPos ) { base .SetPosition(newPos); node.position.x = newPos.xMin; node.position.y = newPos.yMin; } public override void OnSelected () { base .OnSelected(); if (OnNodeSelected != null ) { OnNodeSelected.Invoke(this ); } } } }
BehaviourTreeEditor.uxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True"> <Style src="project://database/Assets/Editor/BehaviourTreeEditor.uss?fileID=7433441132597879392&guid=29b7713829d781d489b826020f33285b&type=3#BehaviourTree.BehaviourTreeEditor" /> <uie:Toolbar> <uie:ToolbarMenu tabindex="-1" parse-escape-sequences="true" display-tooltip-when-elided="true" text="Assets" /> </uie:Toolbar> <SplitView fixed-pane-initial-dimension="300"> <ui:VisualElement name="left-panel" style="flex-grow: 1; -unity-slice-scale: 0;"> <ui:Label tabindex="-1" text="Inspector" parse-escape-sequences="true" display-tooltip-when-elided="true" style="-unity-background-image-tint-color: rgb(60, 60, 60); background-color: rgb(37, 37, 37); color: rgb(196, 196, 196); -unity-slice-scale: 1px; flex-shrink: 0; flex-grow: 0; font-size: 14px; -unity-text-align: upper-left; white-space: nowrap; text-overflow: clip; -unity-background-scale-mode: stretch-to-fill; border-top-width: 0; -unity-text-outline-width: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-left: 0; padding-bottom: 0; border-left-width: 5px;" /> <BehaviourTree.InspectorView style="flex-grow: 1; background-color: rgb(56, 56, 56);" /> </ui:VisualElement> <ui:VisualElement name="right-panel" style="flex-grow: 1;"> <ui:Label tabindex="-1" text="TreeView" parse-escape-sequences="true" display-tooltip-when-elided="true" style="background-color: rgb(37, 37, 37); color: rgb(196, 196, 196); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-left-width: 5px;" /> <BehaviourTree.BehaviourTreeView focusable="true" style="flex-grow: 1;" /> </ui:VisualElement> </SplitView> </ui:UXML>
BehaviourTreeEditor.uss
1 2 3 4 5 6 GridBackground { --grid-background-color: rgb(40 , 40 , 40 ); --line-color: rgba(193 , 196 , 192 , 0.1 ); --thick-line-color: rgba(193 , 196 , 192 , 0.1 ); --spacing: 15 ; }