// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using System.Collections; namespace PixelCrushers.DialogueSystem { /// /// This is a deprecated class that has been replaced by UIAnimatorMonitor in StandardDialogueUI /// and elsewhere. It's only used by the older UnityUIDialogueUI. /// public class UIShowHideController { public enum TransitionMode { State, Trigger } public enum State { Undefined, Showing, Shown, Hiding, Hidden } /// /// Maximum number of seconds to wait for show/hide animation. /// public static float maxWaitDuration = 5; public Component panel { get; set; } public State state { get; set; } public bool debug { get; set; } private TransitionMode m_transitionMode; private Animator m_animator = null; private bool m_lookedForAnimator = false; private Coroutine m_animCoroutine; public UIShowHideController(GameObject gameObjectToControl, Component panelToControl, TransitionMode animationMode = TransitionMode.Trigger, bool debug = false) { panel = panelToControl; m_animator = (gameObjectToControl != null) ? gameObjectToControl.GetComponent() : null; if (m_animator == null && panelToControl != null) m_animator = panelToControl.GetComponent(); state = State.Undefined; m_transitionMode = animationMode; m_animCoroutine = null; this.debug = debug; } public void Show(string showState, bool pauseAfterAnimation, System.Action callback, bool wait = true) { CancelCurrentAnim(); switch (m_transitionMode) { case TransitionMode.State: m_animCoroutine = DialogueManager.instance.StartCoroutine(WaitForAnimationState(State.Showing, State.Shown, showState, pauseAfterAnimation, true, wait, callback)); break; case TransitionMode.Trigger: m_animCoroutine = DialogueManager.instance.StartCoroutine(WaitForAnimationTrigger(State.Showing, State.Shown, showState, pauseAfterAnimation, true, wait, callback)); break; } } public void Hide(string hideState, System.Action callback) { CancelCurrentAnim(); switch (m_transitionMode) { case TransitionMode.State: m_animCoroutine = DialogueManager.instance.StartCoroutine(WaitForAnimationState(State.Hiding, State.Hidden, hideState, false, false, true, callback)); break; case TransitionMode.Trigger: m_animCoroutine = DialogueManager.instance.StartCoroutine(WaitForAnimationTrigger(State.Hiding, State.Hidden, hideState, false, false, true, callback)); break; } } private IEnumerator WaitForAnimationState(State stateWhenBegin, State stateWhenEnd, string stateName, bool pauseAfterAnimation, bool panelActive, bool wait, System.Action callback) { if (state != stateWhenEnd) // Only need to do if we're not already in the state. { // If transitioning, need to wait one frame, or two Animator.Play calls may conflict on the same frame: if (state == State.Hiding || state == State.Showing) yield return null; state = stateWhenBegin; // Activate panel if necessary: if (panel != null && !panel.gameObject.activeSelf) { panel.gameObject.SetActive(true); yield return null; } // Play animation: if (CanTriggerAnimation(stateName)) { if (debug) Debug.Log("" + panel.name + ".Animator.Play(" + stateName + ") time=" + Time.time + ""); CheckAnimatorModeAndTimescale(stateName); m_animator.Play(stateName); // ...and wait for it to finish: if (wait) { yield return null; // Wait to enter state. var clipLength = (m_animator != null) ? m_animator.GetCurrentAnimatorStateInfo(0).length : 0; if (Mathf.Approximately(0, Time.timeScale)) { var timeout = Time.realtimeSinceStartup + clipLength; while (Time.realtimeSinceStartup < timeout) { yield return null; } } else { yield return new WaitForSeconds(clipLength); } if (debug) Debug.Log("... finished " + panel.name + ".Animator.Play(" + stateName + ") time=" + Time.time + ""); } } } // Finish: if (!panelActive) Tools.SetGameObjectActive(panel, false); if (pauseAfterAnimation) Time.timeScale = 0; m_animCoroutine = null; state = stateWhenEnd; if (callback != null) callback.Invoke(); } private IEnumerator WaitForAnimationTrigger(State stateWhenBegin, State stateWhenEnd, string triggerName, bool pauseAfterAnimation, bool panelActive, bool wait, System.Action callback) { if (state != stateWhenEnd) { state = stateWhenBegin; if (panelActive) { if (panel != null && !panel.gameObject.activeSelf) { panel.gameObject.SetActive(true); yield return null; } } if (CanTriggerAnimation(triggerName) && m_animator.gameObject.activeSelf) { if (debug) Debug.Log("" + panel.name + ".Animator.SetTrigger(" + triggerName + ") time=" + Time.time + ""); CheckAnimatorModeAndTimescale(triggerName); float timeout = Time.realtimeSinceStartup + maxWaitDuration; #if UNITY_2019_1_OR_NEWER var goalHashID = Animator.StringToHash($"{m_animator.GetLayerName(0)}.{triggerName}"); #else var goalHashID = Animator.StringToHash(triggerName); #endif var oldHashId = UITools.GetAnimatorNameHash(m_animator.GetCurrentAnimatorStateInfo(0)); var currentHashID = oldHashId; m_animator.SetTrigger(triggerName); if (wait) { // Wait while we're not at the goal state and we're in the original state and we haven't timed out: while ((currentHashID != goalHashID) && (currentHashID == oldHashId) && (Time.realtimeSinceStartup < timeout)) { yield return null; var isAnimatorValid = m_animator != null && m_animator.isActiveAndEnabled && m_animator.runtimeAnimatorController != null && m_animator.layerCount > 0; currentHashID = isAnimatorValid ? UITools.GetAnimatorNameHash(m_animator.GetCurrentAnimatorStateInfo(0)) : currentHashID; } // If we're in the goal state and we haven't timed out, wait for the duration of the goal state: if (m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1 && currentHashID == goalHashID && Time.realtimeSinceStartup < timeout) { var clipLength = m_animator.GetCurrentAnimatorStateInfo(0).length; if (Mathf.Approximately(0, Time.timeScale)) { timeout = Time.realtimeSinceStartup + clipLength; while (Time.realtimeSinceStartup < timeout) { yield return null; } } else { yield return new WaitForSeconds(clipLength); } } if (debug) Debug.Log("... finished " + panel.name + ".Animator.SetTrigger(" + triggerName + ") time=" + Time.time + ""); } } } if (!panelActive) Tools.SetGameObjectActive(panel, false); if (pauseAfterAnimation) Time.timeScale = 0; m_animCoroutine = null; state = stateWhenEnd; if (callback != null) callback.Invoke(); } private void CheckAnimatorModeAndTimescale(string triggerName) { if (Mathf.Approximately(0, Time.timeScale) && (m_animator.updateMode != AnimatorUpdateMode.UnscaledTime) && DialogueDebug.logWarnings) { Debug.LogWarning("Dialogue System: Time is paused but animator mode isn't set to Unscaled Time; the animation triggered by " + triggerName + " won't play.", m_animator); } } private void CancelCurrentAnim() { if (m_animCoroutine != null) { DialogueManager.instance.StopCoroutine(m_animCoroutine); m_animCoroutine = null; } } public void ClearTrigger(string triggerName) { if (HasAnimator() && !string.IsNullOrEmpty(triggerName) && m_animator.isActiveAndEnabled) { m_animator.ResetTrigger(triggerName); } } private bool CanTriggerAnimation(string stateName) { return HasAnimator() && !string.IsNullOrEmpty(stateName); } private bool HasAnimator() { if ((m_animator == null) && !m_lookedForAnimator) { m_lookedForAnimator = true; if (panel != null) { m_animator = panel.GetComponent(); if (m_animator == null) m_animator = panel.GetComponentInChildren(); state = (m_animator != null && m_animator.gameObject.activeInHierarchy) ? State.Shown : State.Hidden; } } #if UNITY_5_3 || UNITY_5_3_OR_NEWER return (m_animator != null) && m_animator.isInitialized && m_animator.gameObject.activeSelf; #else return (m_animator != null) && m_animator.gameObject.activeSelf; #endif } } }