Page 1 of 1
AnimatorPlayWait doesn't work if dialogue time scale is different from animator time scale
Posted: Mon Dec 18, 2023 10:45 am
by lgarczyn
The default dialogue time setting is "real time", or Time.unscaledDeltaTime
Most animators in most game run on Time.deltaTime, or Time.fixedDeltaTime
However in AnimatorPlayWait, the line
Code: Select all
yield return StartCoroutine(DialogueTime.WaitForSeconds(animatorStateInfo.length));
Waits for dialogue time instead of animator time, and thus terminates at the wrong time if Time.timeScale is different from 1.
Re: AnimatorPlayWait doesn't work if dialogue time scale is different from animator time scale
Posted: Mon Dec 18, 2023 10:56 am
by Tony Li
Hi,
Yes, that's correct; DialogueTime.WaitForSeconds observes the value of the current dialogue time setting.
If you want a different behavior, you could write a sequencer command that observes Time.timeScale (e.g., just copy SequencerCommandAnimatorPlayWait and change the yield return line), or change your animator to use unscaled time.
Re: AnimatorPlayWait doesn't work if dialogue time scale is different from animator time scale
Posted: Mon Dec 18, 2023 2:07 pm
by lgarczyn
I've changed the code to poll the animator instead of relying on timings
Code: Select all
// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using System.Collections;
namespace PixelCrushers.DialogueSystem.SequencerCommands
{
/// <summary>
/// Implements sequencer command: "AnimatorPlayWait(animatorParameter, [gameobject|speaker|listener], [crossfadeDuration], [layer])",
/// which plays a state on a subject's Animator and waits until it's done.
///
/// Fixes issues with AnimatorPlayWait by checking if the state is still playing in coroutine
///
/// Arguments:
/// -# Name of a Mecanim animator state.
/// -# (Optional) The subject; can be speaker, listener, or the name of a game object. Default: speaker.
/// -# (Optional) Crossfade duration. Default: 0 (play immediately).
/// -# (Optional) Animation Layer. Default: -1 (all layers).
/// -# (Optional) Max anim duration. Default: 10 seconds.
/// </summary>
[AddComponentMenu("")] // Hide from menu.
public class SequencerCommandAnimatorPlayWaitSmart : SequencerCommand
{
private const float maxDurationToWaitForStateStart = 1;
private const float maxDurationToWaitForStateEnd = 10;
public void Start()
{
// Get the values of the parameters:
string stateName = GetParameter(0);
Transform subject = GetSubject(1);
float crossfadeDuration = GetParameterAsFloat(2);
int layer = GetParameterAsInt(3, -1);
float maxAnimDuration = GetParameterAsFloat(4, maxDurationToWaitForStateEnd);
// Start the state:
Animator animator = (subject != null) ? subject.GetComponentInChildren<Animator>() : null;
if (animator == null)
{
if (DialogueDebug.logWarnings) Debug.Log(string.Format("{0}: Sequencer: AnimatorPlayWait({1}, {2}, fade={3}, layer={4}): No Animator found on {2}", new object[] { DialogueDebug.Prefix, stateName, (subject != null) ? subject.name : GetParameter(1), crossfadeDuration, layer }));
Stop();
}
else
{
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Sequencer: AnimatorPlayWait({1}, {2}, {3})", new object[] { DialogueDebug.Prefix, stateName, subject, crossfadeDuration }));
if (!animator.gameObject.activeSelf) animator.gameObject.SetActive(true);
if (Tools.ApproximatelyZero(crossfadeDuration))
{
animator.Play(stateName, layer, 0);
}
else
{
animator.CrossFadeInFixedTime(stateName, crossfadeDuration, layer);
}
StartCoroutine(MonitorState(animator, stateName, maxAnimDuration));
}
}
/// <summary>
/// Monitors the animator state. Stops the sequence when the state is done,
/// or after maXDurationToWaitForStateStart (1 second) if it never enters
/// that state.
/// </summary>
/// <param name="animator">Animator.</param>
/// <param name="stateName">State name.</param>
/// <param name="maxAnimDuration"></param>
private IEnumerator MonitorState(Animator animator, string stateName, float maxAnimDuration)
{
// Wait to enter the state:
float maxStartTime = DialogueTime.time + maxDurationToWaitForStateStart;
AnimatorStateInfo animatorStateInfo;
bool isInState = CheckIsInState(animator, stateName, out animatorStateInfo);
while (!isInState && DialogueTime.time < maxStartTime)
{
yield return null;
isInState = CheckIsInState(animator, stateName, out animatorStateInfo);
}
float maxEndTime = DialogueTime.time + maxAnimDuration;
// Wait for state to end, then stop:
while (isInState && DialogueTime.time < maxEndTime)
{
yield return null;
isInState = CheckIsInState(animator, stateName, out animatorStateInfo);
}
Stop();
}
/// <summary>
/// Checks if the animator is in a specified state on any of its layers, and
/// returns the state info.
/// </summary>
/// <returns><c>true</c>, if in the state, <c>false</c> otherwise.</returns>
/// <param name="animator">Animator.</param>
/// <param name="stateName">State name.</param>
/// <param name="animatorStateInfo">(Out) Animator state info.</param>
private bool CheckIsInState(Animator animator, string stateName, out AnimatorStateInfo animatorStateInfo)
{
if (animator != null)
{
for (int layerIndex = 0; layerIndex < animator.layerCount; layerIndex++)
{
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(layerIndex);
if (info.IsName(stateName))
{
animatorStateInfo = info;
return true;
}
}
}
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
return false;
}
}
}
Re: AnimatorPlayWait doesn't work if dialogue time scale is different from animator time scale
Posted: Mon Dec 18, 2023 3:24 pm
by Tony Li
Hi,
Thanks for sharing!