How to access the clip timing (start, end time) in PlayableBehaviour functions

Using Unity 2017.1.0p4
I’ve created a playable asset script and a playable script.
The playable asset script is like:

[System.Serializable]
public class MyPlayableAsset : PlayableAsset
{
    public ...; // params

    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go) {
       var p = ScriptPlayable<MyPlayable>.Create(graph);
        var m = p.GetBehaviour();
        m.Init(...); // init with params
        return p;
   }
}

The playable script is like:

public class MyPlayable : PlayableBehaviour

    public void Init(...)
    {
    }
   public override void OnGraphStart(Playable playable)
   {
   }
   public override void OnBehaviourPlay(Playable playable, FrameData info) {
    }

the Init(…) function will be called in playable asset script CreatePlayable(…)

For now I could only access the duration by playable.GetDuration() in my playable script.
The question is, in my playable script, how do I access the clip timing (start and end time)?

Short Answer: you can’t. The PlayableAsset has no back reference to a clip.

Workaround: In your track class you can copy the data from the clip to your custom asset during the creation of the playable graph. The Track Mixer is created before the clips create their playables.

public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
    foreach (var clip in GetClips())
    {
        var myAsset = clip.Asset as MyPlayableAsset;
        if (myAsset)
        {
            myAsset.start = clip.start;
            myAsset.end = clip.end;   
        }
    }

    return base.CreateTrackMixer(graph, go, inputCount);
}
3 Likes

Thanks for the clarification.

That’s right, now I’ve successfully worked it out by accessing them during my initialization process.

        var playableDirector = _playableDirector;

        // clip access: @see: https://forum.unity3d.com/threads/access-the-animation-clip-i-created-in-a-playable-through-code.487193/
        var timelineAsset = playableDirector.playableAsset as TimelineAsset;
        foreach (var track in timelineAsset.GetOutputTracks())
        {
            var playableTrack = track as PlayableTrack;
            if (playableTrack != null)
            {
                foreach (var clip in playableTrack.GetClips())
                {
                    var asset = clip.asset as JumpPointPlayableAsset;
                    if (asset)
                    {
                        var start = clip.start;
                        var end = clip.end;
                        // ...
                    }
                }
            }
        }
4 Likes

You can override playableasset’s “double duration”. When the playable that is created has its “GetDuration” called, it will link into that.

Example:

 public class CustomAnimationClip : PlayableAsset
{
     public AnimationClip Clip;
     public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
     {
         AnimationClipPlayable playable = AnimationClipPlayable.Create(graph, Clip );
         playable.GetDuration();
         return playable;
     }

     public override double duration
     {
         get { return Clip.length; }
     }
}

Note, however, that the Time of a Playable gets clamped by the value of GetDuration, so you should only use this for non looping clips. Sigh.

1 Like

Sure you don’t just want the time and duration of your playable? Those are available in PlayableExtensions

Doesn’t seem advisable to do something that is concerned with its location in the broader timeline.

Once this data has been set, how would be be accessed from within the mixer, specifically within ProcessFrame? I have access to the Playable via GetInput(), but I don’t see how this gets back to the Clip.

It doesn’t. The playable is a ‘compiled’ version of the clip, and doesn’t refer back to the source data.

In either your TrackAsset.CreateTrackMixer() override (where the mixer playable is created), or in your PlayableAsset.CreatePlayable override (where the clip playable is created), you can set additional parameters including the source clip.

In the example above from doneykoo above, the start and end times of the clips are being set on the playable asset from CreateTrackMixer(). The playable asset then passes those values to the playable created in CreatePlayable(). You could do the same with the clip reference itself. Although, I recommend not serializing any fields that reference a TimelineClip in your playable asset.

1 Like

Because it doesn’t work in builds? The downside of copying the data in CreateTrackMixer is that it won’t reflect changes due to dragging the starts and ends of clips.
Serializing the TimelineClips works great in editor(odd code aside), giving me realtime information about the new start and end times, but yeah… it breaks in builds.

TimelineClip is not a UnityObject. So when two objects reference the same clip, when they are reloaded, they each end up with their own copy.

I see. Well, then I guess it’s fine as long as we’re not relying on any serialization. We shouldn’t need that, if it’s set every single time the trackmixer is created, right?

I’ll just store temporary copies in the Data class then

public class LoopData : PlayableBehaviour
{
    [NonSerialized]public TimelineClip Clip;
}

Set that from CreatePlayable in the Playable Clip

[Serializable]
public class LoopClip : PlayableAsset, ITimelineClipAsset
{
    [NonSerialized]public TimelineClip clipPassthrough = null;
    public LoopData template = new LoopData ();

    public ClipCaps clipCaps
    {
        get { return ClipCaps.None; }
    }

    public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
    {
        template.Clip = clipPassthrough;
        var playable = ScriptPlayable<LoopData>.Create (graph, template);
    
        //LoopData clone = playable.GetBehaviour ();
        return playable;
    }
}

Set that from the track

public class LoopTrack : TrackAsset
{
    public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    {
        var clips = GetClips();
        foreach(var clip in clips)
        {
            var loopClip = clip.asset as LoopClip;
            loopClip.clipPassthrough = clip;
        }

        return ScriptPlayable<LoopMixer>.Create(graph, inputCount);
    }
}

and then finally read it out in the processFrame of the mixer:

if (selectedIndex >= 0)
{
    var selectedPlayable = (ScriptPlayable<LoopData>)playable.GetInput(selectedIndex);
    LoopData selectedPlayer = selectedPlayable.GetBehaviour();
    Debug.Log("Clip Start: " + selectedPlayer.Clip.start);
}

I suppose the NonSerialized attribute is only there to signal intent, but this seems to work both in editor and in builds, yay. Even if it’s quite convoluted.

7 Likes

FWIW I think this only works with c.extrapolatedStart instead of c.start, is that correct?

Is this still a good option for Unity 2018.4? or is there a less convoluted method?

2 Likes

i want to get the ease in/out

Ease In/Out data is all exposed in TimelineClip class.

Thanks for this Post

Good…!
Thanks for this Post too!
Too much Great Solutions are here