Unity Graph View打造图形化对话编辑系统(三)——UIBuilder构造Editor Window
目录
视频效果演示
Graph View打造图形化对话编辑系统效果展示
最终源码先附在此
先得写几个类
编辑窗口分成左右两部分,左边是属性窗口(Inspector),当用户选择了一个节点时,可以在这里编辑它。右边是对话树的编辑窗口,编辑工作主要在这里进行。那么我们需要以下几个类:
- HorizontalSplitView —— 水平方向分割窗口视图类,继承自TwoPaneSplitView
- DialogueTreeView —— 对话树视图类,它是节点和连线的容器,继承自GraphView
- DialogueNodeView —— 节点视图类,它负责渲染节点,是接口的容器,继承自Node
- InspectorView —— 节点属性编辑面板,继承自VisualElement
这几个类,因为只需要运行在编辑模式中,所以,最好把他们放到Editor文件夹下面,这样可以确保在发布时不会将他们打包进工程。
以DialogueTreeView为例:
public class DialogGraphView : GraphView
{
public new class UxmlFactory : UxmlFactory<DialogGraphView, UxmlTraits> { }
// 对话树的Scriptable数据
public DialogTree treeData = null;
public DialogGraphView()
{
// 增加格子背景、内容缩放、拖动、框选组件。
Insert(0, new GridBackground());
this.AddManipulator(new ContentZoomer());
this.AddManipulator(new ContentDragger());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());
// 加载uss风格文件,类似HTML里的css
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Dialog Graph/Editor/EditorWindow/DialogGraphWindow.uss");
styleSheets.Add(styleSheet);
}
}
用UIBuilder搭建如下的UI(注意左侧层级关系):
需要注意的一个小坑:
InspectorView和DialogueTreeView,不要直接隶属面板容器,或者说他们不要有兄弟面板,否则就会导致鼠标位置响应不准确。要先给他套上一个VisualElement。这UIBuilder目前还是preview状态,尚未正式发布,所有还有一些各种各样的小问题。
默认情况下,打开我们自己定义的编辑窗口,是通过自定义的菜单来实现的,但是,我们想让我们的编辑窗口关联到Scriptable数据上,我们想让用户双击DialogTree时,能够自动打开编辑窗口,然后对该DialogTree进行编辑,所以,我们需要修改一下窗口的代码:
public class DialogGraphWindow : EditorWindow
{
/*
这是之前的打开方式,我们不再需要它了
[MenuItem("Dialog Graph/Editor Window")]
public static void OpenEditorWindow()
{
DialogGraphWindow wnd = GetWindow<DialogGraphWindow>();
wnd.titleContent = new GUIContent("DialogGraphWindow");
}
*/
// 当双击Assets时,如果双击的是一颗对话树,那我们就编辑它
[OnOpenAsset(1)]
public static bool OnOpenAssets(int id, int line)
{
if( EditorUtility.InstanceIDToObject(id) is DialogTree tree )
{
DialogGraphWindow wnd = GetWindow<DialogGraphWindow>();
wnd.titleContent = new GUIContent($"图形化对话系统");
Label titleLab = wnd.rootVisualElement.Q<Label>("TreeViewTitle");
if( titleLab != null )
titleLab.text = $"对话图 - {tree.name}";
wnd.graphView.Populate(tree);
return true;
}
return false;
}
public static void CloseEditorWindow()
{
DialogGraphWindow wnd = GetWindow<DialogGraphWindow>();
wnd.Close();
}
private DialogGraphView graphView = null;
private InspectorView inspectorView = null;
public void CreateGUI()
{
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Dialog Graph/Editor/EditorWindow/DialogGraphWindow.uxml");
visualTree.CloneTree(rootVisualElement);
graphView = rootVisualElement.Q<DialogGraphView>("GraphView");
graphView.OnNodeSelected += WhenNodeSelected;
inspectorView = rootVisualElement.Q<InspectorView>("Inspector");
Button bt = rootVisualElement.Q<Button>("SaveButton");
bt.clicked += OnSaveButtonClicked;
}
private void OnSaveButtonClicked()
{
AssetDatabase.SaveAssets();
}
private void OnDestroy()
{
AssetDatabase.SaveAssets();
}
}
还有一个问题,如果用户双击了一个DialogTree,进入了编辑状态,然后它又将这个DialogTree删除了,怎么办?此时我们的编辑窗口所编辑的目标已经消失了,窗口应该要自动关闭才合理。所以,我们需要监听这种事情的发生。查阅资料后,果然有一个方法:
public class CloseEditorBeforeAssetRemoved : UnityEditor.AssetModificationProcessor
{
public static AssetDeleteResult OnWillDeleteAsset(string assetPath, RemoveAssetOptions option)
{
if( AssetDatabase.LoadAssetAtPath<DialogTree>(assetPath) is DialogTree tree )
{
DialogGraphWindow.CloseEditorWindow();
}
return AssetDeleteResult.DidNotDelete;
}
}