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.
686 lines
16 KiB
686 lines
16 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 static Character speakingCharacter; |
|
public static string prevStoryText; |
|
|
|
public float writingSpeed = 60; |
|
public AudioClip writingSound; |
|
[Range(0,1)] |
|
public float writingVolume = 1f; |
|
public bool loopWritingSound = true; |
|
public bool beepPerCharacter = false; |
|
public float slowBeepsAt = 10f; |
|
public float fastBeepsAt = 30f; |
|
public float punctuationPause = 0.25f; |
|
public bool alwaysFadeDialog = false; |
|
public float fadeDuration = 1f; |
|
public LeanTweenType fadeEaseType; |
|
public bool alwaysMoveDialog = false; |
|
public Vector2 startPosition; |
|
protected Vector2 endPosition; |
|
public float moveSpeed = 1000f; |
|
public LeanTweenType moveEaseType; |
|
|
|
[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; |
|
|
|
// The current target writing volume to move towards |
|
protected float targetWritingVolume; |
|
|
|
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; |
|
} |
|
|
|
// Fade writing sound |
|
AudioSource audio = GetComponent<AudioSource>(); |
|
if (audio != null) |
|
{ |
|
if (audio.volume == 0f && |
|
targetWritingVolume > 0) |
|
{ |
|
audio.Play(); |
|
} |
|
|
|
if (targetWritingVolume > audio.volume) |
|
{ |
|
audio.volume = targetWritingVolume; |
|
} |
|
else |
|
{ |
|
audio.volume = Mathf.MoveTowards(audio.volume, targetWritingVolume, Time.deltaTime * 8f); |
|
if (audio.volume == 0f) |
|
{ |
|
audio.Stop(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
public virtual void ShowDialog(bool visible) |
|
{ |
|
if (dialogCanvas != null) |
|
{ |
|
LeanTween.cancel(dialogCanvas.gameObject); |
|
CanvasGroup canvasGroup = dialogCanvas.GetComponent<CanvasGroup>(); |
|
if (canvasGroup != null) |
|
{ |
|
canvasGroup.alpha = 1; |
|
} |
|
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 SetTypingSoundVolume(bool audible) |
|
{ |
|
if (audible) |
|
{ |
|
targetWritingVolume = writingVolume; |
|
} |
|
else |
|
{ |
|
targetWritingVolume = 0f; |
|
} |
|
} |
|
|
|
public virtual void FadeInDialog() |
|
{ |
|
LeanTween.cancel(dialogCanvas.gameObject); |
|
CanvasGroup canvasGroup = dialogCanvas.GetComponent<CanvasGroup>(); |
|
if (canvasGroup != null) |
|
{ |
|
canvasGroup.alpha = 0; |
|
} |
|
dialogCanvas.gameObject.SetActive(true); |
|
if (fadeDuration == 0) fadeDuration = float.Epsilon; |
|
LeanTween.value(dialogCanvas.gameObject,0,1,fadeDuration).setEase(fadeEaseType).setOnUpdate( |
|
(float fadeAmount)=>{ |
|
if (canvasGroup != null) |
|
{ |
|
canvasGroup.alpha = fadeAmount; |
|
} |
|
} |
|
).setOnComplete( |
|
()=>{ |
|
if (canvasGroup != null) |
|
{ |
|
canvasGroup.alpha = 1; |
|
} |
|
} |
|
); |
|
} |
|
|
|
public virtual void MoveInDialog() |
|
{ |
|
endPosition = this.transform.position; |
|
float moveDuration = (Vector3.Distance(startPosition,this.transform.position)/moveSpeed); |
|
if (moveSpeed == 0) moveDuration = float.Epsilon; |
|
LeanTween.value(this.gameObject,(Vector2)startPosition,(Vector2)endPosition,moveDuration).setEase(moveEaseType).setOnUpdate( |
|
(Vector3 updatePosition)=>{ |
|
this.transform.position = updatePosition; |
|
} |
|
).setOnComplete( |
|
()=>{ |
|
this.transform.position = endPosition; |
|
} |
|
); |
|
} |
|
|
|
public virtual void FadeOutDialog() |
|
{ |
|
CanvasGroup canvasGroup = dialogCanvas.GetComponent<CanvasGroup>(); |
|
LeanTween.cancel(dialogCanvas.gameObject); |
|
if (fadeDuration == 0) fadeDuration = float.Epsilon; |
|
LeanTween.value(dialogCanvas.gameObject,1,0,fadeDuration).setEase(fadeEaseType).setOnUpdate( |
|
(float fadeAmount)=>{ |
|
if (canvasGroup != null) |
|
{ |
|
canvasGroup.alpha = fadeAmount; |
|
} |
|
} |
|
).setOnComplete( |
|
()=>{ |
|
dialogCanvas.gameObject.SetActive(false); |
|
if (canvasGroup != null) |
|
{ |
|
canvasGroup.alpha = 1; |
|
} |
|
} |
|
); |
|
} |
|
|
|
public virtual void MoveOutDialog() |
|
{ |
|
endPosition = this.transform.position; |
|
float moveDuration = (Vector3.Distance(startPosition,this.transform.position)/moveSpeed); |
|
if (moveSpeed == 0) moveDuration = float.Epsilon; |
|
LeanTween.value(this.gameObject,(Vector2)endPosition,(Vector2)startPosition,moveDuration).setEase(moveEaseType).setOnUpdate( |
|
(Vector3 updatePosition)=>{ |
|
this.transform.position = updatePosition; |
|
} |
|
).setOnComplete( |
|
()=>{ |
|
this.transform.position = endPosition; |
|
} |
|
); |
|
} |
|
|
|
public virtual void SetCharacter(Character character, Flowchart flowchart = null) |
|
{ |
|
if (character == null) |
|
{ |
|
if (characterImage != null) |
|
characterImage.gameObject.SetActive(false); |
|
if (nameText != null) |
|
nameText.text = ""; |
|
characterTypingSound = null; |
|
} |
|
else |
|
{ |
|
Character prevSpeakingCharacter = speakingCharacter; |
|
speakingCharacter = character; |
|
|
|
// Dim portraits of non-speaking characters |
|
foreach (Stage s in Stage.activeStages) |
|
{ |
|
if (s.dimPortraits) |
|
{ |
|
foreach (Character c in s.charactersOnStage) |
|
{ |
|
if (prevSpeakingCharacter != speakingCharacter) |
|
{ |
|
if (c != speakingCharacter) |
|
{ |
|
Portrait.Dim(c,s); |
|
} |
|
else |
|
{ |
|
Portrait.Undim(c,s); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
string characterName = character.nameText; |
|
|
|
if (characterName == "") |
|
{ |
|
// Use game object name as default |
|
characterName = character.name; |
|
} |
|
|
|
if (flowchart != null) |
|
{ |
|
characterName = flowchart.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.parentDialog = this; |
|
dialogText.writingSpeed = writingSpeed; |
|
dialogText.punctuationPause = punctuationPause; |
|
dialogText.beepPerCharacter = beepPerCharacter; |
|
dialogText.slowBeepsAt = slowBeepsAt; |
|
dialogText.fastBeepsAt = fastBeepsAt; |
|
|
|
AudioSource typingAudio = GetComponent<AudioSource>(); |
|
if (characterTypingSound != null || writingSound != null) |
|
{ |
|
if (characterTypingSound != null) |
|
{ |
|
typingAudio.clip = characterTypingSound; |
|
} |
|
else if (writingSound != null) |
|
{ |
|
typingAudio.clip = writingSound; |
|
} |
|
|
|
typingAudio.loop = loopWritingSound; |
|
|
|
// Start at full volume |
|
typingAudio.volume = writingVolume; |
|
SetTypingSoundVolume(true); |
|
|
|
typingAudio.Play(); |
|
} |
|
|
|
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.WaitOnPunctuationStart: |
|
float newPunctuationPause = 0f; |
|
if (!Single.TryParse(token.param, out newPunctuationPause)) |
|
{ |
|
newPunctuationPause = 0f; |
|
} |
|
dialogText.punctuationPause = newPunctuationPause; |
|
break; |
|
case TokenType.WaitOnPunctuationEnd: |
|
dialogText.punctuationPause = punctuationPause; |
|
break; |
|
|
|
case TokenType.Clear: |
|
dialogText.Clear(); |
|
break; |
|
|
|
case TokenType.SpeedStart: |
|
float newSpeed = 0; |
|
if (!Single.TryParse(token.param, out newSpeed)) |
|
{ |
|
newSpeed = 0f; |
|
} |
|
dialogText.writingSpeed = newSpeed; |
|
break; |
|
|
|
case TokenType.SpeedEnd: |
|
dialogText.writingSpeed = writingSpeed; |
|
break; |
|
|
|
case TokenType.Exit: |
|
if (onExitTag != null) |
|
{ |
|
prevStoryText = storyText.text; |
|
SetTypingSoundVolume(false); |
|
onExitTag(); |
|
} |
|
yield break; |
|
|
|
case TokenType.Message: |
|
Flowchart.BroadcastFungusMessage(token.param); |
|
break; |
|
case TokenType.VerticalPunch: |
|
float vPunchIntensity = 0; |
|
if (!Single.TryParse(token.param, out vPunchIntensity)) |
|
{ |
|
vPunchIntensity = 0f; |
|
} |
|
VerticalPunch(vPunchIntensity); |
|
break; |
|
case TokenType.HorizontalPunch: |
|
float hPunchIntensity = 0; |
|
if (!Single.TryParse(token.param, out hPunchIntensity)) |
|
{ |
|
hPunchIntensity = 0f; |
|
} |
|
HorizontalPunch(hPunchIntensity); |
|
break; |
|
case TokenType.Shake: |
|
float shakeIntensity = 0; |
|
if (!Single.TryParse(token.param, out shakeIntensity)) |
|
{ |
|
shakeIntensity = 0f; |
|
} |
|
Shake(shakeIntensity); |
|
break; |
|
case TokenType.Shiver: |
|
float shiverIntensity = 0; |
|
if (!Single.TryParse(token.param, out shiverIntensity)) |
|
{ |
|
shiverIntensity = 0f; |
|
} |
|
Shiver(shiverIntensity); |
|
break; |
|
case TokenType.Flash: |
|
float flashDuration = 0; |
|
if (!Single.TryParse(token.param, out flashDuration)) |
|
{ |
|
flashDuration = 0f; |
|
} |
|
Flash(flashDuration); |
|
break; |
|
case TokenType.Audio: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.PlayOneShot(audioSource.clip); |
|
} |
|
} |
|
break; |
|
case TokenType.AudioLoop: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.Play(); |
|
audioSource.loop = true; |
|
} |
|
} |
|
break; |
|
case TokenType.AudioPause: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.Pause (); |
|
} |
|
} |
|
break; |
|
case TokenType.AudioStop: |
|
{ |
|
AudioSource audioSource = FindAudio(token.param); |
|
if (audioSource != null) |
|
{ |
|
audioSource.Pause (); |
|
} |
|
} |
|
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 |
|
} |
|
|
|
prevStoryText = storyText.text; |
|
|
|
SetTypingSoundVolume(false); |
|
|
|
if (onWritingComplete != null) |
|
{ |
|
onWritingComplete(); |
|
} |
|
|
|
yield break; |
|
} |
|
|
|
protected virtual AudioSource FindAudio(string audioObjectName) |
|
{ |
|
GameObject go = GameObject.Find(audioObjectName); |
|
if (go == null) |
|
{ |
|
return null; |
|
} |
|
|
|
return go.GetComponent<AudioSource>(); |
|
} |
|
|
|
protected virtual void VerticalPunch(float intensity) |
|
{ |
|
iTween.ShakePosition(this.gameObject, new Vector3(0f, intensity, 0f), 0.5f); |
|
} |
|
|
|
protected virtual void HorizontalPunch(float intensity) |
|
{ |
|
iTween.ShakePosition(this.gameObject, new Vector3(intensity, 0f, 0f), 0.5f); |
|
} |
|
|
|
protected virtual void Shake(float intensity) |
|
{ |
|
iTween.ShakePosition(this.gameObject, new Vector3(intensity, intensity, 0f), 0.5f); |
|
} |
|
|
|
protected virtual void Shiver(float intensity) |
|
{ |
|
iTween.ShakePosition(this.gameObject, new Vector3(intensity, intensity, 0f), 1f); |
|
} |
|
|
|
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); |
|
}); |
|
} |
|
|
|
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) |
|
{ |
|
// Stop all tweening portraits |
|
foreach( Character c in Character.activeCharacters ) |
|
{ |
|
if (c.state.portraitImage != null) |
|
{ |
|
if (LeanTween.isTweening(c.state.portraitObj)) |
|
{ |
|
LeanTween.cancel(c.state.portraitObj, true); |
|
c.state.portraitImage.material.SetFloat( "_Fade", 1 ); |
|
Portrait.SetRectTransform(c.state.portraitImage.rectTransform, c.state.position); |
|
if (c.state.dimmed == true) |
|
{ |
|
c.state.portraitImage.material.SetColor("_Color", new Color(0.5f,0.5f,0.5f,1f)); |
|
} |
|
else |
|
{ |
|
c.state.portraitImage.material.SetColor("_Color", new Color(1f,1f,1f,1f)); |
|
} |
|
} |
|
} |
|
} |
|
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); |
|
} |
|
} |
|
} |
|
|
|
}
|
|
|