Pause timeline but loop active animations, possible?

Hello,

I’m trying to find if I can use PlayableDirector for animated dialogue, such as those seen in later Zelda titles or the Persona series.

The big issue I have is that when I pause the timeline to wait for player input, the characters also freeze very unnaturally.

Currently I’ve avoided the issue by scripting tracks that update Animator’s properties instead of controlling it directly, but I’ve been looking for a smarter way to go about it.

Can you add animations to timeline without them being lock-step in their timescale? Is timeline not the right tool for this kind of scenario?

Short answer: timeline doesn’t support that right now.

Longer Answer:

It is possible with timeline, but requires some scripting customization. A good starting point is Ciro’s blog: Unity Blog

However, this is a use case for timeline that we are quite interested in exploring right now. Some of the work we have planned has this type of scenario in mind.

1 Like

Alright, thank you for the update seant_unity!

Do you think this is something that might materialize before 2020? If you don’t feel confident about answering that, I’ll know to look into if we can brew a solution in-house.

It might. We are looking into creating some more example custom tracks that would better handle this type of scenario. The current plan would have them in users hands before 2020, but that is subject to change.

Sorry to necro, but I am in this exact situation as described here.

Is this possible now, in some way, @seant_unity ? Because that would be amazing! :smile:

No, we don’t have that sample. I really wish we did.

However, there are a few ways to accomplish this. For example, similar to the original post.

  1. Leave a gap (no extrapolation on the previous clip) on your animation track where you want the looping animation to be. (I’m assuming you have something to pause and/or loop the timeline)
  2. Place a signal on the animation track before the gap to trigger the looping state on the animator.
  3. Place a signal after the gap to trigger the looping state to end.

The gap will revert control to the animator. If you ease out the previous clip and ease in the next, and place the signals before/after the eases, then it will blend from and to the animator.

Alternately I think it would be possible to write a custom animation track that allows the animation to ‘free run’ when the graph is being looped, but I haven’t validated that.

For pausing/looping/etc… there is this post as well as a bunch of other forums posts.
https://blogs.unity3d.com/2018/04/05/creative-scripting-for-timeline/

1 Like

Thank you very much @seant_unity

I may have devised a great method. But it hinges on one detail to be fully automated. (I got it working, but requires you to feed a string manually)

Is there any way to get the name of the animation currently being played on a character , that’s been assigned in Timeline?

As in, the animation Timeline is overriding the animator with. If I try the conventional method of getting the animation name, it gets the name of the animation it’s playing in its own Animator. NOT the one assigned by Timeline.

There’s no accessible property to do that. The reason the animator gives you the controllers current clip is that the controller is still playing underneath the timeline…i.e. timeline is running independently and overwriting what the state machine is doing - all the animator really knows is something else is giving it a pose, and not which clips generated it.

The only alternative is to write something that determines which animation clips are currently being played by the timeline.

Here’s a sample of how to do it.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

[ExecuteAlways]
public class PrintAnimationNames : MonoBehaviour
{
    public Animator who;
   
    void Update()
    {
        if (who == null)
            return;

        PlayableDirector director = GetComponent<PlayableDirector>();
        if (director == null || !director.playableGraph.IsValid())
            return;

        // goes through each animation track
        var animationOutputs = director.playableGraph.GetOutputCountByType<AnimationPlayableOutput>();
        for (int i = 0; i < animationOutputs; i++)
        {
            var output = (AnimationPlayableOutput) director.playableGraph.GetOutputByType<AnimationPlayableOutput>(i);
            if (output.GetTarget() != who)
                continue;

            var root = output.GetSourcePlayable();
            if (!root.IsValid())
                continue;

            // walks the playable graph, searching for animation clips
            var port = output.GetSourceOutputPort();
            if (port >= 0)
                root = root.GetInput(0);
           
            var queue = new Queue<Playable>();
            queue.Enqueue(root);
            while (queue.Count > 0)
            {
                var playable = queue.Dequeue();
                for (int j = 0; j < playable.GetInputCount(); j++)
                {
                    // skips playables with 0 weight or disabled
                    if (playable.GetInputWeight(j) > 0 && playable.GetInput(j).GetPlayState() == PlayState.Playing)
                        queue.Enqueue(playable.GetInput(j));
                }

                if (playable.IsPlayableOfType<AnimationClipPlayable>())
                {
                    var clipPlayable = (AnimationClipPlayable) playable;
                    // skips the editor generated clip of the default pose
                    if (clipPlayable.GetAnimationClip() != null && clipPlayable.GetAnimationClip().name != "DefaultPose")
                        Debug.Log(clipPlayable.GetAnimationClip().name);
                }
            }
        }
    }
}

Very close now @seant_unity and thank you. :slight_smile:

I tried your script, attached it to my director and selected an animator. And I get nothing, sadly, in debug log.

It does register having 1 in the queue, but never reaches the spot marked in red where it’s going to post the animation name. Maybe I am misunderstanding the setup or utility. It implemented without any errors or warning.

Which version of Unity/Timeline are you using? If I remember right we fixed an issue with 0 weights which might be the problem… so try removing playable.GetInputWeight(j) > 0 - change it to just
if (playable.GetInput(j).GetPlayState() == PlayState.Playing).

@seant_unity I am using Unity 2019.2.3f1

I tried your solution and sadly it still doesn’t go into the
if (playable.IsPlayableOfType())
part of the script.

Just to show, I have gotten the system to work
https://www.dropbox.com/s/q8rrm866fapp61y/PausedAnimation.mp4?raw=1

The only problem is right now I need to fire off a signal for every animated character to loop an animation.
If I could just get the name of the animation, that would go many miles to make the ease of use here simple and intuitive. :slight_smile:

That looks great! Good stuff.

I used 2019.3 to build that, and there are differences to the graph between 2019.2 and 2019.3. So… one last try…replace the while loop with this.

           while (queue.Count > 0)
            {
                var playable = queue.Dequeue();
                for (int j = 0; j < playable.GetInputCount(); j++)
                {
                    if (playable.IsPlayableOfType<AnimationMixerPlayable>() && playable.GetInputWeight(j) <= 0.01f)
                        continue;
    
                    queue.Enqueue(playable.GetInput(j));
                }

                if (playable.IsPlayableOfType<AnimationClipPlayable>())
                {
                    var clipPlayable = (AnimationClipPlayable) playable;
                    // skips the editor generated clip of the default pose
                    if (clipPlayable.GetAnimationClip() != null && clipPlayable.GetAnimationClip().name != "DefaultPose")
                        Debug.Log(clipPlayable.GetAnimationClip().name);
                }
            }

Thank you very much @seant_unity for the hints: I am trying this method but when entering the gap the offset position are not blended between the timeline track and the animator state. Any suggestions?

Sorry for the necro but I am in the same situation. I want to make a system that is very intuitive since I won’t be the one placing things on the timeline.

@Littlenorwegian would you mind sharing a bit more insight on your solution? Or some a screenshot of your timeline structure?

I have branching dialogues so I’m planing of making one timeline per dialogue box, but only when a timeline is necessary (character or camera moving)

Currently the direction I’m going into is holding the timeline at the end and making custom tracks

Could you share how you finally did this?