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!
