Skip to content

Instantly share code, notes, and snippets.

@dimmduh
Last active February 7, 2021 09:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dimmduh/033acb2dad9f9b4a6532534a2fe0a189 to your computer and use it in GitHub Desktop.
Save dimmduh/033acb2dad9f9b4a6532534a2fe0a189 to your computer and use it in GitHub Desktop.
Timeline custom tracks - base classes. Still work in progress.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Playables;
public abstract class BaseClip : PlayableAsset
{
[HideInInspector, SerializeField]
[Bind(nameof(BasePlayableBehaviour.clipStartTime))]
public float startTime;
[HideInInspector, SerializeField]
[Bind(nameof(BasePlayableBehaviour.clipEndTime))]
public float endTime;
public ScriptPlayable<T> CreatePlayable<T>(PlayableGraph graph, params string[] bindFields) where T : BasePlayableBehaviour, new()
{
return CreatePlayable<T>(graph, bindFields.ToDictionary(s => s, s => s));
}
public ScriptPlayable<T> CreatePlayable<T>(PlayableGraph graph, Dictionary<string, string> bindFields = null) where T : BasePlayableBehaviour, new()
{
var playable = ScriptPlayable<T>.Create(graph);
var behaviour = playable.GetBehaviour();
if (bindFields == null)
bindFields = new Dictionary<string, string>();
//read from attributes
var bindFieldsWithAttribute = GetType().GetFieldsWithAttribute<BindAttribute>();
foreach (var fieldInfo in bindFieldsWithAttribute)
{
var targetName = fieldInfo.GetCustomAttribute<BindAttribute>().targetFieldName.NullIfEmpty() ?? fieldInfo.Name;
bindFields.Add(fieldInfo.Name, targetName);
}
//fill values
foreach (var bindField in bindFields)
{
var value = z_Utils.GetFieldValue(this, bindField.Key);
//resolve complex value
var valueType = value.GetType();
if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(ExposedReference<>))
{
var method = value.GetType().GetMethod("Resolve");
value = method.Invoke(value, new []{graph.GetResolver()});
}
z_Utils.SetFieldValue(behaviour, bindField.Value, value);
}
return playable;
}
public virtual string GetDisplayName()
{
return null;
}
}
using UnityEditor.Timeline;
using UnityEngine.Timeline;
[CustomTimelineEditor(typeof(BaseClip))]
public class BaseClipEditor : ClipEditor
{
public override void OnClipChanged(TimelineClip clip)
{
base.OnClipChanged(clip);
var newDisplayName = ((BaseClip) clip.asset).GetDisplayName();
if (newDisplayName != null)
clip.displayName = newDisplayName;
}
}
using UnityEngine;
using UnityEngine.Playables;
public class BasePlayableBehaviour : PlayableBehaviour
{
public float clipStartTime;
public float clipEndTime;
private static double timelineStopTime;
protected PlayableDirector GetDirector(Playable playable)
{
return (PlayableDirector) playable.GetGraph().GetResolver();
}
protected void SetDirectorSpeed(Playable playable, float value)
{
GetDirector(playable).playableGraph.GetRootPlayable(0).SetSpeed(value);
}
public override void OnPlayableCreate(Playable playable)
{
Debug.Log("OnPlayableCreate");
}
public override void OnGraphStop(Playable playable)
{
var director = GetDirector(playable);
if (director && director.state == PlayState.Paused)
{
timelineStopTime = director.time;
OnTimelinePause(playable);
}
}
public override void OnGraphStart(Playable playable)
{
var director = GetDirector(playable);
if (director && director.state == PlayState.Playing)
{
if (director.time + 0.001 < timelineStopTime)
OnTimelineRestart(playable);
else
OnTimelineResume(playable);
}
}
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
var duration = playable.GetDuration();
var time = playable.GetTime();
var prevTime = time - info.deltaTime;
if (info.effectivePlayState == PlayState.Playing)
{
if (prevTime <= 0)
OnClipStart(playable);
else if (time >= 0 && time < duration)
{
OnClipResume(playable);
}
}
}
public override void OnBehaviourPause(Playable playable, FrameData info)
{
var duration = playable.GetDuration();
var count = playable.GetTime() + info.deltaTime;
if ((info.effectivePlayState == PlayState.Paused && count > duration) || playable.GetGraph().GetRootPlayable(0).IsDone())
{
OnClipFinish(playable);
}
var director = (PlayableDirector) playable.GetGraph().GetResolver();
if (director && director.state == PlayState.Paused && info.effectivePlayState == PlayState.Playing && count < duration)
{
OnClipPause(playable);
}
}
protected virtual void OnTimelinePause(Playable playable)
{
Debug.Log("OnTimelinePause");
}
protected virtual void OnTimelineResume(Playable playable)
{
Debug.Log("OnTimelineResume");
}
protected virtual void OnTimelineRestart(Playable playable)
{
Debug.Log("OnTimelineRestart");
}
protected virtual void OnClipResume(Playable playable)
{
}
protected virtual void OnClipPause(Playable playable)
{
}
protected virtual void OnClipStart(Playable playable)
{
}
protected virtual void OnClipFinish(Playable playable)
{
}
}
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
public class BaseTrackAsset : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
SetStartEndTimeToClips();
return ScriptPlayable<TimeControlMixerBehaviour>.Create(graph, inputCount);
}
protected void SetStartEndTimeToClips()
{
foreach (var clip in GetClips())
{
if (clip.asset is BaseClip customClip)
{
//todo is it ok to cast to float?
customClip.startTime = (float) clip.start;
customClip.endTime = (float) clip.end;
}
}
}
}
using System;
namespace UnityEngine.Playables
{
public class BindAttribute : Attribute
{
public string targetFieldName;
public BindAttribute()
{
}
public BindAttribute(string targetFieldName)
{
this.targetFieldName = targetFieldName;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment