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:
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);
}
}