// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using System.Collections; using System.Collections.Generic; namespace PixelCrushers.DialogueSystem { [AddComponentMenu("")] // Use wrapper. public class StandardDialogueUI : CanvasDialogueUI, IStandardDialogueUI { #region Serialized Fields public StandardUIAlertControls alertUIElements; public StandardUIDialogueControls conversationUIElements; public StandardUIQTEControls QTEIndicatorElements; [Tooltip("Add an EventSystem if one isn't in the scene.")] public bool addEventSystemIfNeeded = true; [Tooltip("Check in Awake if panels are properly assigned. Untick to suppress warnings.")] public bool verifyPanelAssignments = true; #endregion #region Properties & Private Fields private Queue m_alertQueue = new Queue(); private StandardUIRoot m_uiRoot = new StandardUIRoot(); private WaitForEndOfFrame endOfFrame = CoroutineUtility.endOfFrame; public override AbstractUIRoot uiRootControls { get { return m_uiRoot; } } public override AbstractUIAlertControls alertControls { get { return alertUIElements; } } public override AbstractDialogueUIControls dialogueControls { get { return conversationUIElements; } } public override AbstractUIQTEControls qteControls { get { return QTEIndicatorElements; } } protected Queue alertQueue { get { return m_alertQueue; } } protected Coroutine closeCoroutine = null; protected const float WaitForOpenTimeoutDuration = 8f; #endregion #region Initialization /// /// Sets up the component. /// public override void Awake() { base.Awake(); VerifyAssignments(); conversationUIElements.Initialize(); alertUIElements.HideImmediate(); conversationUIElements.HideImmediate(); QTEIndicatorElements.HideImmediate(); } private void VerifyAssignments() { if (addEventSystemIfNeeded) UITools.RequireEventSystem(); if (DialogueDebug.logWarnings && verifyPanelAssignments) { if (alertUIElements.alertText.gameObject == null) Debug.LogWarning("Dialogue System: No UI text element is assigned to Standard Dialogue UI's Alert UI Elements.", this); if (conversationUIElements.subtitlePanels.Length == 0) Debug.LogWarning("Dialogue System: No subtitle panels are assigned to Standard Dialogue UI.", this); if (conversationUIElements.menuPanels.Length == 0) Debug.LogWarning("Dialogue System: No response menu panels are assigned to Standard Dialogue UI.", this); } } #if UNITY_5_3 // SceneManager.sceneLoaded wasn't implemented for all Unity 5.3.x versions. public void OnLevelWasLoaded(int level) { if (addEventSystemIfNeeded) UITools.RequireEventSystem(); } public virtual void OnEnable() { } public virtual void OnDisable() { } #else public virtual void OnEnable() { UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded; UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded; } public virtual void OnDisable() { UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded; } public void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode) { if (addEventSystemIfNeeded) UITools.RequireEventSystem(); } #endif public override void Open() { if (closeCoroutine != null) { StopCoroutine(closeCoroutine); closeCoroutine = null; } base.Open(); conversationUIElements.OpenSubtitlePanelsOnStart(this); conversationUIElements.ClearSubtitleTextOnConversationStart(); } public override void Close() { if (conversationUIElements.waitForClose && (AreAnyPanelsClosing() || !IsMainPanelClosed())) { closeCoroutine = StartCoroutine(CloseAfterPanelsAreClosed()); } else { CloseNow(); } } public virtual void CloseImmediately() { CloseNow(); } protected virtual void CloseNow() { base.Close(); conversationUIElements.ClearCaches(); } protected virtual IEnumerator CloseAfterPanelsAreClosed() { // Close subtitle/menu panels and wait for them to finish: conversationUIElements.ClosePanels(); while (AreAnyPanelsClosing()) { yield return null; } // Close main panel and wait for it to finish: if (conversationUIElements.mainPanel != null && !conversationUIElements.dontDeactivateMainPanel) { if (DialogueSystemController.isWarmingUp) { conversationUIElements.mainPanel.animatorMonitor.CancelCurrentAnimation(); conversationUIElements.mainPanel.gameObject.SetActive(false); conversationUIElements.mainPanel.panelState = UIPanel.PanelState.Closed; } else { conversationUIElements.mainPanel.Close(); while (conversationUIElements.mainPanel.panelState == UIPanel.PanelState.Closing) { yield return null; } } } CloseNow(); } protected virtual bool IsMainPanelClosed() { return conversationUIElements.mainPanel == null || conversationUIElements.mainPanel.panelState == UIPanel.PanelState.Closed; } // extraSubtitlePanel may be a custom (e.g., bubble) panel that isn't part of the dialogue UI's regular list. public virtual bool AreAnyPanelsClosing(StandardUISubtitlePanel extraSubtitlePanel = null) { return conversationUIElements.AreAnyPanelsClosing(extraSubtitlePanel); } #endregion #region Update public override void Update() { base.Update(); UpdateAlertQueue(); } #endregion #region Alerts public override void ShowAlert(string message, float duration) { if (string.IsNullOrEmpty(message)) return; if (alertUIElements.dontQueueDuplicates) { if (alertUIElements.isVisible && string.Equals(alertUIElements.alertText.text, message)) return; foreach (var queuedItem in alertQueue) { if (string.Equals(message, queuedItem.message)) return; } } if (alertUIElements.allowForceImmediate && message.Contains("[f]")) { base.ShowAlert(message.Replace("[f]", string.Empty), duration); } else if (alertUIElements.queueAlerts) { m_alertQueue.Enqueue(new QueuedUIAlert(message, duration)); } else { base.ShowAlert(message, duration); } } public override void HideAllAlerts() { m_alertQueue.Clear(); base.HideAllAlerts(); } private void UpdateAlertQueue() { if (alertUIElements.queueAlerts && m_alertQueue.Count > 0 && !alertUIElements.isVisible && !(alertUIElements.waitForHideAnimation && alertUIElements.isHiding)) { ShowNextQueuedAlert(); } } private void ShowNextQueuedAlert() { if (m_alertQueue.Count > 0) { var queuedAlert = m_alertQueue.Dequeue(); base.ShowAlert(queuedAlert.message, queuedAlert.duration); } } #endregion #region Subtitles public override void ShowSubtitle(Subtitle subtitle) { if (conversationUIElements.waitForMainPanelOpen && conversationUIElements.mainPanel != null && conversationUIElements.mainPanel.panelState != UIPanel.PanelState.Open) { StartCoroutine(ShowSubtitleWhenMainPanelOpen(subtitle)); } else { ShowSubtitleImmediate(subtitle); } } protected virtual IEnumerator ShowSubtitleWhenMainPanelOpen(Subtitle subtitle) { if (conversationUIElements.mainPanel == null) { ShowSubtitleImmediate(subtitle); } else { var focusedPanel = conversationUIElements.standardSubtitleControls.StageFocusedPanel(subtitle); float timeout = Time.realtimeSinceStartup + WaitForOpenTimeoutDuration; var showContinueButton = false; while (conversationUIElements.mainPanel.panelState != UIPanel.PanelState.Open && Time.realtimeSinceStartup < timeout) { yield return endOfFrame; var isContinueButtonActive = focusedPanel != null && focusedPanel.continueButton != null && focusedPanel.continueButton.gameObject.activeSelf; showContinueButton = showContinueButton || isContinueButtonActive; if (isContinueButtonActive) { focusedPanel.continueButton.gameObject.SetActive(false); } yield return null; } ShowSubtitleImmediate(subtitle); if (showContinueButton) focusedPanel.ShowContinueButton(); } } protected virtual void ShowSubtitleImmediate(Subtitle subtitle) { conversationUIElements.standardMenuControls.Close(); conversationUIElements.standardSubtitleControls.ShowSubtitle(subtitle); } public override void HideSubtitle(Subtitle subtitle) { conversationUIElements.standardSubtitleControls.HideSubtitle(subtitle); } /// /// Returns the speed of the first typewriter effect found. /// public virtual float GetTypewriterSpeed() { return conversationUIElements.standardSubtitleControls.GetTypewriterSpeed(); } /// /// Sets the speed of all typewriter effects. /// public virtual void SetTypewriterSpeed(float charactersPerSecond) { conversationUIElements.standardSubtitleControls.SetTypewriterSpeed(charactersPerSecond); } /// /// Changes a dialogue actor's subtitle panel for the currently active conversation. /// public virtual void SetActorSubtitlePanelNumber(DialogueActor dialogueActor, SubtitlePanelNumber subtitlePanelNumber) { conversationUIElements.standardSubtitleControls.SetActorSubtitlePanelNumber(dialogueActor, subtitlePanelNumber); } /// /// Changes a dialogue actor's menu panel for the currently active conversation. /// public virtual void SetActorMenuPanelNumber(DialogueActor dialogueActor, MenuPanelNumber menuPanelNumber) { conversationUIElements.standardMenuControls.SetActorMenuPanelNumber(dialogueActor, menuPanelNumber); } public virtual void OverrideActorPanel(Actor actor, SubtitlePanelNumber subtitlePanelNumber, bool immediate = false) { conversationUIElements.standardSubtitleControls.OverrideActorPanel(actor, subtitlePanelNumber, null, immediate); } public virtual void OverrideActorPanel(Actor actor, SubtitlePanelNumber subtitlePanelNumber, StandardUISubtitlePanel customPanel, bool immediate = false) { conversationUIElements.standardSubtitleControls.OverrideActorPanel(actor, subtitlePanelNumber, customPanel, immediate); } public virtual void ForceOverrideSubtitlePanel(StandardUISubtitlePanel customPanel) { conversationUIElements.standardSubtitleControls.ForceOverrideSubtitlePanel(customPanel); } /// /// Shows an actor immediately in a subtitle panel. /// public virtual void ShowActorInPanel(Actor actor, SubtitlePanelNumber subtitlePanelNumber, StandardUISubtitlePanel customPanel = null) { conversationUIElements.standardSubtitleControls.ShowActorInPanel(actor, subtitlePanelNumber, customPanel); } #endregion #region Response Menu public override void ShowResponses(Subtitle subtitle, Response[] responses, float timeout) { if (conversationUIElements.waitForMainPanelOpen && conversationUIElements.mainPanel != null && conversationUIElements.mainPanel.panelState != UIPanel.PanelState.Open) { StartCoroutine(ShowResponsesWhenMainPanelOpen(subtitle, responses, timeout)); } else { ShowResponsesImmediate(subtitle, responses, timeout); } } protected virtual IEnumerator ShowResponsesWhenMainPanelOpen(Subtitle subtitle, Response[] responses, float timeout) { if (conversationUIElements.mainPanel == null) yield break; float waitForOpenTimeout = Time.realtimeSinceStartup + WaitForOpenTimeoutDuration; while (conversationUIElements.mainPanel.panelState != UIPanel.PanelState.Open && Time.realtimeSinceStartup < waitForOpenTimeout) { yield return null; } ShowResponsesImmediate(subtitle, responses, timeout); } protected virtual void ShowResponsesImmediate(Subtitle subtitle, Response[] responses, float timeout) { conversationUIElements.standardSubtitleControls.UnfocusAll(); conversationUIElements.standardSubtitleControls.HideOnResponseMenu(); base.ShowResponses(subtitle, responses, timeout); } public override void OnClick(object data) { if (data is Response) { conversationUIElements.standardMenuControls.MakeButtonsNonclickable(); base.OnClick(data); } } public virtual void OverrideActorMenuPanel(Transform actorTransform, MenuPanelNumber menuPanelNumber, StandardUIMenuPanel customPanel) { conversationUIElements.standardMenuControls.OverrideActorMenuPanel(actorTransform, menuPanelNumber, customPanel ?? conversationUIElements.defaultMenuPanel); } public virtual void OverrideActorMenuPanel(Actor actor, MenuPanelNumber menuPanelNumber, StandardUIMenuPanel customPanel) { conversationUIElements.standardMenuControls.OverrideActorMenuPanel(actor, menuPanelNumber, customPanel ?? conversationUIElements.defaultMenuPanel); } public virtual void ForceOverrideMenuPanel(StandardUIMenuPanel customPanel) { conversationUIElements.standardMenuControls.ForceOverrideMenuPanel(customPanel); } #endregion } }