Custom Activation Track

Hello,

I’m trying to write a custom Timeline track/clip system that also includes the functionality of the built-in Activation track.

I’ve already set up my custom scripts following the Extending Timeline blog post, and my code in XxxBehaviour.ProcessFrame() is working fine.

I’m assuming I now need a way to get a reference to the GameObject linked to the Track, so that I can call SetActive() on it whenever OnBehaviourPause() and OnBehaviourPlay() are called.

Is my thinking correct? If so, I can’t figure out how to get access to this GameObject. I can keep the reference to the object passed in ProcessFrame(), but it gets nullified every once in a while, it looks like PlayableAsset.CreatePlayable gets called again and clears everything.

Hello there, if I understand your question then unfortunately I have bad news: this is extremely difficult to work around because of the nature of Timeline. Timelines are made of Playable graphs which are reconstructed when a change happens or when entering play mode - I believe this will be what is nulling the GameObject reference that you are using. Can you elaborate on exactly what you want to the custom playable to do? There might be a work around in the details.

For the activation track, we took a slightly different approach. We used just a plain old playable for the clips, and a track mixer behaviour that enables/disables the gameObject (passed into ProcessFrame) based on whether the mixer playable has an input with non-zero weight.

This approach, thought marginally less efficient, was far more robust and deterministic.

@JamesB Here’s what I’m trying to do: I basically have an object that’s a sequence with a start and end frame, and I want to retime it so that it uses an animation clip to play through from start to end over the duration of the clip. So if the clip lasts 2 seconds, the sequence plays over 2 seconds, if the clip lasts 10 seconds, the sequence plays over 10 seconds, etc.

My PlayableBehaviour looks something like this:

    public class FrameSequenceBehaviour
        : PlayableBehaviour
    {
        public override void ProcessFrame(Playable playable, FrameData info, object playerData)
        {
            var frameSequence = playerData as FrameSequence;
            if (frameSequence != null)
            {
                var pct = (float)(playable.GetTime() / playable.GetDuration());
                frameSequence.SetLerpPct(pct);
            }
        }
    }

What I’d like to do is also make it so that frameSequence.gameObject gets enabled/disabled when the the playhead is off any clip in the track. Basically combining this with how the Activation track works.

I was able to get this working with an ExposedReference, but I don’t want to use that, and would rather just use the object associated with the Track, so that I can just change it for the whole track and not on a per clip basis.

@seant_unity Does the clip have to be a “plain old playable” for that approach to work? Do you think something similar could work if I moved my code to a Mixer?

Thanks for the help!

@prisonerjohn After extensive testing, there is no perfectly robust way to perform an operation when a PlayableBehaviour starts and ends. There are lots of ways of getting close but I’ve run into the same problems as you’re having and always hit edge cases where the operation is not performed. I’ve discussed it with @seant_unity at length and unless something has changed in the API or something in the underlying code, it’s just not possible. My recommendation would be to use a separate Activation track. Not really what you want to hear I’d imagine. Who knows, maybe Sean will leave a post correcting me. (Please correct me Sean!)

It doesn’t have to be a plain old playable at all, it can be any type of playable behaviour. In fact, it will work well with the already existing (clip) behaviour you have.

Here’s the process frame for track behaviour:

        public override void ProcessFrame(Playable playable, FrameData info, object playerData)
        {
            var boundGameObject = playerData as GameObject;
            if (boundGameObject == null)
                return;

            int inputCount = playable.GetInputCount();
            bool hasInput = false;
            for (int i = 0; i < inputCount; i++)
            {
                if (playable.GetInputWeight(i) > 0)
                {
                    hasInput = true;
                    break;
                }
            }

            boundGameObject.SetActive(hasInput);
        }

Thanks @seant_unity , that seems to be working!

I had to put that code in a mixer behaviour, it wouldn’t work if I put it directly in the playable behaviour, the input count (or weights) were coming back empty.

Exactly. By track behaviour, I meant mixer :slight_smile:

@prisonerjohn Bit of a mix up on my part. When I said that you can’t cover all edge cases, I was referring to things that happen if you don’t let the playable go all the way through. To catch this you need to make sure your boundGameObject is set correctly in the OnGraphStop and OnPlayableDestroy methods (you’ll need to cache the boundGameObject of course). The reason I was pessimistic about it working was that a few months ago there was a bug where OnGraphStop wasn’t always getting called when it should meaning there was an edge case that could not be covered. I can’t remember the exact details of it now but the bug has been fixed so there should be no more problems.

Sorry for the mix up, my bad.

Thanks @JamesB , I’ll make sure to add relevant code in those methods to cover those cases.

Hi @seant_unity
Replicating ActivationTrack code, Mixer is destroyed when last clip is deleted from track
and never created if there are no clips on track .

what’s the difference between Activation track and custom track
Why can’t Mixer exist without clips on the track ?

Thanks in advance

I think the mixer not existing without clips is just a legacy decision we haven’t yet corrected.

More accurately, a mixer playable is created when there are clips or animated track parameters on the track. The original assumption was that a track wasn’t needed if it has no clips, but we’ve learned there are use cases users want where this does not apply.

A workaround is to have an animted parameter in your mixer you don’t actually use… see TimelineEditor: TrackAsset.Curves not dispalyed in inline curve editor - Unity Engine - Unity Discussions for an example.

1 Like

Sorry, i don’t understand “A workaround is to have an animted parameter in your mixer you don’t actually use” and neither does this example.

Could you please write a more specific example?

Thanks in advance