Table of Contents

Architectural Overview

Whether you are a designer, writer or developer looking to extend the system, it is important to understand how the dialogue system works under the hood.

The system consists are 3 layers:

  1. Node Layer
  2. Data Layer
  3. Execution Layer

In code they are represented through the following classes:

  1. DialogueNode<T>
  2. DialogueNodeData
  3. DialogueProcessor<T>

These are the building blocks from which the dialogue system is built. The CLogic Dialogue system uses a data-oriented approached; data is created on the nodes, and passed down to the processor for execution.

There are 3 main scripts that are reponsible for each of these steps in order:

  1. The Dialogue Editor Graph (DialogueEditorGraph)
  2. The Dialogue Importer (DialogueGraphImporter)
  3. The Dialogue Director (DialogueDirector)

Node Layer

First, the node layer is created through Unity's Graph Toolkit. Nodes are added to the DialogueEditorGraph where they are given ports to connect them together.

Each node is given freedom on how it should exists and react to connections. Validations may be made at this stage to ensure the node is properly formed.

This step is handled almost entirely by the DialogueEditorGraph.

Data Layer

The stage is processed when the editor graph is saved. The editor version of the graph cannot be used to execute the dialogue at runtime so it is converted into a runtime representation of type DialogueGraph. Each node is requested to output data of type DialogueNodeData.

This is done by the DialogueGraphImporter. It will create an array of DialogueNodeData inside of the DialogueGraph. It functions as a linked list. The start node is provided alongside it.

Execution Layer

Now the dialogue is ready to execute. The DialogueDireector scans the hiearachy for node processors and populates a dictionary internally matching each type to its processor. Then, whenever a node is executed, it calls the processor passing the current DialougeNodeData

Upon a progress request (usually a click or pressing return), the director will ask the currently active processor whether it can progress onto the next node. This is useful to block progress in case of requiring choices or delaying progression when using a typewriting animation; the first progression requests will skip the animation and the second will allow normal progression

NOTE

The dialogue system, at the lowest of level is not based around showing dialogue to the screen. Instead, it simply executes nodes that are linked together. All of the 'dialogue' logic such as playing animations, showing text, spawning characters, etc... is handled by individual processors.

As such, the system can be extended to include non-dialogue elements such as sequencing nodes for a tutorial system, conditions for a certain branch, and so on. Their is native support for the following CLogic libraries:

  • CLogic.Conditionals
  • CLogic.DataSaving

Technical Overview

Now that you have a good understanding of the overall architecture, we'll go more in-depth as to how we've implemented this in code.

Dialogue Node

Each node in the dialogue system inherits from DialogueNode<T>. This generic class contains various QOL functions to streamline node creation.

Note

Any type inherting from Node is a valid Graph Toolkit node but it should implement the IDialogueNode interface to be recognised by the dialogue system

First, the node should specify which data it will output through its type parameter. Then, the node will implement its ports and relevant options.

APIs are exposed to create execution ports and handle the linking for the data automatically.
See Getting Started

Finally it needs to process the connected ports and options to produce and output of type T. This is done in the T ProcessNodeAsset(DialogueGraph graph, Dictionary<IPort, int> portMap) function. After the node has been processed into data its role is complete.

Nodes can also have validation logic on them through void OnValidate(GraphLogger graphLogger).

Dialogue Graph Importer

There are 2 types of graph: DialogueEditorGraph & DialogueGraph. DialogueEditorGraph is of type Graph and contains the serialized nodes. See Graph API reference for more details.
The DialogueGraph is a scriptableobject which contains the processed data of each node; this is a runtime representation of the editor graph.

The dialogue graph contains 3 major elements:

  1. Starter Node Index
  2. Array of DialogueNodeData
  3. Sub-Assets

First, during the import process, each node is mapped to an id during the node map process. Then, each node is requested to process their ports and options, and output the relevant data. Each node data is sorted based on their id and put into an array where their id represents their index in the array. This array is then put inside of the DialogueGraph scriptableobject to be used by the DialogueDirector. Finally, the index of the first node, (the start node) is put in the scriptableobject.

Sub-assets can optionally be added through provisioner nodes. See Provisioner Nodes.
If a provisioner node outputs a scriptableobject, it will be serialized as a sub-asset inside of the DialogueGraph.

Dialogue Director

The main purpose of the director is to move from node to node (their runtime representation) and call onto the appropriate processors.

Before the director can execute a graph, it first needs to go through a processor discovery process. It will scan its gameobject and move down in that gameobject's hierarchy x levels down (2 levels max by default) for processors. It will then map each processed type to its processor.

Processors can also be singletons and be reused by multiple directors. See Singleton Processors

Before a processor is called, the director will first call any preprocessors handling the specific type of data that will be passed to the processor. This is where data from Provisioner Nodes are added. Post-processors also exist and are called after the node has been processed

Tip

For asset subgraphs, if they are not expanded, they are loaded into memory separately when their execution is required.

Processors

Processors are given data about the current node they are to execute. They exists as monobehaviours and need to inherit from DialogueNodeProcessor<T>.

Note

Processors can inherit directly from MonoBehaviour but need to implement the IDialogueNodeProcessor interface to be discovered by the dialogue system

Tip

Not all processors need to be monobehaviours, they can be pure c# classes as long as they implement the IDialogueNodeProcessor interface. They can then act as a stateless singleton processor and be discovered by implement the [SingletonProcessor] attribute. See Singleton Processors for mode details