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.
Subtitle Panel and Unscaled Time
Re: Subtitle Panel and Unscaled Time
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.
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.
-
- Posts: 222
- Joined: Wed Jan 22, 2020 10:48 pm
Re: Subtitle Panel and Unscaled Time
OK great! So would your recommendation be to comment out this line in UIAnimatorMonitor:
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:
The sequencer message is one of the callbacks.
Edit 2:
Ahh, looks like there's a safety valve kind of thing in StandardUISubtitlePanel:
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:
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:
and then in UIPanel:
and then in UIAnimatorMonitor:
and then finally in StandardUISubtitlePanel:
Code: Select all
CheckAnimatorModeAndTimescale(triggerName);
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();
}
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();
}
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();
}
}
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
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();
}
Re: Subtitle Panel and Unscaled Time
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.
-
- Posts: 222
- Joined: Wed Jan 22, 2020 10:48 pm
Re: Subtitle Panel and Unscaled Time
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!
I'll keep an eye out for changes here or in the release notes. Thanks again!