// This code is part of the Fungus library (http://fungusgames.com) maintained by Chris Gregan (http://twitter.com/gofungus).
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections;
namespace Fungus
{
///
/// Display story text in a visual novel style dialog box.
///
public class SayDialog : MonoBehaviour
{
[Tooltip("Duration to fade dialogue in/out")]
[SerializeField] protected float fadeDuration = 0.25f;
[Tooltip("The continue button UI object")]
[SerializeField] protected Button continueButton;
[Tooltip("The canvas UI object")]
[SerializeField] protected Canvas dialogCanvas;
[Tooltip("The name text UI object")]
[SerializeField] protected Text nameText;
[Tooltip("The story text UI object")]
[SerializeField] protected Text storyText;
public virtual Text StoryText { get { return storyText; } }
[Tooltip("The character UI object")]
[SerializeField] protected Image characterImage;
public virtual Image CharacterImage { get { return characterImage; } }
[Tooltip("Adjust width of story text when Character Image is displayed (to avoid overlapping)")]
[SerializeField] protected bool fitTextWithImage = true;
protected float startStoryTextWidth;
protected float startStoryTextInset;
protected WriterAudio writerAudio;
protected Writer writer;
protected CanvasGroup canvasGroup;
protected bool fadeWhenDone = true;
protected float targetAlpha = 0f;
protected float fadeCoolDownTimer = 0f;
protected Sprite currentCharacterImage;
// Most recent speaking character
protected static Character speakingCharacter;
protected Writer GetWriter()
{
if (writer != null)
{
return writer;
}
writer = GetComponent();
if (writer == null)
{
writer = gameObject.AddComponent();
}
return writer;
}
protected CanvasGroup GetCanvasGroup()
{
if (canvasGroup != null)
{
return canvasGroup;
}
canvasGroup = GetComponent();
if (canvasGroup == null)
{
canvasGroup = gameObject.AddComponent();
}
return canvasGroup;
}
protected WriterAudio GetWriterAudio()
{
if (writerAudio != null)
{
return writerAudio;
}
writerAudio = GetComponent();
if (writerAudio == null)
{
writerAudio = gameObject.AddComponent();
}
return writerAudio;
}
protected void Start()
{
// Dialog always starts invisible, will be faded in when writing starts
GetCanvasGroup().alpha = 0f;
// Add a raycaster if none already exists so we can handle dialog input
GraphicRaycaster raycaster = GetComponent();
if (raycaster == null)
{
gameObject.AddComponent();
}
// It's possible that SetCharacterImage() has already been called from the
// Start method of another component, so check that no image has been set yet.
// Same for nameText.
if (nameText != null && nameText.text == "")
{
SetCharacterName("", Color.white);
}
if (currentCharacterImage == null)
{
// Character image is hidden by default.
SetCharacterImage(null);
}
}
protected virtual void LateUpdate()
{
UpdateAlpha();
if (continueButton != null)
{
continueButton.gameObject.SetActive( GetWriter().IsWaitingForInput );
}
}
protected virtual void UpdateAlpha()
{
if (GetWriter().IsWriting)
{
targetAlpha = 1f;
fadeCoolDownTimer = 0.1f;
}
else if (fadeWhenDone && Mathf.Approximately(fadeCoolDownTimer, 0f))
{
targetAlpha = 0f;
}
else
{
// Add a short delay before we start fading in case there's another Say command in the next frame or two.
// This avoids a noticeable flicker between consecutive Say commands.
fadeCoolDownTimer = Mathf.Max(0f, fadeCoolDownTimer - Time.deltaTime);
}
CanvasGroup canvasGroup = GetCanvasGroup();
if (fadeDuration <= 0f)
{
canvasGroup.alpha = targetAlpha;
}
else
{
float delta = (1f / fadeDuration) * Time.deltaTime;
float alpha = Mathf.MoveTowards(canvasGroup.alpha, targetAlpha, delta);
canvasGroup.alpha = alpha;
if (alpha <= 0f)
{
// Deactivate dialog object once invisible
gameObject.SetActive(false);
}
}
}
protected virtual void ClearStoryText()
{
if (storyText != null)
{
storyText.text = "";
}
}
#region Public members
///
/// Currently active Say Dialog used to display Say text
///
public static SayDialog ActiveSayDialog { get; set; }
///
/// Returns a SayDialog by searching for one in the scene or creating one if none exists.
///
public static SayDialog GetSayDialog()
{
if (ActiveSayDialog == null)
{
// Use first Say Dialog found in the scene (if any)
SayDialog sd = GameObject.FindObjectOfType();
if (sd != null)
{
ActiveSayDialog = sd;
}
if (ActiveSayDialog == null)
{
// Auto spawn a say dialog object from the prefab
GameObject prefab = Resources.Load("Prefabs/SayDialog");
if (prefab != null)
{
GameObject go = Instantiate(prefab) as GameObject;
go.SetActive(false);
go.name = "SayDialog";
ActiveSayDialog = go.GetComponent();
}
}
}
return ActiveSayDialog;
}
///
/// Stops all active portrait tweens.
///
public static void StopPortraitTweens()
{
// Stop all tweening portraits
foreach( Character c in Character.ActiveCharacters )
{
if (c.State.portraitImage != null)
{
if (LeanTween.isTweening(c.State.portraitImage.gameObject))
{
LeanTween.cancel(c.State.portraitImage.gameObject, true);
PortraitController.SetRectTransform(c.State.portraitImage.rectTransform, c.State.position);
if (c.State.dimmed == true)
{
c.State.portraitImage.color = new Color(0.5f, 0.5f, 0.5f, 1f);
}
else
{
c.State.portraitImage.color = Color.white;
}
}
}
}
}
///
/// Sets the active state of the Say Dialog gameobject.
///
public virtual void SetActive(bool state)
{
gameObject.SetActive(state);
}
///
/// Sets the active speaking character.
///
/// The active speaking character.
/// An optional Flowchart to use for variable substitution in the character name string.
public virtual void SetCharacter(Character character, Flowchart flowchart = null)
{
if (character == null)
{
if (characterImage != null)
{
characterImage.gameObject.SetActive(false);
}
if (nameText != null)
{
nameText.text = "";
}
speakingCharacter = null;
}
else
{
var prevSpeakingCharacter = speakingCharacter;
speakingCharacter = character;
// Dim portraits of non-speaking characters
foreach (Stage stage in Stage.ActiveStages)
{
if (stage.DimPortraits)
{
foreach (var c in stage.CharactersOnStage)
{
if (prevSpeakingCharacter != speakingCharacter)
{
if (c != null && !c.Equals(speakingCharacter))
{
stage.SetDimmed(c, true);
}
else
{
stage.SetDimmed(c, false);
}
}
}
}
}
string characterName = character.NameText;
if (characterName == "")
{
// Use game object name as default
characterName = character.GetObjectName();
}
if (flowchart != null)
{
characterName = flowchart.SubstituteVariables(characterName);
}
SetCharacterName(characterName, character.NameColor);
}
}
///
/// Sets the character image to display on the Say Dialog.
///
public virtual void SetCharacterImage(Sprite image)
{
if (characterImage == null)
{
return;
}
if (image != null)
{
characterImage.sprite = image;
characterImage.gameObject.SetActive(true);
currentCharacterImage = image;
}
else
{
characterImage.gameObject.SetActive(false);
if (startStoryTextWidth != 0)
{
storyText.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left,
startStoryTextInset,
startStoryTextWidth);
}
}
// Adjust story text box to not overlap image rect
if (fitTextWithImage &&
storyText != null &&
characterImage.gameObject.activeSelf)
{
if (startStoryTextWidth == 0)
{
startStoryTextWidth = storyText.rectTransform.rect.width;
startStoryTextInset = storyText.rectTransform.offsetMin.x;
}
// Clamp story text to left or right depending on relative position of the character image
if (storyText.rectTransform.position.x < characterImage.rectTransform.position.x)
{
storyText.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left,
startStoryTextInset,
startStoryTextWidth - characterImage.rectTransform.rect.width);
}
else
{
storyText.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Right,
startStoryTextInset,
startStoryTextWidth - characterImage.rectTransform.rect.width);
}
}
}
///
/// Sets the character name to display on the Say Dialog.
///
public virtual void SetCharacterName(string name, Color color)
{
if (nameText != null)
{
nameText.text = name;
nameText.color = color;
}
}
///
/// Write a line of story text to the Say Dialog. Starts coroutine automatically.
///
/// The text to display.
/// Clear any previous text in the Say Dialog.
/// Wait for player input before continuing once text is written.
/// Fade out the Say Dialog when writing and player input has finished.
/// Stop any existing voiceover audio before writing starts.
/// Voice over audio clip to play.
/// Callback to execute when writing and player input have finished.
public virtual void Say(string text, bool clearPrevious, bool waitForInput, bool fadeWhenDone, bool stopVoiceover, AudioClip voiceOverClip, Action onComplete)
{
StartCoroutine(DoSay(text, clearPrevious, waitForInput, fadeWhenDone, stopVoiceover, voiceOverClip, onComplete));
}
///
/// Write a line of story text to the Say Dialog. Must be started as a coroutine.
///
/// The text to display.
/// Clear any previous text in the Say Dialog.
/// Wait for player input before continuing once text is written.
/// Fade out the Say Dialog when writing and player input has finished.
/// Stop any existing voiceover audio before writing starts.
/// Voice over audio clip to play.
/// Callback to execute when writing and player input have finished.
public virtual IEnumerator DoSay(string text, bool clearPrevious, bool waitForInput, bool fadeWhenDone, bool stopVoiceover, AudioClip voiceOverClip, Action onComplete)
{
var writer = GetWriter();
if (writer.IsWriting || writer.IsWaitingForInput)
{
writer.Stop();
while (writer.IsWriting || writer.IsWaitingForInput)
{
yield return null;
}
}
gameObject.SetActive(true);
this.fadeWhenDone = fadeWhenDone;
// Voice over clip takes precedence over a character sound effect if provided
AudioClip soundEffectClip = null;
if (voiceOverClip != null)
{
WriterAudio writerAudio = GetWriterAudio();
writerAudio.OnVoiceover(voiceOverClip);
}
else if (speakingCharacter != null)
{
soundEffectClip = speakingCharacter.SoundEffect;
}
yield return StartCoroutine(writer.Write(text, clearPrevious, waitForInput, stopVoiceover, soundEffectClip, onComplete));
}
///
/// Tell the Say Dialog to fade out once writing and player input have finished.
///
public virtual bool FadeWhenDone { set { fadeWhenDone = value; } }
///
/// Stop the Say Dialog while its writing text.
///
public virtual void Stop()
{
fadeWhenDone = true;
GetWriter().Stop();
}
///
/// Stops writing text and clears the Say Dialog.
///
public virtual void Clear()
{
ClearStoryText();
// Kill any active write coroutine
StopAllCoroutines();
}
#endregion
}
}