Dialogue System for Unity
LoreHub's Dialogue System is a headless system, so you can use it with any game engine. In this tutorial you will learn how you can implement Dialogue Player in Unity. You can find the source code on GitHub. You are welcome to improve this code.
This is just an example implementation it could be done differently but it will help you to understand the core concept of the schema.
Workflow

First, you need to build dialogue using LoreHub. As soon as you build a branching dialogue, you can export it into JSON schema. The LoreHub's Dialogue System JSON schema is strongly typed and versioned and you can read API documentation on this page.
Before you can import schema to Unity you need to implement Dialogue Player that is compatible with JSON Schema. This tutorial will help you to do it.

Implement Player for Branching Dialogue

This is a simple visualization of the branching dialogue schema. Every dialogue consists of Dialogue Node, Dialogue Option and DialogueLink that connects:
Dialogue NodetoDialogue NodeDialogue OptiontoDialogue NodeDialoguetoDialogue Node- this is start of the Dialogue.
A Dialogue Node without options, or a Dialogue Option, can have several links. Each link can have conditions: variables that must all have the given values (AND). The player follows the first link whose conditions are all met. The Dialogue has one link, to its first node.
A condition can also check whether a node was played or an option was selected; it then has a kind and variableId is the node or option id, see state data conditions. The code below handles variables only.
This is the folder structure of our project:

It contains a LoreHub folder for all code that is related to the Dialogue System, a UI folder for UI-related scripts, a Scenes folder for Unity Scenes, and TextMesh Pro for working with text.
Create Basic Dialogue's Entities
First, we need to create each entity to represent different parts of the dialogue. They should be created using the default way in Unity: Right Mouse Button - Create - C# Script.

What is the purpose of each one?
Dialogue - This is the main script that will handle dialogue import and it will contain collections of each entity.
DialoguePlayer - Will handle playing the generated dialogue. It will render DialogueNode and DialogueOption.
DialogueNode - Object that will contain all related to Dialogue Node information. Like content of the node, node DialogueOption and DialogueLink coming from it.
DialogueOption - Options contain the text on the option and DialogueLink that are coming from it.
DialogueLink - Links have references to DialogueNode and DialogueOption that are connected.
Dialogue Link
using UnityEngine;
using System.Collections;
using UnityEditor;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
public class DialogueLink : MonoBehaviour
{
// Id of the Dialogue Link.
public string Id;
// From what object this Link is.
public string FromId;
// Where this link leads to.
public string ToId;
// Conditions of the link: variable id and the value it must have.
public Dictionary<string, string> RequiredVariables = new Dictionary<string, string>();
// Factory method to create this Link from JSON object.
public static DialogueLink Create(JObject link, GameObject container)
{
// Create Game Object and Assign this Script to it.
GameObject goLink = new GameObject();
goLink.transform.SetParent(container.transform);
goLink.AddComponent<DialogueLink>();
// Set all values from JSON.
DialogueLink dialogueLinkScript = goLink.GetComponent(typeof(DialogueLink)) as DialogueLink;
dialogueLinkScript.Id = (string)link["id"];
dialogueLinkScript.FromId = (string)link["from"];
dialogueLinkScript.ToId = (string)link["to"];
// Links without conditions have no requiredVariables.
JArray requiredVariables = (JArray)link["requiredVariables"];
if (requiredVariables != null)
{
foreach (JObject requiredVariable in requiredVariables)
{
dialogueLinkScript.RequiredVariables[(string)requiredVariable["variableId"]] = (string)requiredVariable["value"];
}
}
// Set GameObject's name to human readable.
goLink.name = dialogueLinkScript.GetName();
return dialogueLinkScript;
}
// Generate human readable name.
public string GetName()
{
string[] split = this.Id.Split("/");
return "link/" + split[split.Length - 1];
}
// Link can be followed when every required variable has the required value.
public bool CanFollow(Dictionary<string, string> variables)
{
foreach (KeyValuePair<string, string> requiredVariable in this.RequiredVariables)
{
string value;
if (!variables.TryGetValue(requiredVariable.Key, out value) || value != requiredVariable.Value)
{
return false;
}
}
return true;
}
}
Dialogue Option
using UnityEngine;
using System.Collections;
using UnityEditor;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
public class DialogueOption : MonoBehaviour
{
// Id of the Dialogue Option.
public string Id;
// Text on the Dialogue Options.
public string Text;
// Option's links from it.
public List<DialogueLink> LinksFromMe = new List<DialogueLink>();
// Factory method to create this Dialogue Option from JSON object.
public static DialogueOption Create(JObject option)
{
// Create Game Object and assign Dialogue Option to id.
GameObject goOption = new GameObject();
goOption.AddComponent<DialogueOption>();
// Set all values from JSON.
DialogueOption dialogueOptionScript = goOption.GetComponent(typeof(DialogueOption)) as DialogueOption;
dialogueOptionScript.Id = (string)option["id"];
dialogueOptionScript.Text = (string)option["text"];
// Set GameObject's name to human readable.
goOption.name = dialogueOptionScript.GetName();
return dialogueOptionScript;
}
// Generate human readable name.
public string GetName()
{
string[] split = this.Id.Split("/");
return "option/" + split[split.Length - 1];
}
}
Dialogue Node
using UnityEngine;
using System.Collections;
using UnityEditor;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
public class DialogueNode : MonoBehaviour
{
// Id of the Dialogue Node.
public string Id;
// Node's links from it.
public List<DialogueLink> LinksFromMe = new List<DialogueLink>();
// All options for this Dialogue Node.
public List<DialogueOption> Options = new List<DialogueOption>();
// All options' Ids for this Dialogue Node.
public List<string> OptionIds = new List<string>();
// Factory method to create this Dialogue Node from JSON object.
public static DialogueNode Create(JObject node, GameObject container)
{
// Create GameObject and assign script.
GameObject goNode = new GameObject();
goNode.transform.SetParent(container.transform);
goNode.AddComponent<DialogueNode>();
// Create Node from JSON object.
DialogueNode dialogueNodeScript = goNode.GetComponent(typeof(DialogueNode)) as DialogueNode;
dialogueNodeScript.Id = (string)node["id"];
// Add Options' Ids to it.
JArray optionsIds = (JArray)node["optionsIds"];
foreach (string id in optionsIds)
{
dialogueNodeScript.OptionIds.Add(id);
}
// Set friendly name.
goNode.name = dialogueNodeScript.GetName();
return dialogueNodeScript;
}
// Generate human readable name.
public string GetName()
{
string[] split = this.Id.Split("/");
return "node/" + split[split.Length - 1];
}
// Add option to Dialogue Node
public void AddOption(DialogueOption option)
{
option.gameObject.transform.SetParent(this.gameObject.transform);
this.Options.Add(option);
}
}
Dialogue
This class is responsible for parsing exported JSON. In the end it will generate this folder structure:

using UnityEngine;
using System.Collections;
using UnityEditor;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using TMPro;
public class Dialogue : MonoBehaviour
{
// This is field where in editor you should drop JSON file from LoreHub.
public TextAsset json;
// Dialogue's Id
public string Id;
// This is container for all nodes.
private GameObject nodesContainer;
// This is container for all links.
private GameObject linksContainer;
// This is container for
private GameObject unassignedContainer;
// Collection of all links.
public List<DialogueLink> links;
// Collection of all Nodes.
public List<DialogueNode> nodes;
// Collection of all Options.
public List<DialogueOption> options;
// Function that will parse JSON and generate Dialogue in Unity.
public void GenerateDialogue()
{
// We need to delete old data so we can create Dialogue from scratch.
DestroyImmediate(this.nodesContainer);
DestroyImmediate(this.linksContainer);
DestroyImmediate(this.unassignedContainer);
// Create Unassigned Container - for items without parent.
this.unassignedContainer = new GameObject();
this.unassignedContainer.name = "Unassigned Container";
this.unassignedContainer.transform.SetParent(this.transform);
// Create Nodes container.
this.nodesContainer = new GameObject();
this.nodesContainer.name = "Nodes Container";
this.nodesContainer.transform.SetParent(this.transform);
// Create Links container.
this.linksContainer = new GameObject();
this.linksContainer.name = "Links Container";
this.linksContainer.transform.SetParent(this.transform);
// Get the initial JSON Object.
JObject exportData = JObject.Parse(json.ToString());
// Extract resources from JSON
JArray resources = (JArray)exportData["resources"];
// Create Collections and empty them.
this.links = new List<DialogueLink>();
this.nodes = new List<DialogueNode>();
this.options = new List<DialogueOption>();
// Sort resources.
foreach (JObject item in resources)
{
// We can define resource by their type.
string type = (string)item["type"];
if (type == "@lorehub/dialogue")
{
this.Id = (string)item["id"];
}
if (type == "@lorehub/dialogue-node")
{
DialogueNode node = DialogueNode.Create(item, this.nodesContainer);
this.nodes.Add(node);
}
if (type == "@lorehub/dialogue-link")
{
DialogueLink link = DialogueLink.Create(item, this.linksContainer);
this.links.Add(link);
}
if (type == "@lorehub/dialogue-node-option")
{
DialogueOption option = DialogueOption.Create(item);
options.Add(option);
option.gameObject.transform.SetParent(this.unassignedContainer.transform);
}
}
AttachLinks();
AssignOptionsToNodes();
// Add Dialogue Player Script.
DialoguePlayer player = this.gameObject.GetComponent<DialoguePlayer>();
if (player == null)
{
player = this.gameObject.AddComponent<DialoguePlayer>();
player.Dialogue = this;
}
}
// This method will go through each link and attach them to Nodes or Options.
private void AttachLinks()
{
foreach (DialogueLink link in this.links)
{
DialogueNode neededNode = this.nodes.Find(n => n.Id == link.FromId);
if (neededNode != null)
{
neededNode.LinksFromMe.Add(link);
}
DialogueOption neededOption = this.options.Find(n => n.Id == link.FromId);
if (neededOption != null)
{
neededOption.LinksFromMe.Add(link);
}
}
}
// Attach all Options to Nodes.
private void AssignOptionsToNodes()
{
foreach (DialogueNode node in this.nodes)
{
foreach (string optionId in node.OptionIds)
{
DialogueOption neededOption = this.options.Find(o => o.Id == optionId);
if (neededOption != null)
{
node.AddOption(neededOption);
}
}
}
}
}
Dialogue Player
DialoguePlayer is using UI components to render Dialogue.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class DialoguePlayer : MonoBehaviour
{
// This is Dialogue that it will play.
public Dialogue Dialogue;
// This is Current Node that need to be rendered.
public DialogueNode CurrentNode;
// Current values of variables by variable id. Fill it from your game state.
public Dictionary<string, string> Variables = new Dictionary<string, string>();
// Where we should add Node's Text
public TMP_Text DialogueText;
// Prefab for Option Button.
public GameObject OptionButtonPrefab;
// Where we should generate option button
public GameObject PlaceForOptionButtons;
// Option buttons that will be generated.
public List<GameObject> buttons = new List<GameObject>();
public void Start()
{
// Find Start Node;
DialogueLink linkFromDialogue = this.Dialogue.links.Find(l => l.FromId == this.Dialogue.Id);
this.CurrentNode = this.Dialogue.nodes.Find(n => n.Id == linkFromDialogue.ToId);
RenderNode(this.CurrentNode);
}
void Update()
{
// Click anywhere to show next node if no options.
if (Input.GetMouseButtonDown(0) && this.CurrentNode.Options.Count == 0)
{
DialogueNode nextNode = this.GetNextNode(this.CurrentNode.LinksFromMe);
if (nextNode != null)
{
this.SetNextNode(nextNode);
}
}
}
// Find the node of the first link whose conditions are all met. Use it for an option's links too.
public DialogueNode GetNextNode(List<DialogueLink> links)
{
DialogueLink link = links.Find(l => l.CanFollow(this.Variables));
if (link == null)
{
return null;
}
return this.Dialogue.nodes.Find(n => n.Id == link.ToId);
}
// Update next node and re-render it.
public void SetNextNode(DialogueNode node)
{
this.CurrentNode = node;
RenderNode(this.CurrentNode);
}
// Render Node functionality.
private void RenderNode(DialogueNode node)
{
// Clean
foreach (GameObject go in this.buttons)
{
DestroyImmediate(go);
}
this.buttons = new List<GameObject>();
// Create
this.DialogueText.text = node.ContentBlock.GetText();
foreach (DialogueOption option in node.Options)
{
GameObject goOption = Instantiate(this.OptionButtonPrefab, new Vector3(0, 0, 0), Quaternion.identity);
RectTransform rectTransform = goOption.GetComponent<RectTransform>();
goOption.transform.SetParent(this.PlaceForOptionButtons.transform);
Transform buttonChild = goOption.transform.GetChild(0);
Transform buttonTextGo = buttonChild.transform.GetChild(0);
TMP_Text buttonText = buttonTextGo.gameObject.GetComponent<TMP_Text>();
buttonText.text = option.Text;
// Add my script to handle click
MyButton myButton = goOption.AddComponent<MyButton>();
myButton.Option = option;
myButton.Button = buttonChild.gameObject;
myButton.DialoguePlayer = this;
buttons.Add(goOption);
}
}
}