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.
692 lines
15 KiB
692 lines
15 KiB
using UnityEngine; |
|
using UnityEngine.UI; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System; |
|
|
|
namespace Fungus |
|
{ |
|
|
|
/** |
|
* Implement this interface to be notified about Writer events |
|
*/ |
|
public interface IWriterListener |
|
{ |
|
// Called when the Writer starts writing new text |
|
// An optional audioClip sound effect can be supplied (e.g. for voiceover) |
|
void OnStart(AudioClip audioClip); |
|
|
|
// Called when the Writer has paused writing text (e.g. on a {wi} tag) |
|
void OnPause(); |
|
|
|
// Called when the Writer has resumed writing text |
|
void OnResume(); |
|
|
|
// Called when the Writer has finshed writing text |
|
void OnEnd(); |
|
|
|
// Called every time the Writer writes a new character glyph |
|
void OnGlyph(); |
|
} |
|
|
|
public class Writer : MonoBehaviour, IDialogInputListener |
|
{ |
|
[Tooltip("Gameobject containing a Text, Inout Field or Text Mesh object to write to")] |
|
public GameObject targetTextObject; |
|
|
|
[Tooltip("Writing characters per second")] |
|
public float writingSpeed = 60; |
|
|
|
[Tooltip("Pause duration for punctuation characters")] |
|
public float punctuationPause = 0.25f; |
|
|
|
[Tooltip("Color of text that has not been revealed yet")] |
|
public Color hiddenTextColor = new Color(1,1,1,0); |
|
|
|
[Tooltip("Write one word at a time rather one character at a time")] |
|
public bool writeWholeWords = false; |
|
|
|
// This property is true when the writer is waiting for user input to continue |
|
[System.NonSerialized] |
|
public bool isWaitingForInput; |
|
|
|
// This property is true when the writer is writing text or waiting (i.e. still processing tokens) |
|
[System.NonSerialized] |
|
public bool isWriting; |
|
|
|
protected float currentWritingSpeed; |
|
protected float currentPunctuationPause; |
|
protected Text textUI; |
|
protected InputField inputField; |
|
protected TextMesh textMesh; |
|
protected bool boldActive = false; |
|
protected bool italicActive = false; |
|
protected bool colorActive = false; |
|
protected string colorText = ""; |
|
protected bool inputFlag; |
|
|
|
protected List<IWriterListener> writerListeners = new List<IWriterListener>(); |
|
|
|
public string text |
|
{ |
|
get |
|
{ |
|
if (textUI != null) |
|
{ |
|
return textUI.text; |
|
} |
|
else if (inputField != null) |
|
{ |
|
return inputField.text; |
|
} |
|
else if (textMesh != null) |
|
{ |
|
return textMesh.text; |
|
} |
|
return ""; |
|
} |
|
|
|
set |
|
{ |
|
if (textUI != null) |
|
{ |
|
textUI.text = value; |
|
} |
|
else if (inputField != null) |
|
{ |
|
inputField.text = value; |
|
} |
|
else if (textMesh != null) |
|
{ |
|
textMesh.text = value; |
|
} |
|
} |
|
} |
|
|
|
protected virtual void Awake() |
|
{ |
|
GameObject go = targetTextObject; |
|
if (go == null) |
|
{ |
|
go = gameObject; |
|
} |
|
|
|
textUI = go.GetComponent<Text>(); |
|
inputField = go.GetComponent<InputField>(); |
|
textMesh = go.GetComponent<TextMesh>(); |
|
|
|
// Cache the list of child writer listeners |
|
foreach (Component component in GetComponentsInChildren<Component>()) |
|
{ |
|
IWriterListener writerListener = component as IWriterListener; |
|
if (writerListener != null) |
|
{ |
|
writerListeners.Add(writerListener); |
|
} |
|
} |
|
} |
|
|
|
public virtual bool HasTextObject() |
|
{ |
|
return (textUI != null || inputField != null || textMesh != null); |
|
} |
|
|
|
public virtual bool SupportsRichText() |
|
{ |
|
if (textUI != null) |
|
{ |
|
return textUI.supportRichText; |
|
} |
|
if (inputField != null) |
|
{ |
|
return false; |
|
} |
|
if (textMesh != null) |
|
{ |
|
return textMesh.richText; |
|
} |
|
return false; |
|
} |
|
|
|
protected virtual string OpenMarkup() |
|
{ |
|
string tagText = ""; |
|
|
|
if (SupportsRichText()) |
|
{ |
|
if (colorActive) |
|
{ |
|
tagText += "<color=" + colorText + ">"; |
|
} |
|
if (boldActive) |
|
{ |
|
tagText += "<b>"; |
|
} |
|
if (italicActive) |
|
{ |
|
tagText += "<i>"; |
|
} |
|
} |
|
|
|
return tagText; |
|
} |
|
|
|
protected virtual string CloseMarkup() |
|
{ |
|
string closeText = ""; |
|
|
|
if (SupportsRichText()) |
|
{ |
|
if (italicActive) |
|
{ |
|
closeText += "</i>"; |
|
} |
|
if (boldActive) |
|
{ |
|
closeText += "</b>"; |
|
} |
|
if (colorActive) |
|
{ |
|
closeText += "</color>"; |
|
} |
|
} |
|
|
|
return closeText; |
|
} |
|
|
|
public virtual void SetTextColor(Color textColor) |
|
{ |
|
if (textUI != null) |
|
{ |
|
textUI.color = textColor; |
|
} |
|
else if (inputField != null) |
|
{ |
|
if (inputField.textComponent != null) |
|
{ |
|
inputField.textComponent.color = textColor; |
|
} |
|
} |
|
else if (textMesh != null) |
|
{ |
|
textMesh.color = textColor; |
|
} |
|
} |
|
|
|
public virtual void SetTextAlpha(float textAlpha) |
|
{ |
|
if (textUI != null) |
|
{ |
|
Color tempColor = textUI.color; |
|
tempColor.a = textAlpha; |
|
textUI.color = tempColor; |
|
} |
|
else if (inputField != null) |
|
{ |
|
if (inputField.textComponent != null) |
|
{ |
|
Color tempColor = inputField.textComponent.color; |
|
tempColor.a = textAlpha; |
|
inputField.textComponent.color = tempColor; |
|
} |
|
} |
|
else if (textMesh != null) |
|
{ |
|
Color tempColor = textMesh.color; |
|
tempColor.a = textAlpha; |
|
textMesh.color = tempColor; |
|
} |
|
} |
|
|
|
public virtual void Write(string content, bool clear, bool waitForInput, AudioClip audioClip, Action onComplete) |
|
{ |
|
if (clear) |
|
{ |
|
this.text = ""; |
|
} |
|
|
|
if (!HasTextObject()) |
|
{ |
|
return; |
|
} |
|
|
|
// If this clip is null then WriterAudio will play the default sound effect (if any) |
|
NotifyStart(audioClip); |
|
|
|
string tokenText = content; |
|
if (waitForInput) |
|
{ |
|
tokenText += "{wi}"; |
|
} |
|
|
|
TextTagParser tagParser = new TextTagParser(); |
|
List<TextTagParser.Token> tokens = tagParser.Tokenize(tokenText); |
|
|
|
StartCoroutine(ProcessTokens(tokens, onComplete)); |
|
} |
|
|
|
protected virtual IEnumerator ProcessTokens(List<TextTagParser.Token> tokens, Action onComplete) |
|
{ |
|
// Reset control members |
|
boldActive = false; |
|
italicActive = false; |
|
colorActive = false; |
|
colorText = ""; |
|
currentPunctuationPause = punctuationPause; |
|
currentWritingSpeed = writingSpeed; |
|
|
|
isWriting = true; |
|
|
|
foreach (TextTagParser.Token token in tokens) |
|
{ |
|
bool exit = false; |
|
|
|
switch (token.type) |
|
{ |
|
case TextTagParser.TokenType.Words: |
|
yield return StartCoroutine(DoWords(token.param)); |
|
break; |
|
|
|
case TextTagParser.TokenType.BoldStart: |
|
boldActive = true; |
|
break; |
|
|
|
case TextTagParser.TokenType.BoldEnd: |
|
boldActive = false; |
|
break; |
|
|
|
case TextTagParser.TokenType.ItalicStart: |
|
italicActive = true; |
|
break; |
|
|
|
case TextTagParser.TokenType.ItalicEnd: |
|
italicActive = false; |
|
break; |
|
|
|
case TextTagParser.TokenType.ColorStart: |
|
colorActive = true; |
|
colorText = token.param; |
|
break; |
|
|
|
case TextTagParser.TokenType.ColorEnd: |
|
colorActive = false; |
|
break; |
|
|
|
case TextTagParser.TokenType.Wait: |
|
yield return StartCoroutine(DoWait(token.param)); |
|
break; |
|
|
|
case TextTagParser.TokenType.WaitForInputNoClear: |
|
yield return StartCoroutine(DoWaitForInput(false)); |
|
break; |
|
|
|
case TextTagParser.TokenType.WaitForInputAndClear: |
|
yield return StartCoroutine(DoWaitForInput(true)); |
|
break; |
|
|
|
case TextTagParser.TokenType.WaitOnPunctuationStart: |
|
if (!Single.TryParse(token.param, out currentPunctuationPause)) |
|
{ |
|
currentPunctuationPause = punctuationPause; |
|
} |
|
break; |
|
|
|
case TextTagParser.TokenType.WaitOnPunctuationEnd: |
|
currentPunctuationPause = punctuationPause; |
|
break; |
|
|
|
case TextTagParser.TokenType.Clear: |
|
text = ""; |
|
break; |
|
|
|
case TextTagParser.TokenType.SpeedStart: |
|
if (!Single.TryParse(token.param, out currentWritingSpeed)) |
|
{ |
|
currentWritingSpeed = writingSpeed; |
|
} |
|
break; |
|
|
|
case TextTagParser.TokenType.SpeedEnd: |
|
currentWritingSpeed = writingSpeed; |
|
break; |
|
|
|
case TextTagParser.TokenType.Exit: |
|
exit = true; |
|
break; |
|
|
|
case TextTagParser.TokenType.Message: |
|
Flowchart.BroadcastFungusMessage(token.param); |
|
break; |
|
|
|
case TextTagParser.TokenType.VerticalPunch: |
|
float vintensity; |
|
if (!Single.TryParse(token.param, out vintensity)) |
|
{ |
|
vintensity = 10f; |
|
} |
|
Punch(new Vector3(0, vintensity, 0), 0.5f); |
|
break; |
|
|
|
case TextTagParser.TokenType.HorizontalPunch: |
|
float hintensity; |
|
if (!Single.TryParse(token.param, out hintensity)) |
|
{ |
|
hintensity = 10f; |
|
} |
|
Punch(new Vector3(hintensity, 0, 0), 0.5f); |
|
break; |
|
|
|
case TextTagParser.TokenType.Punch: |
|
float intensity; |
|
if (!Single.TryParse(token.param, out intensity)) |
|
{ |
|
intensity = 10f; |
|
} |
|
Punch(new Vector3(intensity, intensity, 0), 0.5f); |
|
break; |
|
|
|
case TextTagParser.TokenType.Flash: |
|
float flashDuration; |
|
if (!Single.TryParse(token.param, out flashDuration)) |
|
{ |
|
flashDuration = 0.2f; |
|
} |
|
Flash(flashDuration); |
|
break; |
|
|
|
case TextTagParser.TokenType.Audio: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.PlayOneShot(audioSource.clip); |
|
} |
|
} |
|
break; |
|
|
|
case TextTagParser.TokenType.AudioLoop: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.Play(); |
|
audioSource.loop = true; |
|
} |
|
} |
|
break; |
|
|
|
case TextTagParser.TokenType.AudioPause: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.Pause(); |
|
} |
|
} |
|
break; |
|
|
|
case TextTagParser.TokenType.AudioStop: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.Stop(); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
if (exit) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
inputFlag = false; |
|
isWriting = false; |
|
|
|
NotifyEnd(); |
|
|
|
if (onComplete != null) |
|
{ |
|
onComplete(); |
|
} |
|
} |
|
|
|
protected virtual IEnumerator DoWords(string param) |
|
{ |
|
string startText = text; |
|
string openText = OpenMarkup(); |
|
string closeText = CloseMarkup(); |
|
|
|
float timeAccumulator = Time.deltaTime; |
|
|
|
for (int i = 0; i < param.Length; ++i) |
|
{ |
|
string left = ""; |
|
string right = ""; |
|
|
|
PartitionString(writeWholeWords, param, i, out left, out right); |
|
text = ConcatenateString(startText, openText, closeText, left, right); |
|
|
|
NotifyGlyph(); |
|
|
|
// No delay if user has clicked |
|
if (inputFlag) |
|
{ |
|
continue; |
|
} |
|
|
|
// Punctuation pause |
|
if (left.Length > 0 && |
|
IsPunctuation(left.Substring(left.Length - 1)[0])) |
|
{ |
|
yield return StartCoroutine(DoWait(currentPunctuationPause)); |
|
} |
|
|
|
// Delay between characters |
|
if (currentWritingSpeed > 0f) |
|
{ |
|
if (timeAccumulator > 0f) |
|
{ |
|
timeAccumulator -= 1f / currentWritingSpeed; |
|
} |
|
else |
|
{ |
|
yield return new WaitForSeconds(1f / currentWritingSpeed); |
|
} |
|
} |
|
} |
|
} |
|
|
|
protected void PartitionString(bool wholeWords, string inputString, int i, out string left, out string right) |
|
{ |
|
left = ""; |
|
right = ""; |
|
|
|
if (wholeWords) |
|
{ |
|
// Look ahead to find next whitespace or end of string |
|
for (int j = i; j < inputString.Length; ++j) |
|
{ |
|
if (Char.IsWhiteSpace(inputString[j]) || |
|
j == inputString.Length - 1) |
|
{ |
|
left = inputString.Substring(0, j + 1); |
|
right = inputString.Substring(j + 1, inputString.Length - j - 1); |
|
break; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
left = inputString.Substring(0, i + 1); |
|
right = inputString.Substring(i + 1); |
|
} |
|
} |
|
|
|
protected string ConcatenateString(string startText, string openText, string closeText, string leftText, string rightText) |
|
{ |
|
string tempText = startText + openText + leftText + closeText; |
|
|
|
Color32 c = hiddenTextColor; |
|
string hiddenColor = String.Format("#{0:X2}{1:X2}{2:X2}{3:X2}", c.r, c.g, c.b, c.a); |
|
|
|
// Make right hand side text hidden |
|
if (rightText.Length > 0) |
|
{ |
|
tempText += "<color=" + hiddenColor + ">" + rightText + "</color>"; |
|
} |
|
|
|
return tempText; |
|
} |
|
|
|
public virtual string GetTagHelp() |
|
{ |
|
return ""; |
|
} |
|
|
|
protected virtual IEnumerator DoWait(string param) |
|
{ |
|
float duration = 1f; |
|
if (!Single.TryParse(param, out duration)) |
|
{ |
|
duration = 1f; |
|
} |
|
|
|
NotifyPause(); |
|
|
|
float timeRemaining = duration; |
|
while (timeRemaining > 0f && !inputFlag) |
|
{ |
|
timeRemaining -= Time.deltaTime; |
|
yield return null; |
|
} |
|
|
|
NotifyResume(); |
|
} |
|
|
|
protected virtual IEnumerator DoWait(float duration) |
|
{ |
|
NotifyPause(); |
|
|
|
float timeRemaining = duration; |
|
while (timeRemaining > 0f && !inputFlag) |
|
{ |
|
timeRemaining -= Time.deltaTime; |
|
yield return null; |
|
} |
|
|
|
NotifyResume(); |
|
} |
|
|
|
protected virtual IEnumerator DoWaitForInput(bool clear) |
|
{ |
|
NotifyPause(); |
|
|
|
inputFlag = false; |
|
isWaitingForInput = true; |
|
|
|
while (!inputFlag) |
|
{ |
|
yield return null; |
|
} |
|
|
|
isWaitingForInput = false; |
|
inputFlag = false; |
|
|
|
if (clear) |
|
{ |
|
textUI.text = ""; |
|
} |
|
|
|
NotifyResume(); |
|
} |
|
|
|
protected virtual bool IsPunctuation(char character) |
|
{ |
|
return character == '.' || |
|
character == '?' || |
|
character == '!' || |
|
character == ',' || |
|
character == ':' || |
|
character == ';' || |
|
character == ')'; |
|
} |
|
|
|
protected virtual void Punch(Vector3 axis, float time) |
|
{ |
|
iTween.ShakePosition(this.gameObject, axis, time); |
|
} |
|
|
|
protected virtual void Flash(float duration) |
|
{ |
|
CameraController cameraController = CameraController.GetInstance(); |
|
cameraController.screenFadeTexture = CameraController.CreateColorTexture(new Color(1f,1f,1f,1f), 32, 32); |
|
cameraController.Fade(1f, duration, delegate { |
|
cameraController.screenFadeTexture = CameraController.CreateColorTexture(new Color(1f,1f,1f,1f), 32, 32); |
|
cameraController.Fade(0f, duration, null); |
|
}); |
|
} |
|
|
|
protected virtual AudioSource FindAudio(string audioObjectName) |
|
{ |
|
GameObject go = GameObject.Find(audioObjectName); |
|
if (go == null) |
|
{ |
|
return null; |
|
} |
|
|
|
return go.GetComponent<AudioSource>(); |
|
} |
|
|
|
protected virtual void NotifyStart(AudioClip audioClip) |
|
{ |
|
foreach (IWriterListener writerListener in writerListeners) |
|
{ |
|
writerListener.OnStart(audioClip); |
|
} |
|
} |
|
|
|
protected virtual void NotifyPause() |
|
{ |
|
foreach (IWriterListener writerListener in writerListeners) |
|
{ |
|
writerListener.OnPause(); |
|
} |
|
} |
|
|
|
protected virtual void NotifyResume() |
|
{ |
|
foreach (IWriterListener writerListener in writerListeners) |
|
{ |
|
writerListener.OnResume(); |
|
} |
|
} |
|
|
|
protected virtual void NotifyEnd() |
|
{ |
|
foreach (IWriterListener writerListener in writerListeners) |
|
{ |
|
writerListener.OnEnd(); |
|
} |
|
} |
|
|
|
protected virtual void NotifyGlyph() |
|
{ |
|
foreach (IWriterListener writerListener in writerListeners) |
|
{ |
|
writerListener.OnGlyph(); |
|
} |
|
} |
|
|
|
// |
|
// IDialogInputListener implementation |
|
// |
|
public virtual void OnNextLineEvent() |
|
{ |
|
inputFlag = true; |
|
} |
|
} |
|
|
|
}
|
|
|