Dialogue System Tutorial

A simple, clean and expandable unity dialogue system

The system uses scriptable objects and singletons, allowing for easy creation and implementation of dialogue from anywhere else in the project. The scripts are straightforward and versatile allowing you to expand them at will.
Full script below (Creative Commons 4.0).

Code

Main dialogue script

//Created by Arman Awan - ArmanDoesStuff 2018
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TextBoxMan : MonoBehaviour
{    
    #region variables
    //Speech Stuff
    public enum Speaker { Arman, Mac, BadGuy };
    [System.Serializable]
    public struct SpeechGroup
    {
        public Speaker currentSpeaker;
        public AudioClip speechClip;
        [TextArea(2,5)]
        public string speechText;
    }
    public Sprite[] artworks;

    //Event for the end of each speech
    public delegate void EndAction();
    public static event EndAction EndSpeechAction;

    //Stuff to set
    [SerializeField] CanvasGroup TextboxCanvasGroup;
    [SerializeField] Image speakerIcon;
    [SerializeField] Text speakerName;
    [SerializeField] Text mainText;
    [SerializeField] AudioSource audC;
    [SerializeField] AudioClip StartSound;
    [SerializeField] AudioClip NextSound;

    //Other
    SpeechScptObj currentSpeechFull;
    int currentSpeechIndex;
    bool speechInProgress = false;
    public static TextBoxMan instance;
    string currentSpeechText = "";
    float fillAmount = 0;
    bool running = false;
    #endregion

    #region methods

    //set singleton so this script can be called from anywhere
    private void Awake()
    {
        instance = this;
    }
        
    private void Update()
    {
        //type in the current speech's text
        if (running)
        {
            fillAmount += Time.deltaTime * 25f;
            if (fillAmount < currentSpeechText.Length)
            {
                mainText.text = currentSpeechText.Substring(0, (int)fillAmount);
            }
            else
            {
                running = false;
                mainText.text = currentSpeechText;
            }
        }
    }

    //called from elsewhere to start a new dialogue sequence
    public void StartSpeech(SpeechScptObj speech, float delay = 0)
    {
        StartCoroutine(StartSpeechCroute(speech, delay));
    }

    IEnumerator StartSpeechCroute(SpeechScptObj speech, float delay = 0f)
    {
        //make sure to wait for the current dialogue sequence
        while (speechInProgress)
        {
            yield return null;
        }
        speechInProgress = true;
        //set the content and wait before filling
        currentSpeechFull = speech;
        currentSpeechIndex = 0;
        yield return new WaitForSeconds(delay);
        FillSpeech();
        //play sound and fade in/allow interaction
        SoundPlayer.master.PlaySound(StartSound, SoundPlayer.SoundTypes.generic);
        float cgrpAlpha = 0;
        while (cgrpAlpha < 1)
        {
            cgrpAlpha += Time.deltaTime * 2;
            TextboxCanvasGroup.alpha = cgrpAlpha;
            yield return null;
        }
        TextboxCanvasGroup.alpha = 1;
        TextboxCanvasGroup.blocksRaycasts = TextboxCanvasGroup.interactable = true;

    }

    IEnumerator EndSpeechCroute(float delay = 0)
    {
        //stop any current speaking and revert to hidden state
        audC.Stop();
        SoundPlayer.master.PlaySound(StartSound, SoundPlayer.SoundTypes.generic);
        TextboxCanvasGroup.blocksRaycasts = TextboxCanvasGroup.interactable = false;
        float cgrpAlpha = 1;
        while (cgrpAlpha > 0)
        {
            cgrpAlpha -= Time.deltaTime * 2;
            TextboxCanvasGroup.alpha = cgrpAlpha;
            yield return null;
        }
        yield return new WaitForSeconds(delay);

        //Trigger and then remove any actions assigned to the script
        if (EndSpeechAction != null)
        {
            EndSpeechAction.Invoke();
            foreach (var d in EndSpeechAction.GetInvocationList())
            {
                EndSpeechAction -= d as TextBoxMan.EndAction;
            }
        }
        //Allow for next speech
        speechInProgress = false;
    }

    //Allow a new speech without creating one from the editor, useful for single notifications
    public void SingleSpeech(Speaker _speaker, string _text, float delay = 0)
    {
        SpeechScptObj sObj = new SpeechScptObj();
        SpeechGroup sGrp = new SpeechGroup();
        sGrp.currentSpeaker = _speaker;
        sGrp.speechText = _text;
        sObj.speechGroups.Add(sGrp);
        StartSpeech(sObj, delay);
    }

    public void SetSpeechNext()
    {
        //incriment and set the next piece of dialogue
        currentSpeechIndex++;
        if (currentSpeechFull.speechGroups.Count > currentSpeechIndex)
        {
            FillSpeech();
            SoundPlayer.master.PlaySound(NextSound, SoundPlayer.SoundTypes.generic);
        }
        //unless the conversation is finished, in which case end the speech
        else
        {
            StartCoroutine(EndSpeechCroute());
        }
    }

    void FillSpeech()
    {
        //set the current piece of dialogue and other inforamtion
        SpeechGroup s = currentSpeechFull.speechGroups[currentSpeechIndex];
        speakerIcon.sprite = artworks[(int)s.currentSpeaker];
        speakerName.text = s.currentSpeaker.ToString();
        //start the type-in effect
        currentSpeechText = s.speechText;
        fillAmount = 0;
        running = true;
        //play any accompanying voice over
        audC.Stop();
        if (s.speechClip != null)
        {
            audC.clip = s.speechClip;
            audC.Play(SoundPlayer.SoundTypes.dialogue);
        }
    }

    
    #endregion
}

Scriptable Object that is created in the editor, pre-filled and used from other script

//Created by Arman Awan - ArmanDoesStuff 2018
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "New Speech", menuName = "Speech")]
public class SpeechScptObj : ScriptableObject
{
    public int speechNum;
    public string speechName;
    public List<TextBoxMan.SpeechGroup> speechGroups;
}


Example use

//Created by Arman Awan - ArmanDoesStuff 2018
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpeechTriggerTest : MonoBehaviour
{
    #region variables
    [SerializeField] SpeechScptObj testSpeech01;
    [SerializeField] SpeechScptObj testSpeech02;
    [SerializeField] AudioClip explodeSound;
    #endregion

    #region accessors
    #endregion

    #region methods
    public void BtnPressed()
    {
        GetComponent<CanvasGroup>().interactable = false;
        TextBoxMan.EndSpeechAction += AfterSpeech;
        TextBoxMan.instance.StartSpeech(testSpeech01);
    }

    public void AfterSpeech()
    {
        LevelMan.instance.Trauma = 1f;
        SoundPlayer.master.PlaySound(explodeSound, SoundPlayer.SoundTypes.effect);
        TextBoxMan.instance.StartSpeech(testSpeech02, 1f);
    }
    #endregion
}


Thanks for reading, I hope you enjoy!

Creative Commons License

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.