In the previous engine tool summary, I mentioned that XNode can be used to write visualizations. During the winter vacation in the Netherlands, I would like to introduce XNode and its related uses based on the simple visual dialogue editor I wrote before.

About XNode

  XNode is a Unity visual editor plugin. Another similar plugin is NodeGraphProcessor. I personally prefer the (self-proclaimed) lightweight and user-friendly XNode because I like its interface. Here is a picture for your comparison.

XNode

NodeGraphProcessor

Use XNode

  XNode is divided into Graph (the entire visual graph), Node (customized node) and Port (port for connecting nodes). To create a custom visual editor, you need a Graph script (to create the corresponding graph), a Node script (to define the content of the node), and a NodeEditor script (optional, to define how the elements in the node are drawn in the graph). You can fully connect to Odin and let it control it, or you can use Unity for serialization and call the GUI for drawing. I personally choose the latter (I don’t have money to buy Odin.jpg). In addition, although the official has provided a corresponding demonstration project, those addition, subtraction, multiplication and division are too simple to be customized, so I will briefly explain it in combination with my code content later (if you need details and basics, you can move to the official document).

Node

  Let’s start with the node, which is the most critical and indispensable part of XNode. However, XNode encapsulates this part well. You can use its given properties after inheriting Node. You can add [NodeWidth] before the class to limit the width, and add [Input] and [Output] to the elements in the class to determine whether it is an input or output terminal. The ConnectionType.Multiple in the [OutPut] part means that as an output node, it can pass values to multiple input nodes, and for list output, dynamicPortList needs to be set to true so that XNode can manage the connection information of each node.

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 ();
}

Editor

  Then there is the editor. Although it is not necessary, you have to modify it if you want to do something fancy. For my dialogue editor, the number of input and output ports of a single dialogue item is uncertain, so you need to inherit NodeEditor to modify the editor. Here is a part of my code, which mainly focuses on the magic modification of the drawElementCallback function of ReorderableList. It is also worth mentioning that the EditorGUILayout of the underlying Unity is invalid here. Just use EditorGUI to align yourself or use Odin to do simplify the work.

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);
}

//Define the arrangement of the elements and assign input and output nodes to each list item.
void InitList(ReorderableList list)
{
int reorderableListIndex = -1;
SerializedProperty arrayData = serializedObject.FindProperty("dialogueList");
if (dialogGraph == null) dialogGraph = window.graph as DialogueGraph;

//List item height and arrangement of elements within it
list.elementHeightCallback = (index) => {};
list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {};

//Custom nodes also need to change when moved, added, or deleted
list.onReorderCallback = (ReorderableList list) => {};
list.onAddCallback = (ReorderableList list) => {};
list.onRemoveCallback = (ReorderableList list) => {};
}
}

Graph

  There is actually nothing to say about the last graph. Just inherit NodeGraph and give it a name in CreateAssetMenu. But for an editor, you have to define how to read the nodes in your graph and how to pass information. This depends on you. I simply separate the acquisition of information and event movement. I think this is more logical.

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() {}
}

  The final dialogue node diagram above is a passage from A Glowing Canvas on the Color of Timidity . It is quite touching. Take a step forward or take a step back, which choice will you make?

A Glowing Canvas on the Color of Timidity

Written behind

  This is the end of the brief introduction of XNode. The editor tool seems simple, but it is very laborious to implement. After all, I also introduced reflection of variables and functions to realize retrieval and event nodes (InstanceID and GUID storage, GenericMenu display) and encapsulated DialogueSystem to read the information in the diagram through Next(). After completion, I also followed XNode and put it on Unity Assets. At the same time, I open sourced the tool and source code to Github, hoping to be of some help to those who come later.

Four Nodes of Dialogue System