An easy to use Unity 3D library for creating illustrated Interactive Fiction games and more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 lines
7.7 KiB

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Fungus
{
public class Dialog : MonoBehaviour
{
public float writingSpeed = 60;
public AudioClip writingSound;
public bool loopWritingSound = true;
public float punctuationPause = 0.25f;
[Tooltip("Click anywhere on screen to continue when set to true, or only on dialog when false.")]
public bool clickAnywhere = true;
public Canvas dialogCanvas;
public Text nameText;
public Text storyText;
public Image characterImage;
public AudioClip characterTypingSound;
protected float currentSpeed;
protected float currentPunctuationPause;
protected bool boldActive;
protected bool italicActive;
protected bool colorActive;
protected string colorText;
protected float clickCooldownTimer;
protected bool wasPointerClicked;
protected AudioSource voiceOverAudio;
protected virtual void LateUpdate()
{
wasPointerClicked = false;
if (clickCooldownTimer > 0f)
{
clickCooldownTimer -= Time.deltaTime;
clickCooldownTimer = Mathf.Max(0, clickCooldownTimer);
}
if (clickCooldownTimer == 0f &&
clickAnywhere &&
Input.GetMouseButtonDown(0))
{
wasPointerClicked = true;
clickCooldownTimer = 0.2f;
}
}
public virtual void ShowDialog(bool visible)
{
if (dialogCanvas != null)
{
dialogCanvas.gameObject.SetActive(visible);
}
if (visible)
{
// A new dialog is often shown as the result of a mouse click, so we need
// to make sure the previous click doesn't register on the new dialogue
wasPointerClicked = false;
clickCooldownTimer = 0.2f;
}
}
public virtual void SetCharacter(Character character, FungusScript fungusScript = null)
{
if (character == null)
{
if (characterImage != null)
characterImage.gameObject.SetActive(false);
if (nameText != null)
nameText.text = "";
characterTypingSound = null;
}
else
{
string characterName = character.nameText;
if (characterName == "")
{
// Use game object name as default
characterName = character.name;
}
if (fungusScript != null)
{
characterName = fungusScript.SubstituteVariables(characterName);
}
characterTypingSound = character.soundEffect;
SetCharacterName(characterName, character.nameColor);
}
}
public virtual void SetCharacterImage(Sprite image)
{
if (characterImage != null)
{
if (image != null)
{
characterImage.sprite = image;
characterImage.gameObject.SetActive(true);
}
else
{
characterImage.gameObject.SetActive(false);
}
}
}
public virtual void SetCharacterName(string name, Color color)
{
if (nameText != null)
{
nameText.text = name;
nameText.color = color;
}
}
protected virtual IEnumerator WriteText(string text, Action onWritingComplete, Action onExitTag)
{
storyText.text = "";
// Parse the story text & tag markup to produce a list of tokens for processing
DialogParser parser = new DialogParser();
parser.Tokenize(text);
if (parser.tokens.Count == 0)
{
if (onWritingComplete != null)
{
onWritingComplete();
}
yield break;
}
DialogText dialogText = new DialogText();
dialogText.writingSpeed = writingSpeed;
dialogText.punctuationPause = punctuationPause;
GameObject typingAudio = null;
if (characterTypingSound != null || writingSound != null)
{
typingAudio = new GameObject("WritingSound");
typingAudio.AddComponent<AudioSource>();
if (characterTypingSound != null)
{
typingAudio.audio.clip = characterTypingSound;
}
else if (writingSound != null)
{
typingAudio.audio.clip = writingSound;
}
typingAudio.audio.loop = loopWritingSound;
typingAudio.audio.Play();
dialogText.typingAudio = typingAudio.audio;
}
foreach (Token token in parser.tokens)
{
switch (token.type)
{
case TokenType.Words:
dialogText.Append(token.param);
break;
case TokenType.BoldStart:
dialogText.boldActive = true;
break;
case TokenType.BoldEnd:
dialogText.boldActive = false;
break;
case TokenType.ItalicStart:
dialogText.italicActive = true;
break;
case TokenType.ItalicEnd:
dialogText.italicActive = false;
break;
case TokenType.ColorStart:
dialogText.colorActive = true;
dialogText.colorText = token.param;
break;
case TokenType.ColorEnd:
dialogText.colorActive = false;
break;
case TokenType.Wait:
float duration = 1f;
if (!Single.TryParse(token.param, out duration))
{
duration = 1f;
}
yield return StartCoroutine(WaitForSecondsOrInput(duration));
break;
case TokenType.WaitForInputNoClear:
OnWaitForInputTag(true);
yield return StartCoroutine(WaitForInput(null));
OnWaitForInputTag(false);
break;
case TokenType.WaitForInputAndClear:
OnWaitForInputTag(true);
yield return StartCoroutine(WaitForInput(null));
OnWaitForInputTag(false);
currentSpeed = writingSpeed;
dialogText.Clear();
StopVoiceOver();
break;
case TokenType.WaitOnPunctuation:
float newPunctuationPause = 0f;
if (!Single.TryParse(token.param, out newPunctuationPause))
{
newPunctuationPause = punctuationPause;
}
dialogText.punctuationPause = newPunctuationPause;
break;
case TokenType.Clear:
dialogText.Clear();
break;
case TokenType.Speed:
float newSpeed = 0;
if (!Single.TryParse(token.param, out newSpeed))
{
newSpeed = 0f;
}
dialogText.writingSpeed = newSpeed;
break;
case TokenType.Exit:
if (onExitTag != null)
{
Destroy(typingAudio);
onExitTag();
}
yield break;
case TokenType.Message:
FungusScript.BroadcastFungusMessage(token.param);
break;
}
// Update text writing
while (!dialogText.UpdateGlyphs(wasPointerClicked))
{
storyText.text = dialogText.GetDialogText();
yield return null;
}
storyText.text = dialogText.GetDialogText();
wasPointerClicked = false;
// Now process next token
}
Destroy(typingAudio);
if (onWritingComplete != null)
{
onWritingComplete();
}
yield break;
}
public virtual void Clear()
{
ClearStoryText();
// Reset control variables
currentSpeed = 60;
currentPunctuationPause = 0.25f;
boldActive = false;
italicActive = false;
colorActive = false;
colorText = "";
// Kill any active write coroutine
StopAllCoroutines();
}
protected virtual void ClearStoryText()
{
if (storyText != null)
{
storyText.text = "";
}
}
protected virtual IEnumerator WaitForInput(Action onInput)
{
while (!wasPointerClicked)
{
yield return null;
}
wasPointerClicked = false;
if (onInput != null)
{
onInput();
}
}
protected virtual IEnumerator WaitForSecondsOrInput(float duration)
{
float timer = duration;
while (timer > 0 && !wasPointerClicked)
{
timer -= Time.deltaTime;
yield return null;
}
wasPointerClicked = false;
}
protected virtual void OnWaitForInputTag(bool waiting)
{}
public virtual void OnPointerClick()
{
if (clickCooldownTimer == 0f)
{
wasPointerClicked = true;
}
}
public virtual void PlayVoiceOver(AudioClip voiceOverSound)
{
if (voiceOverAudio == null)
{
voiceOverAudio = gameObject.AddComponent<AudioSource>();
}
voiceOverAudio.clip = voiceOverSound;
voiceOverAudio.Play();
}
public virtual void StopVoiceOver()
{
if (voiceOverAudio)
{
Destroy(voiceOverAudio);
}
}
}
}