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!