Modify clip timings per-instance from code

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!

1 Like

If it’s your custom playableBehaviour/track, I can think of some solutions:
In your PlayableBehaviour, maybe you can postpone your process some seconds.

        class SomePlayableBehaviour : PlayableBehaviour
        {
            // initialize this randomly:
            private float randomWaitTime;
            private float currentWaitedTime;

            public override void ProcessFrame(Playable playable, FrameData info, object playerData)
            {
                currentWaitedTime += info.deltaTime;
                if (currentWaitedTime < randomWaitTime) return;
                // do some work...
            }
        }

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.

        class TimeOffsetRuntimeClip : RuntimeClip
        {
            // how to initialize this depends on you!
            protected double timeOffset;

            public override void EvaluateAt(double localTime, FrameData frameData)
            {
                localTime += timeOffset; // I think there's a better solution, but this is just for demo
                base.EvaluateAt(localTime, frameData);
            }
            // ... you may also need to override other functions to get consistence
        }

// ... in your track asset

        internal override Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree)
        {
            // just copy the original function, but change RuntimeClip to your new clip
            // ...
            var clip = new TimeOffsetRuntimeClip(timelineClips[c], source, blend);
            tree.Add(clip);
            // ...
        }

One thing is that the access level of these functions are internal, so you may have to make .asmref to override them.

Whoah, I never considered using an asmref to modify an internal function. That’s genius! I’ll try these out for sure, thank you.

1 Like