KiwiCoder의 BehaviorTree Editor를 참고하며 만들고 있는 중이다.
[CreateAssetMenu()]
public class BehaviorTree : ScriptableObject
{
public Node rootNode;
public Node.E_NodeState treeState = Node.E_NodeState.Running;
public List<Node> nodes = new List<Node>();
public Node.E_NodeState Update()
{
if (rootNode.state == Node.E_NodeState.Running)
{
treeState = rootNode.Update();
}
return treeState;
}
// 노드 객체 생성
public Node CreateNode(System.Type type)
{
Node node = ScriptableObject.CreateInstance(type) as Node;
if (node == null)
Debug.LogError("node CreateInstance failed");
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 class BehaviorTreeEditor : EditorWindow
{
private BehaviorTreeView _treeView;
private InspectorView _inspectorView;
[MenuItem("BehaviorTree/BehaviorTree Editor")]
public static void OpenWindow()
{
BehaviorTreeEditor wnd = GetWindow<BehaviorTreeEditor>();
wnd.titleContent = new GUIContent("BehaviorTreeEditor");
}
public void CreateGUI()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
// Instantiate UXML
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/__Scripts__/__Core__/Behavior Tree/Editor/BehaviorTreeEditor.uxml");
visualTree.CloneTree(root);
// Allocate StyleSheet
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/__Scripts__/__Core__/Behavior Tree/Editor/BehaviorTreeEditor.uss");
root.styleSheets.Add(styleSheet);
_treeView = root.Q<BehaviorTreeView>();
_inspectorView = root.Q<InspectorView>();
OnSelectionChange();
}
// 선택시 변경 이벤트 함수
private void OnSelectionChange()
{
BehaviorTree tree = Selection.activeObject as BehaviorTree;
if (tree)
{
_treeView.PopulateView(tree);
}
}
}
public class BehaviorTreeView : GraphView
{
private BehaviorTree _tree;
public new class UxmlFactory : UxmlFactory<BehaviorTreeView, GraphView.UxmlTraits> { }
public BehaviorTreeView()
{
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/__Scripts__/__Core__/Behavior Tree/Editor/BehaviorTreeEditor.uss");
styleSheets.Add(styleSheet); // 스타일 시트 직접참조
}
internal void PopulateView(BehaviorTree tree)
{
_tree = tree;
graphViewChanged -= OnGraphViewChanged;
DeleteElements(graphElements); // 두 개 이상 생성 대비 삭제
graphViewChanged += OnGraphViewChanged;
_tree.nodes.ForEach(n => CreateNodeView(n)); // 다시 생성
}
// 뷰 체인지 이벤트 함수
private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
{
if (graphViewChange.elementsToRemove != null)
{
graphViewChange.elementsToRemove.ForEach(element =>
{
if (element is NodeView nodeView)
_tree.DeleteNode(nodeView.node);
});
}
return graphViewChange;
}
// 메뉴 재정의
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
{
var types = TypeCache.GetTypesDerivedFrom<LeafAction>();
foreach (var type in types)
{
evt.menu.AppendAction($"[{type.BaseType.Name}] {type.Name}", (t) => CreateNode(type));
}
}
{
var types = TypeCache.GetTypesDerivedFrom<Composite>();
foreach (var type in types)
{
evt.menu.AppendAction($"[{type.BaseType.Name}] {type.Name}", (t) => CreateNode(type));
}
}
{
var types = TypeCache.GetTypesDerivedFrom<Decorator>();
foreach (var type in types)
{
evt.menu.AppendAction($"[{type.BaseType.Name}] {type.Name}", (t) => 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);
AddElement(nodeView);
}
}
public class InspectorView : VisualElement
{
public new class UxmlFactory : UxmlFactory<InspectorView, VisualElement.UxmlTraits> {}
public InspectorView()
{
}
}
public class NodeView : UnityEditor.Experimental.GraphView.Node
{
public Node node;
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;
}
public override void SetPosition(Rect newPos)
{
base.SetPosition(newPos);
node.position.x = newPos.xMin;
node.position.y = newPos.yMin;
}
}
public class SplitView : TwoPaneSplitView
{
public new class UxmlFactory : UxmlFactory<SplitView, TwoPaneSplitView.UxmlTraits> { }
}
GridBackground
{
--gird-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;
}