Code Example: How To Execute Logic At The Beginning And End Of A Clip

Inspired by this post: Code Example : How To Detect The End Of The Playable Clip , and enabled by @drihpee_1 to execute this, I wanted to share some simple code that lets you do two things:

When Timeline enters a Behaviour (clip, in my mind), run some code, and when it exits (this is possible again by @drihpee_1 in the above thread), run other code.

I wanted to enable some clips where for the course of an animation, I can disable player input, for example, and be able to do that straight from a timeline. Or, trigger a script function just on the start to initiate some other script.

This is the entire set of code you need to create a custom track that handles these clips, so it looks something like this, just simple blocks that will set some state for their duration:
4427011--404266--upload_2019-4-14_14-8-11.png

I hope this helps someone else who just wants to do basic things with Timelines. If you want to learn how to do more with blending, I recommend the Subtitle Example available here: Timeline: struggling with nuances of custom script clips

If any of the information is incorrect please let me know so I can update this post.

SimpleBehaviour.cs implements your actual runtime logic in the context of the clip on the timeline:

using System;
using UnityEngine;
using UnityEngine.Playables;

[Serializable]
public class SimpleBehaviour : PlayableBehaviour
{
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        // Only execute in Play mode
        if (Application.isPlaying)
        {
            // Execute your starting logic here, calling into a singleton for example
            Debug.Log("Clip started!")
        }
    }

    // source: https://discussions.unity.com/t/739066
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        // Only execute in Play mode
        if (Application.isPlaying)
        {
            var duration = playable.GetDuration();
            var time = playable.GetTime();
            var count = time + info.deltaTime;
           
            if ((info.effectivePlayState == PlayState.Paused && count > duration) || Mathf.Approximately((float)time, (float)duration))
            {
                // Execute your finishing logic here:
                Debug.Log("Clip done!")
            }
            return;
        }
    }
}

SimpleClip.cs takes care of storing the clip as a PlayableAsset:

using System;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

[Serializable]
public class SimpleClip : PlayableAsset, ITimelineClipAsset
{
    // Create the runtime version of the clip, by creating a copy of the template
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        return ScriptPlayable<SimpleBehaviour>.Create(graph);
    }

    // Make sure we disable all blending since we aren't handling that in the mixer
    public ClipCaps clipCaps
    {
        get { return ClipCaps.None; }
    }
}

SimpleMixerBehaviour.cs is required since we are implementing a custom track, maybe there is a way around not needing a mixer but I haven’t found how:

using UnityEngine;
using UnityEngine.Playables;

public class SimpleMixerBehaviour : PlayableBehaviour
{
    // Override the ProcessFrame because we want to have our own color coded tracks
    // to keep things in the Editor visually clean
    public override void ProcessFrame(Playable playable, FrameData info, object playerData) { }
}

SimpleTrack.cs instantiates the mixer at runtime and specifies what kind of clips it handles:

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

[TrackColor(1.0f, 0.0f, 0.0f)]
[TrackClipType(typeof(SimpleClip))]
// No track binding since we're executing general logic during our scene
public class SimpleTrack : TrackAsset
{
    public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    {
        // This Mixer won't do anything, but I want to use our own track type instead of a PlayableTrack to keep things simple
        return ScriptPlayable<SimpleMixerBehaviour>.Create(graph, inputCount);
    }
}
7 Likes

any short video showing which script to add and where?? thx in advance

Thank you So Much for this!
This makes some things I was doing much cleaner and easier. I’ll definitely be expanding on this approach!

For Kabab and anyone else who doesn’t understand how to use this:

SimpleBehaviour - this script is where your runtime behaviour goes. Whatever the in-game action you want to perform is, put it here.

SimpleTrack - with this script added to your project you can right-click in any existing timeline and add a “SimpleTrack” instance.
The simple track can host instances of the SimpleClip.
SimpleClip - the code provided doesn’t do much, it just starts and pauses the SimpleBehaviour BUT you can add more to it, such as variables, e.g.:

public ExposedReference<GameObject> Target;
public int someValue;

If you add this to SimpleClip then your clip can have a reference to a gameobject assigned to it, and you can type an int in that field in the inspector.

But to use these variables you have to get them into the SimpleBehaviour, so lets suppose you add a method called SetMyVariables to the SimpleBehaviour.
Then you can do this inside the SimpleClip CreatePlayable method:

    public ExposedReference<GameObject> Target;

    public int someValue;
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        ScriptPlayable<SimpleBehaviour> playable = ScriptPlayable<SimpleBehaviour>.Create(graph);
        SimpleBehaviour playableBehaviour = playable.GetBehaviour();
        //now you can pass your varibles into the SimpleBehaviour
        playableBehaviour.SetMyVariables(Target.Resolve(graph.GetResolver()), someValue);
        return playable;
    }

And boom, now you can assign gameobjects and variables in the Clip in the Timeline, and when that clip is run it will create the SimpleBehaviour you wrote and do whatever it is you want it to do.

The names “Simple” are just an example.
I’m creating a BaseClip and inheriting from it so that my SimpleTrack (renamed to GameLogicTrack) can contain many clip types that each run their own behaviours.