Subtitle Panel and Unscaled Time

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Subtitle Panel and Unscaled Time

Post by VoodooDetective »

I was just curious, is there an important reason the subtitle panel animators work with unscaled time?

Our {{default}} sequence waits for the Subtitle panel to open (I have Sequencer.Message("SubtitleOpened")) to make sure the panel is open before dialogue starts. But if I pause the game as the panel is opening, then the message hits the Sequencer when it's paused and so the message is discarded hanging the game.

I was going to switch to scaled time for those animators, but I just wanted to make sure that wouldn't break something else.
User avatar
Tony Li
Posts: 21981
Joined: Thu Jul 18, 2013 1:27 pm

Re: Subtitle Panel and Unscaled Time

Post by Tony Li »

Hi,

Some games pause time during conversations. In those cases, the animators need to be set to unscaled time.

Otherwise, it's perfectly fine to set it to normal time if that works best for your project.
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Subtitle Panel and Unscaled Time

Post by VoodooDetective »

OK great! So would your recommendation be to comment out this line in UIAnimatorMonitor:

Code: Select all

CheckAnimatorModeAndTimescale(triggerName);
in WaitForAnimation()?


EDIT:
Huh, even if I do that and it uses Normal time, I'm having that issue pop up. I'm sending the Sequencer message from a coroutine that starts in ShowSubtitle and waits for the panel to open:

Code: Select all

        private IEnumerator OnPanelOpen(params Action[] onOpenCallbacks)
        {
            while (!hasFocus && panelState != PanelState.Open)
            {
                yield return null;
            }
            yield return waitForEndOfFrame;
            foreach (Action action in onOpenCallbacks) action?.Invoke();
        }
The sequencer message is one of the callbacks.


Edit 2:
Ahh, looks like there's a safety valve kind of thing in StandardUISubtitlePanel:

Code: Select all

        protected IEnumerator FocusWhenOpen()
        {
            float timeout = Time.realtimeSinceStartup + 5f;
            while (panelState != PanelState.Open && Time.realtimeSinceStartup < timeout)
            {
                yield return null;
            }
            m_focusWhenOpenCoroutine = null;
            FocusNow();
        }
That causes FocusNow() to be called even if the panel isn't open yet.

Edit 3:
AHHH, but even if I comment out the safety valve, the panel gets set to open before the animation has finished. Digging more.

Edit 4:
Ahhhhhh OK, so there's another place unscaled time is forced and also where there is another safety valve in UIAnimatorMonitor:

Code: Select all

        private IEnumerator WaitForAnimation(string triggerName, System.Action callback, bool wait)
        {
            if (HasAnimator() && !string.IsNullOrEmpty(triggerName))
            {
                if (IsAnimatorValid())
                {
                    // Run Animator and wait:
                    CheckAnimatorModeAndTimescale(triggerName);
                    m_animator.SetTrigger(triggerName);
                    currentTrigger = triggerName;
                    float timeout = Time.realtimeSinceStartup + MaxWaitDuration + 100000;
                    var goalHashID = Animator.StringToHash(triggerName);
                    var oldHashId = UIUtility.GetAnimatorNameHash(m_animator.GetCurrentAnimatorStateInfo(0));
                    var currentHashID = oldHashId;
                    if (wait)
                    {
                        while ((currentHashID != goalHashID) && (currentHashID == oldHashId) && (Time.realtimeSinceStartup < timeout))
                        {
                            yield return null;
                            currentHashID = IsAnimatorValid() ? UIUtility.GetAnimatorNameHash(m_animator.GetCurrentAnimatorStateInfo(0)) : 0;
                        }
                        if (Time.realtimeSinceStartup < timeout && IsAnimatorValid())
                        {
                            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);
                            }
                        }
                    }
                }
                else if (m_animation != null && m_animation.enabled)
                {
                    m_animation.Play(triggerName);
                    if (wait)
                    {
                        var clip = m_animation.GetClip(triggerName);
                        if (clip != null)
                        {
                            yield return new WaitForSeconds(clip.length);
                        }
                    }
                }
            }
            currentTrigger = string.Empty;
            m_coroutine = null;
            if (callback != null) callback.Invoke();
        }

If I comment out all the safety valves, the code that forces unscaled time, AND set all animators to normal, then the issue goes away. But that seems like a pretty heavy handed approach. Do you have any better ideas?

I wonder if this animation functionality could be abstracted out into an interface that could have this stuff as the default implementation, and then allow folks to implement their own. Like with a library like animancer or something. Still digging...


Edit 5:
OK, so you may have a better idea, but what would you think of:

Code: Select all

namespace PixelCrushers
{
    public interface IUIAnimatorMonitor
    {
        string currentTrigger { get; }
        void SetTrigger(string triggerName, System.Action callback, bool wait = true);
        void CancelCurrentAnimation();
    }
}
and then in UIPanel:

Code: Select all

        public virtual IUIAnimatorMonitor animatorMonitor
        {
            get
            {
                if (m_animatorMonitor == null) m_animatorMonitor = new UIAnimatorMonitor(gameObject);
                return m_animatorMonitor;
            }
        }

and then in UIAnimatorMonitor:

Code: Select all

    public class UIAnimatorMonitor : IUIAnimatorMonitor
and then finally in StandardUISubtitlePanel:

Code: Select all

        protected IEnumerator FocusWhenOpen()
        {
            // float timeout = Time.realtimeSinceStartup + 5f;
            while (panelState != PanelState.Open)// && Time.realtimeSinceStartup < timeout)
            {
                yield return null;
            }
            m_focusWhenOpenCoroutine = null;
            FocusNow();
        }
User avatar
Tony Li
Posts: 21981
Joined: Thu Jul 18, 2013 1:27 pm

Re: Subtitle Panel and Unscaled Time

Post by Tony Li »

I think something similar to that would work. The intent of the realtimeSinceStartup check is to prevent situations where the animator is waiting forever for an animation (and thus hanging the rest of the Dialogue System) because an animator's states or transitions aren't set up right. But, if you're using normal time, I suppose it should not even try to start the animation if time is paused -- in which case, it wouldn't get to the loop that checks realtimeSinceStartup. I'll take a look at your suggestions and what I can do to make it a little easier to work with in normal time.
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Subtitle Panel and Unscaled Time

Post by VoodooDetective »

Awesome, thank you! That change I made is working, but I don't know if it's the cleanest. You may have a better idea. Just let me know if I can do anything, or if you need a tester :)

I'll keep an eye out for changes here or in the release notes. Thanks again!
Post Reply