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