Search Unity

Question Modify clip timings per-instance from code

Discussion in 'Timeline' started by jonagill, Apr 14, 2023.

  1. jonagill

    jonagill

    Joined:
    Oct 5, 2019
    Posts:
    12
    Hi all,

    Does anyone know if it's possible to manually modify the start time of a Timeline clip in particular PlayableDirector's graph? My use case is that I have a lot of enemy objects, each with a PlayableDirector running an instance of the same TimelineAsset. To add some variety, there are some clips that I would like to randomly alter the start time of (without changing the graphs of any other enemies).

    I was hoping I'd be able to achieve this by calling
    scriptPlayable.SetTime()
    in my
    CreatePlayable()
    method, but I'm not sure that works in practice. It looks like the clip timings get piped in each frame from
    TimelinePlayable.Evaluate()
    calling
    RuntimeClip.EvaluateAt()
    , which reads in timing data from the serialized TimelineClip. I'm guessing this means I'd have to modify the TimelineClip to change the timings, which would then propagate the changes across every enemy, not just the ones I intend to modify.

    Thoughts or ideas would be appreciated!
     
    Yuchen_Chang likes this.
  2. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    126
    If it's your custom playableBehaviour/track, I can think of some solutions:
    In your PlayableBehaviour, maybe you can postpone your process some seconds.
    Code (CSharp):
    1.         class SomePlayableBehaviour : PlayableBehaviour
    2.         {
    3.             // initialize this randomly:
    4.             private float randomWaitTime;
    5.             private float currentWaitedTime;
    6.  
    7.             public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    8.             {
    9.                 currentWaitedTime += info.deltaTime;
    10.                 if (currentWaitedTime < randomWaitTime) return;
    11.                 // do some work...
    12.             }
    13.         }
    Another solution:
    The RuntimeClip is created by
    TrackAsset.CompileClips()
    , and both EvaluateAt() and CompileClips() are overridable, so I think you can create your own RuntimeClip child class, and then override both of them to customize.

    Code (CSharp):
    1.         class TimeOffsetRuntimeClip : RuntimeClip
    2.         {
    3.             // how to initialize this depends on you!
    4.             protected double timeOffset;
    5.  
    6.             public override void EvaluateAt(double localTime, FrameData frameData)
    7.             {
    8.                 localTime += timeOffset; // I think there's a better solution, but this is just for demo
    9.                 base.EvaluateAt(localTime, frameData);
    10.             }
    11.             // ... you may also need to override other functions to get consistence
    12.         }
    13.  
    14. // ... in your track asset
    15.  
    16.         internal override Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree)
    17.         {
    18.             // just copy the original function, but change RuntimeClip to your new clip
    19.             // ...
    20.             var clip = new TimeOffsetRuntimeClip(timelineClips[c], source, blend);
    21.             tree.Add(clip);
    22.             // ...
    23.         }
    One thing is that the access level of these functions are internal, so you may have to make .asmref to override them.
     
    Last edited: Apr 14, 2023
  3. jonagill

    jonagill

    Joined:
    Oct 5, 2019
    Posts:
    12
    Whoah, I never considered using an asmref to modify an internal function. That's genius! I'll try these out for sure, thank you.
     
    Yuchen_Chang likes this.