Search Unity

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

Discussion in 'Timeline' started by doneykoo, Sep 8, 2017.

  1. doneykoo

    doneykoo

    Joined:
    Oct 28, 2016
    Posts:
    9
    Using Unity 2017.1.0p4
    I've created a playable asset script and a playable script.
    The playable asset script is like:
    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class MyPlayableAsset : PlayableAsset
    4. {
    5.     public ...; // params
    6.  
    7.     // Factory method that generates a playable based on this asset
    8.     public override Playable CreatePlayable(PlayableGraph graph, GameObject go) {
    9.        var p = ScriptPlayable<MyPlayable>.Create(graph);
    10.         var m = p.GetBehaviour();
    11.         m.Init(...); // init with params
    12.         return p;
    13.    }
    14. }
    15.  
    The playable script is like:
    Code (CSharp):
    1.  
    2. public class MyPlayable : PlayableBehaviour
    3.  
    4.     public void Init(...)
    5.     {
    6.     }
    7.    public override void OnGraphStart(Playable playable)
    8.    {
    9.    }
    10.    public override void OnBehaviourPlay(Playable playable, FrameData info) {
    11.     }
    12.  
    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)?
     
    Last edited: Sep 8, 2017
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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.

    Code (CSharp):
    1. public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    2. {
    3.     foreach (var clip in GetClips())
    4.     {
    5.         var myAsset = clip.Asset as MyPlayableAsset;
    6.         if (myAsset)
    7.         {
    8.             myAsset.start = clip.start;
    9.             myAsset.end = clip.end;  
    10.         }
    11.     }
    12.  
    13.     return base.CreateTrackMixer(graph, go, inputCount);
    14. }
     
    lofell, Qwolf0521 and Deadcow_ like this.
  3. doneykoo

    doneykoo

    Joined:
    Oct 28, 2016
    Posts:
    9
    Thanks for the clarification.

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

    Code (CSharp):
    1.  
    2.  
    3.         var playableDirector = _playableDirector;
    4.  
    5.         // clip access: @see: https://forum.unity3d.com/threads/access-the-animation-clip-i-created-in-a-playable-through-code.487193/
    6.         var timelineAsset = playableDirector.playableAsset as TimelineAsset;
    7.         foreach (var track in timelineAsset.GetOutputTracks())
    8.         {
    9.             var playableTrack = track as PlayableTrack;
    10.             if (playableTrack != null)
    11.             {
    12.                 foreach (var clip in playableTrack.GetClips())
    13.                 {
    14.                     var asset = clip.asset as JumpPointPlayableAsset;
    15.                     if (asset)
    16.                     {
    17.                         var start = clip.start;
    18.                         var end = clip.end;
    19.                         // ...
    20.                     }
    21.                 }
    22.             }
    23.         }
    24.  
     
  4. AubreyH

    AubreyH

    Joined:
    May 17, 2018
    Posts:
    18
    You can override playableasset's "double duration". When the playable that is created has its "GetDuration" called, it will link into that.

    Example:
    Code (CSharp):
    1.  public class CustomAnimationClip : PlayableAsset
    2. {
    3.      public AnimationClip Clip;
    4.      public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    5.      {
    6.          AnimationClipPlayable playable = AnimationClipPlayable.Create(graph, Clip );
    7.          playable.GetDuration();
    8.          return playable;
    9.      }
    10.  
    11.      public override double duration
    12.      {
    13.          get { return Clip.length; }
    14.      }
    15. }
    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.
     
    Covfefeh likes this.
  5. JSwigartPlayful

    JSwigartPlayful

    Joined:
    Feb 29, 2016
    Posts:
    18
  6. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,195
    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.
     
  7. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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.
     
    bomara561 likes this.
  8. LaurensMMonk

    LaurensMMonk

    Joined:
    Jun 6, 2018
    Posts:
    6
    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.
     
  9. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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.
     
  10. LaurensMMonk

    LaurensMMonk

    Joined:
    Jun 6, 2018
    Posts:
    6
    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

    Code (csharp):
    1. public class LoopData : PlayableBehaviour
    2. {
    3.     [NonSerialized]public TimelineClip Clip;
    4. }
    Set that from CreatePlayable in the Playable Clip
    Code (csharp):
    1. [Serializable]
    2. public class LoopClip : PlayableAsset, ITimelineClipAsset
    3. {
    4.     [NonSerialized]public TimelineClip clipPassthrough = null;
    5.     public LoopData template = new LoopData ();
    6.  
    7.     public ClipCaps clipCaps
    8.     {
    9.         get { return ClipCaps.None; }
    10.     }
    11.  
    12.     public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
    13.     {
    14.         template.Clip = clipPassthrough;
    15.         var playable = ScriptPlayable<LoopData>.Create (graph, template);
    16.    
    17.         //LoopData clone = playable.GetBehaviour ();
    18.         return playable;
    19.     }
    20. }
    Set that from the track
    Code (csharp):
    1. public class LoopTrack : TrackAsset
    2. {
    3.     public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    4.     {
    5.         var clips = GetClips();
    6.         foreach(var clip in clips)
    7.         {
    8.             var loopClip = clip.asset as LoopClip;
    9.             loopClip.clipPassthrough = clip;
    10.         }
    11.  
    12.         return ScriptPlayable<LoopMixer>.Create(graph, inputCount);
    13.     }
    14. }
    and then finally read it out in the processFrame of the mixer:
    Code (csharp):
    1. if (selectedIndex >= 0)
    2. {
    3.     var selectedPlayable = (ScriptPlayable<LoopData>)playable.GetInput(selectedIndex);
    4.     LoopData selectedPlayer = selectedPlayable.GetBehaviour();
    5.     Debug.Log("Clip Start: " + selectedPlayer.Clip.start);
    6. }
    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.
     
  11. AdamBL

    AdamBL

    Joined:
    Oct 27, 2016
    Posts:
    29
    FWIW I think this only works with c.extrapolatedStart instead of c.start, is that correct?
     
  12. zwoythaler

    zwoythaler

    Joined:
    Jan 3, 2019
    Posts:
    1
    Is this still a good option for Unity 2018.4? or is there a less convoluted method?
     
    SunnyChow and Menion-Leah like this.
  13. SunnyChow

    SunnyChow

    Joined:
    Jun 6, 2013
    Posts:
    360
    i want to get the ease in/out
     
  14. Ratslayer

    Ratslayer

    Joined:
    Feb 6, 2014
    Posts:
    37
    Ease In/Out data is all exposed in TimelineClip class.
     
  15. zheyuanzhou

    zheyuanzhou

    Joined:
    Oct 29, 2017
    Posts:
    22
    Thanks for this Post
     
  16. lofell

    lofell

    Joined:
    Nov 4, 2020
    Posts:
    7
    Good..!
    Thanks for this Post too!
    Too much Great Solutions are here
     
    Last edited: Jun 17, 2022