Page 1 of 1

Animations interrupted when moving to a new dialogue node

Posted: Tue Mar 28, 2023 4:16 pm
by lostmushroom
Hey Tony, just a question about using the Sequence field to play animations. I noticed that when I click through the dialogue nodes fast, the animation doesn't play in full, but seems to skip to its last frame. So for example, I have the following sequence command which controls a Stat bar.

AnimatorFloat(StatLevel, [var=Stat], StatBar, 0.75)

When playing normally, it plays smoothly between the start value and end value, but when clicking through fast, it jumps straight to the end value.

Is there a way to tell DS to play the animation in full despite moving to the next node?

Re: Animations interrupted when moving to a new dialogue node

Posted: Tue Mar 28, 2023 7:08 pm
by Tony Li
Hi,

Yes, you can write a custom sequencer command to do that. Most of the built-in commands attempt to put the command in its final state when you skip ahead, instead of leaving it halfway done. However, some commands, like Audio() [versus AudioWait()] let the action continue even if the player skips ahead.

Re: Animations interrupted when moving to a new dialogue node

Posted: Tue Mar 28, 2023 8:29 pm
by lostmushroom
Thanks, that sounds good. So just to check - using a custom sequencer, it will be possible to have the animations not be interrupted by advancing to the next node, but still have them play in full (as in reach their final state)? It's not gonna leave them halfway done if I do it that way?

Re: Animations interrupted when moving to a new dialogue node

Posted: Tue Mar 28, 2023 9:05 pm
by Tony Li
It depends. For example, AnimatorPlay() starts playing an animator state but doesn't make it jump to the end if you skip ahead. So if one node has this Sequence:

Code: Select all

AnimatorPlay(Dance)
And the next node has this Sequence:

Code: Select all

AnimatorPlay(Idle)
Then as soon as the player skips to the next node it will immediately switch to the Idle animation even if Dance isn't done yet.

But let's say you write a custom sequencer command named UninterrupibleAnimatorFloat that you can use like this:

Code: Select all

UninterruptibleAnimatorFloat(StatLevel, [var=Stat], StatBar, 0.75)
This command should do it:

SequencerCommandUninterruptibleAnimatorFloat.cs

Code: Select all

using System.Collections;
using UnityEngine;

namespace PixelCrushers.DialogueSystem.SequencerCommands
{
    [AddComponentMenu("")] // Hide from menu.
    public class SequencerCommandUninterruptibleAnimatorFloat : SequencerCommand
    {

        public void Start()
        {
            // Get the values of the parameters:
            string animatorParameter = GetParameter(0);
            int animatorParameterHash = Animator.StringToHash(animatorParameter);
            float targetValue = GetParameterAsFloat(1, 1);
            Transform subject = GetSubject(2);
            float duration = GetParameterAsFloat(3, 0);

            // Check the parameters:
            if (subject == null)
            {
                if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Sequencer: AnimatorFloat(): subject '{1}' wasn't found.", new System.Object[] { DialogueDebug.Prefix, GetParameter(2) }));
            }
            else
            {
                var animator = subject.GetComponentInChildren<Animator>();
                if (animator == null)
                {
                    if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Sequencer: AnimatorFloat(): no Animator found on '{1}'.", new System.Object[] { DialogueDebug.Prefix, subject.name }));
                }
                else
                {
                    if (DialogueDebug.logInfo) Debug.Log(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}: Sequencer: AnimatorFloat({1}, {2}, {3}, {4})", new System.Object[] { DialogueDebug.Prefix, animatorParameter, targetValue, subject, duration }));
                    DialogueManager.instance.StartCoroutine(LerpAnimatorFloat(animator, animatorParameterHash, targetValue, duration));
                }
            }
            Stop();
        }

        // This method is static so it can run independently of the sequencer command.
        // it runs through the Dialogue Manager GameObject's DialogueSystemController component.
        private static IEnumerator LerpAnimatorFloat(Animator animator, int animatorParameterHash, float targetValue, float duration)
        {
            var originalValue = animator.GetFloat(animatorParameterHash);
            var startTime = DialogueTime.time;
            var elapsed = 0f;
            while (elapsed < duration)
            { 
                elapsed = (DialogueTime.time - startTime) / duration;
                float current = Mathf.Lerp(originalValue, targetValue, elapsed / duration);
                if (animator != null) animator.SetFloat(animatorParameterHash, current);
                yield return null;
            }
            if (animator != null) animator.SetFloat(animatorParameterHash, targetValue);
        }
    }
}

Re: Animations interrupted when moving to a new dialogue node

Posted: Wed Mar 29, 2023 3:45 pm
by lostmushroom
That works perfectly, thanks so much Tony (:

Re: Animations interrupted when moving to a new dialogue node

Posted: Wed Mar 29, 2023 9:08 pm
by Tony Li
Glad to help!

Re: Animations interrupted when moving to a new dialogue node

Posted: Mon Jun 05, 2023 6:20 am
by sadie110
It sounds like you're experiencing an issue where the animation doesn't play fully when you click through the dialogue nodes quickly. This behavior is expected because the Dialogue System (DS) typically moves to the next node as soon as it receives input.

To ensure that the animation plays in full before moving to the next node, you can use a coroutine to control the flow of the dialogue. Here's an example of how you can modify your code to achieve this:

csharp
Copy code
using UnityEngine;
using System.Collections;
using PixelCrushers.DialogueSystem;

public class YourDialogueClass : MonoBehaviour
{
private bool isAnimating = false;

// Your code to initiate the dialogue goes here.

private IEnumerator PlayAnimationCoroutine(string stat, GameObject statBar, float duration)
{
// Check if an animation is already playing.
if (isAnimating)
{
// Wait until the previous animation finishes before starting a new one.
yield return new WaitUntil(() => !isAnimating);
}

// Set the flag to indicate that an animation is playing.
isAnimating = true;

// Play the animation using the Sequence field.
DialogueLua.SetVariable("Stat", stat);
DialogueManager.PlaySequence("AnimatorFloat(StatLevel, [var=Stat], " + statBar.name + ", " + duration + ")");

// Wait for the animation to complete.
yield return new WaitForSeconds(duration);

// Reset the flag to indicate that the animation has finished.
isAnimating = false;

// Move to the next node in the dialogue.
DialogueManager.DialogueContinue();
}

public void PlayAnimation(string stat, GameObject statBar, float duration)
{
StartCoroutine(PlayAnimationCoroutine(stat, statBar, duration));
}
}
In this example, we introduce a boolean flag called isAnimating to keep track of whether an animation is currently playing. When the PlayAnimationCoroutine is called, it first checks if an animation is already playing. If so, it waits until the previous animation finishes before starting a new one.

The animation is played using DialogueManager.PlaySequence as before. After the animation, the flag is reset, and then DialogueManager.DialogueContinue is called to move to the next node in the dialogue.

By using coroutines and the yield return statements, we ensure that the animation completes before proceeding to the next dialogue node, regardless of how fast you click through the dialogue.

Remember to attach this script to a GameObject in your scene and call the PlayAnimation method whenever you want to play an animation during the dialogue.

Re: Animations interrupted when moving to a new dialogue node

Posted: Mon Jun 05, 2023 7:51 am
by Tony Li
Thanks for sharing your code and explanation! :-)