Documentation

LoreHub Dialogue System

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

Dialogue System 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.

LoreHub Export Json

Implement Player for Branching Dialogue

Branching Dialogue Schema

This is a simple visualization of the branching dialogue schema. Every dialogue consists of Dialogue Node, Dialogue Option and DialogueLink that connects:

  • Dialogue Node to Dialogue Node
  • Dialogue Option to Dialogue Node
  • Dialogue to Dialogue Node - this is start of the Dialogue.

This is the folder structure of our project:

Unity Folder Structure

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.

Unity Folder Structure Dialogue System

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.

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;

  // 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"];

    // 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];
  }

}

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 link from it.
  public DialogueLink LinksFromMe = null;

  // 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;
  // Option's link from it.
  public DialogueLink LinksFromMe = null;
  // 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: Dialogue Game Object

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 = link;
      }
      DialogueOption neededOption = this.options.Find(n => n.Id == link.FromId);
      if (neededOption != null)
      {
        neededOption.LinksFromMe = 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;

  // 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.Dialogue.nodes.Find(n => n.Id == this.CurrentNode.LinksFromMe.ToId);
      if (nextNode != null)
      {
        this.SetNextNode(nextNode);
      }
    }
  }

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