How can I add curve keys and recording to a custom track?

I’m stumped trying to add keys and recording to a custom track.

I’m creating an object that needs to record its properties over time in a custom track.

I don’t want to give it an AnimatorController and an Animation track, because it doesn’t require an AnimatorController.

I also don’t want to use an Animation track because I want custom blending – in my case, I’m blending a camera view override.

So far I found these questions that indicate recording and keying on custom tracks was removed in 2019.3:

https://discussions.unity.com/t/784694
https://answers.unity.com/questions/1671231/how-to-enable-record-mode-for-a-custom-timeline-tr.html

Now I was able to gather that CreateCurves could be used to bring back curves.

A few questions:

  • Should I call TrackAsset.CreateCurves or TimelineClip.CreateCurves? Why?
  • Why do I have to call SetCurve after to get the curves button to appear after CreateCurves is called on a clip? What if I don’t know what curves I want in advance?
  • When should I call any of those methods?
  • How do I get the record button to show up?
  • In spite of the record button showing up, I can still hit Record in the Animation Window when I double click my curved clips, but nothing gets recorded – why?
  • Do I need to implement GatherProperties on the TrackAsset or on IPropertyPreview to get this to work?

@seant_unity said in one of the linked threads:

Which menu? Which left side panel? How are “fields that can be animated” automatically chosen?

You shouldn’t need to use the API to use animated parameters on tracks and clips. It is completely dependent on how the custom tracks and clips are structured.

However, animated parameters on clips and tracks are animating values on the Track/PlayableAsset, not on the target component like AnimationTracks do.

Here’s an example:

    [Serializable]
    public class TextPlayableBehaviour : PlayableBehaviour
    {
        public Color color = Color.white; // automatically animated
        public int fontSize = 14;        
        public string text = "";

        // ProcessFrame would copy these value to the Text object bound to the track...
    }


    // Represents the serialized data for a clip on the TextTrack
    [Serializable]
    public class TextPlayableAsset : PlayableAsset
    {
        public TextPlayableBehaviour template = new TextPlayableBehaviour();

        public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
        {
            // Using a template will clone the serialized values
            return ScriptPlayable<TextPlayableBehaviour>.Create(graph, template);
        }
    }

This works for either a track or a clip. Timeline will automatically detect that a template is being used inside the playable asset, and manage curves on the playable behaviour in the inline curve editor. The button to expand the curve editor is on the track header, like the animation track.

The curve editor will show only animatable properties. If it’s not currently animated, a constant curve is shown. Keys can be added (recorded) using the context menu in the inspector, or by editing the curve directly.

When you modify the curves in editor, those parameters are automatically updated before PrepareFrame or ProcessFrame calls are run on your PlayableBehaviour.

v1.4.5 fixes a bunch of bugs with these types of curves in the editor, so I’d recommend using that version if possible.

If you are copying the behaviour values to a Component, then you need to specify which Component values you are modifying in GatherProperties so when previewing is disabled, their scene values are properly restored. But you do not need to specify anything for PlayableBehaviour values.

If you want to set values in the API, then CreateCurves can be used to initialize the animation clip for the animated properties.

After creating the curve, values can be set via API using animation apis:
e.g.
AnimationUtility.SetEditorCurve(clip.curves, typeof(TextPlayableAsset), “color.r”, AnimationCurve.Linear(0,0,1,1));

Note - the type is the type of PlayableAsset (or track asset), but the “template”. prefix for the color parameter isn’t required.

I hope that helps.

1 Like

Hi Sean, thanks for the quick reply.

After multiple iterations on extending the timeline, I stopped using a template that I copy to the Behaviour on CreateInstance, and keep my data inside the PlayableAsset itself. As such, I don’t have a “template” field.

How exactly is the structural analysis done?

  • Is it by the field name “template”?
  • Is it by any PlayableBehaviour typed field on the PlayableAsset?
  • Is it inferred from the result of CreatePlayable?
  • Is it documented anywhere?

Can I gain access to this animation feature without a template field?

The answers to your four questions:

  • No, You don’t need to name it “template”.

  • Yes*

  • No, but only fields that exist on the type returned by CreatePlayable will be animated.

  • I’ll have to check.

  • Only fields serialized on a PlayableAsset as part of a PlayableBehaviour can be animated.

You can put as many PlayableBehaviours as you want in your PlayableAsset, but only the fields that exist on the PlayableBehaviour that you pass to ScriptPlayable<PlayableBehaviourT>.Create(graph, template); will be animated at Runtime.

You don’t need to pass in the template to ScriptPlayable.Create, although you should, but you will need to serialize the type of PlayableBehaviour that you instantiate in CreatePlayable in your PlayableAsset in order to get access to this animation feature.

Resurrecting this thread to leave some advice for future travelers:

If you are looking to add curves to your Tracks, rather than your Clips, your code should be a little different to the example posted above. Tracks use CreateTrackMixer() instead of CreatePlayable(), so your serialized fields should be inside your mixer type. Any serialized type that inherits from PlayableBehaviour() will draw keys in the Timeline editor, but you’ll only actually be able to query for animated values if you put them in your Mixer behaviour. (At least, that’s the only way I was able to!)

Your code should be something like:

    public class MyTrack : TrackAsset
    {
        [SerializeField] public MyMixer mixerTemplate;

        public sealed override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
        {
            return ScriptPlayable<MyMixer>.Create(graph, mixerTemplate, inputCount);
        }
    }
  
    public class MyMixer : PlayableBehaviour
    {
        [SerializeField] public float myAnimatableValue;
      
        // NOTE: This function is called at runtime and edit time.
        // Keep that in mind when setting the values of properties.
        public override void ProcessFrame(Playable playable, FrameData info, object playerData)
        {
            Debug.Log( myAnimatableValue );
        }
    }

Edit: it seems to be important that your mixer is public. Changing it to a private serialized field seems to cause it to not render properly in the Timeline.

1 Like