在之前的引擎工具总结中提到了可以用XNode来写可视化,正好荷兰冬假期间就之前写的简易可视化对话编辑器来介绍下XNode以及相关使用。
关于XNode XNode 是一个Unity可视化编辑器制作插件,同类的还有NodeGraphProcessor 。个人因比较喜欢XNode的界面选择了(自称)轻量,用户友好的XNode。这里放下图也方便各位对比下。
XNode
NodeGraphProcessor
使用XNode XNode中分为Graph(整个可视化的图),Node(自定义的节点)和Port(节点间连线的端口)。为创建自定义的可视化编辑器,需要Graph脚本(创建对应的图),Node脚本(定义节点的内容),NodeEditor脚本(非必须,定义节点中的元素在图中如何绘制)。可以完全接入Odin并交由其控制,也可以使用Unity进行序列化并调用GUI进行绘制,个人选择的是后者(没钱买Odin.jpg)。另外虽然官方给了相应的演示工程,但那些加减乘除太简单了根本没用到定制,所以后续我会结合我代码内容进行简单讲解(需要细节而基础的可以移步官方文档 )。
节点 先从节点开始讲起吧,这是XNode最关键且必不可少的部分。但这部分XNode封装的比较好,继承Node后使用其给定的属性就可。大概就是类前可加[NodeWidth]限定宽度,类内元素加[Input], [Output]以决定是输入还是输出端。[OutPut]部分的ConnectionType.Multiple表示其作为输出节点可以把值传给多个输入节点,而对于列表输出则需要置真dynamicPortList以供XNode管理各节点连接信息。
1 2 3 4 5 6 7 8 [Serializable ] [NodeWidth(350 )] public class DialogueNode : Node { [Input ] public Type before; [Output(connectionType = ConnectionType.Multiple) ] public Type after; [Output(dynamicPortList = true, connectionType = ConnectionType.Multiple) ] public List<Type> optionList = new ();}
编辑器 然后是编辑器,虽然非必须但如果你想整点什么花活就不得不修改这个。对于我这个对话编辑器来说就是单个对话项的输入输出端口数不定,这就需要继承NodeEditor去修改编辑器。另外值得一提的是这里比较底层Unity的EditorGUILayout是无效的,老老实实用EditorGUI自己搓对齐或者拿Odin来点科技与狠活吧。
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 [CustomNodeEditor(typeof(DialogueNode)) ] public class DialogueNodeEditor : NodeEditor { DialogueNode node; DialogueGraph dialogGraph; NodePort before, after; public override void OnCreate () { base .OnCreate(); node = serializedObject.targetObject as DialogueNode; } public override void OnBodyGUI () { NodeEditorGUILayout.DynamicPortList("dialogueList" , typeof (DialogueInfo), serializedObject, IO.Output, ConnectionType.Override, TypeConstraint.Inherited, InitList); } void InitList (ReorderableList list ) { int reorderableListIndex = -1 ; SerializedProperty arrayData = serializedObject.FindProperty("dialogueList" ); if (dialogGraph == null ) dialogGraph = window.graph as DialogueGraph; list.elementHeightCallback = (index) => {}; list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {}; list.onReorderCallback = (ReorderableList list) => {}; list.onAddCallback = (ReorderableList list) => {}; list.onRemoveCallback = (ReorderableList list) => {}; } }
图 最后图这边其实也没什么说的,继承NodeGraph后CreateAssetMenu给下名称就行。但对于一个编辑器而言,得在这定义如何读取你图中那些节点以及如何传递信息,这点就交由各位去头疼了。个人仅简单的将获取信息和事件移动拆开,感觉这样逻辑清晰些?
1 2 3 4 5 6 7 [Serializable, CreateAssetMenu(fileName = "New Dialogue Graph" , menuName = "Dialogue Graph" ) ] public class DialogueGraph : NodeGraph { void Init () {} void GetInfo () {} void MoveOn () {} }
上张最终完成的对话节点图,内容选取的是《胆怯色上发光的画布 》中的一段。算是挺有感触的。前进一步亦或后退一步,你又会作何选择呢?
胆怯色上发光的画布
写在最后 XNode的简单说明就到此结束了,编辑器工具看似简单,但真做起来也是极为费事,毕竟后续我还引入了对变量和函数的反射以实现检索和事件节点(InstanceID和GUID存储,GenericMenu显示)并封装DialogueSystem靠Next()读取图中信息。完成后也学XNode上架Unity Assets的同时将工具和源码就开源到Github,希望对后来者有些许帮助。
对话系统的四种节点