Looping a timeline sub-section for a undefined amount of time...?

Summary:
Basically, I need to be able to loop the timeline from frame Fx to frame Fy for an amount of time that is specified by user interaction (unknown a prior).

Use case:
The user can Tap or Hold a button:
a) A Tap triggers an effect that needs to be executed for a pre-defined duration (fixed time).
b) A Hold triggers the same effect, but it loops until the user releases the button.

Current status:
I’m successfully achieving this by using animation states (Animator). I define 3 states: In, Stay and Out.
Then, it simply works by looping Stay state while user holds the button.

Question / requirement:
Since Timeline was release, I wanted to try it out because it looks awesome.
I implemented several effects using Timeline. But I don’t know how to replicate the Hold behaviour.
I only can Tap them.

How can I hold / loop a sub-section of the timeline?
Is this even possible?

I was reading this old post: Animator (statemachine) for Timeline?
that it seems to be helpful in this topic.

Thanks you very much!

I would like to know this as well

Check out the blog post by Ciro. It isn’t exactly the same, but it’s seems pretty close to what you are trying to do.

1 Like

So essentially, set PlayableDirector.Time

Is there a way to have this smoothly transition from wherever it was to the new point in time?

Blending in timeline at arbitrary points in time is possible, in some cases, but not straightforward. There is another thread about it here . It may be of use if you are looking to try and script in a blend when jumping time, however it would only apply to animation tracks.

Thanks for the lead! i was able to get what i wanted working. Although i’m sure there are lots of situations where this will currently break, here’s the code in case it helps anyone:

https://vimeo.com/276729349

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using Object = System.Object;

[RequireComponent(typeof(PlayableDirector))]
public class CrossFadeTimeline : MonoBehaviour
{
    public bool StartTrigger;
    public float FadeDuration = 2f;
    public float SeekTime = 0f;

    private PlayableDirector _playableDirector;
    private AnimationPlayableOutput _output;
    private PlayableDirector _playableDirector2;
    private Playable _originalSourcePlayable;
    private Playable _clone;
    private AnimationMixerPlayable _mixer;
    private int _originalIndex;
    private int _cloneIndex;
    private float _decreasingWeight;
    private float _increasingWeight;

    // Assume playOnAwake on the playabledirector
    void OnEnable()
    {
        _playableDirector = GetComponent<PlayableDirector>();

        if (_playableDirector.playableGraph.IsValid())
        {
            // assumes the first output is the one we want to fade
            var oldOutput = (AnimationPlayableOutput)_playableDirector.playableGraph.GetOutputByType<AnimationPlayableOutput>(0);
            _originalSourcePlayable = oldOutput.GetSourcePlayable();

            _clone = _playableDirector.playableAsset.CreatePlayable(_playableDirector.playableGraph, _playableDirector.gameObject);
            _mixer = AnimationMixerPlayable.Create(_playableDirector.playableGraph, 2);
                               
            _cloneIndex = _mixer.AddInput(_clone, 0);
            _originalIndex = _mixer.AddInput(_originalSourcePlayable, 0, 1f);

            if (oldOutput.IsOutputValid() && oldOutput.GetTarget() != null)
            {
                _output = AnimationPlayableOutput.Create(_playableDirector.playableGraph, "OverridedDirectorOutput", oldOutput.GetTarget());        
                _output.SetSourcePlayable(_mixer);
                _output.SetSourceOutputPort(oldOutput.GetSourceOutputPort());              
                _output.SetWeight(1f);            
                oldOutput.SetTarget(null);
            }
        }
    }

    public void StartFadeOut(float time)
    {
        StartTrigger = true;
        SeekTime = time;
    }

    void Update()
    {
        if (StartTrigger)
        {
            StartTrigger = false;
            if (_output.IsOutputValid())
                StartCoroutine(CrossFade());
        }
    }

    IEnumerator CrossFade()
    {
        float t = 0;

        Debug.Log($"CrossFade Start - OriginalTime: {_playableDirector.time:N4} CloneTime {_clone.GetTime():N4}");

        _clone.SetTime(_playableDirector.time);
        _clone.Play();

        _playableDirector.time = SeekTime;
        _playableDirector.Play();

        while (t < FadeDuration)
        {
            var normalizedTime = Mathf.Clamp01(t / FadeDuration);
            _decreasingWeight = 1 - normalizedTime;
            _increasingWeight = normalizedTime;

            _mixer.SetInputWeight(_cloneIndex, _decreasingWeight);
            _mixer.SetInputWeight(_originalIndex, _increasingWeight);

            Debug.Log($"Tick - CloneTime-: {_clone.GetTime():N4} / {_decreasingWeight}, OriginalTime+: {_playableDirector.time:N4} / {_increasingWeight}");

            yield return null;
            t += Time.deltaTime;
        }

        _mixer.SetInputWeight(_cloneIndex, 0);
        _mixer.SetInputWeight(_originalIndex, 1f);

        _clone.Pause();
        Debug.Log("CrossFade Finished");
    }
}
5 Likes

I opened this in 2021.1 and it does not work for me.
Unity tells me SetSourceOutputPort is deprecated and should be replaced with
SetSourcePlayable(value, port) which I did* but the graph no longer outputs to the animator.
The visualizer shows me that the timelines exist and the mixer blending works but still nothing moves.

Can anyone help me figure out why this does not work?

  • line 48 and 49 turned into:
_output.SetSourcePlayable(_mixer, oldOutput.GetSourceOutputPort());

–EDIT:

okay no idea what went wrong here but the best solution is already there. tarahugger postet a much more complete example here: [Example] UnityTimelordBlender: A solution for blending timelines.

and this one works perfectly.

the one thing that I tried to do (just as a test) was to set the blend trigger bool from a timeline signal in the same timeline, and that breaks it.