Skip to content

Instantly share code, notes, and snippets.

@alankent
Created January 21, 2022 16:46
Show Gist options
  • Save alankent/738a1b7fb7f1fafa85c9b2c21fed1ea3 to your computer and use it in GitHub Desktop.
Save alankent/738a1b7fb7f1fafa85c9b2c21fed1ea3 to your computer and use it in GitHub Desktop.
using UnityEngine;
using UnityEditor.Timeline;
using UnityEngine.Sequences.Timeline;
using UnityEngine.Timeline;
namespace UnityEditor.Sequences.Timeline
{
[CustomTimelineEditor(typeof(ProgressClip))]
public class ProgressClipEditor : ClipEditor
{
/// <inheritdoc cref="ClipEditor.GetClipOptions"/>
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
{
var options = base.GetClipOptions(clip);
// Brighten clip color when playhead over clip.
var director = TimelineEditor.inspectedDirector;
if (director != null && director.time >= clip.start && director.time <= clip.end)
{
options.highlightColor = options.highlightColor * 1.5f;
}
return options;
}
}
}
using UnityEditor.Timeline;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Sequences.Timeline
{
[CustomTimelineEditor(typeof(ProgressTrackAsset))]
public class ProgressTrackEditor : TrackEditor
{
public override TrackDrawOptions GetTrackOptions(TrackAsset track, Object binding)
{
var options = base.GetTrackOptions(track, binding);
//options.icon = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/_SHARED/Scripts/Progress Track/Editor/ProgressTrackIcon.png", typeof(Texture2D));
return options;
}
}
}
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor.Recorder.Timeline;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using UnityEngine.UI;
// GREAT VIDEO! https://www.youtube.com/watch?v=UEuM-Fckx5w
public class ProgressBehaviour : PlayableBehaviour
{
public float Alpha;
public Vector2 BarScale = Vector2.one;
public Vector2 BarPosition;
public bool ShowVideoFilename = false;
public string VideoFilename;
public PlayableDirector PlayableDirector;
private Canvas m_Canvas;
private Transform m_leftBar;
private Transform m_rightBar;
private Text m_filenameText;
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
// Time in seconds since start of clip and the duration of the clip.
// Because we want to reach 100% on the last frame inside the clip, we subtract one frame duration (plus a bit more for rounding errors)
// Note that this does not work when scrubbing the timeline in Editor mode as deltaTime is zero then, but when recording its fine.
float time = (float)playable.GetTime();
float clipDuration = ((float)playable.GetDuration()) - (1.5f * info.deltaTime);
float progress = time / clipDuration;
if (progress < 0f) progress = 0f;
if (progress > 1f) progress = 1f;
ScriptPlayable<ProgressMixerBehaviour> outputPlayable =
(ScriptPlayable<ProgressMixerBehaviour>)playable.GetOutput(0);
var outputPlayableBehaviour = outputPlayable.GetBehaviour();
m_Canvas = outputPlayableBehaviour.canvas;
// Fade in/out the alpha. Time in seconds since start of clip and the duration of the clip.
if (m_Canvas == null) return;
if (m_Canvas.transform.childCount == 0)
{
// if the board isn't loaded, load it
var leftGo = new GameObject("Left Bar");
leftGo.hideFlags = HideFlags.HideAndDontSave;
leftGo.transform.parent = m_Canvas.transform;
var leftImage = leftGo.AddComponent<Image>();
leftImage.color = new Color(1f, 1f, 1f, Alpha);
m_leftBar = leftGo.transform;
var rightGo = new GameObject("Right Bar");
rightGo.hideFlags = HideFlags.HideAndDontSave;
rightGo.transform.parent = m_Canvas.transform;
var rightImage = rightGo.AddComponent<Image>();
rightImage.color = new Color(1f, 1f, 1f, Alpha / 2f);
m_rightBar = rightGo.transform;
var filenameGo = new GameObject("Filename");
filenameGo.hideFlags = HideFlags.HideAndDontSave;
filenameGo.transform.parent = m_Canvas.transform;
m_filenameText = filenameGo.AddComponent<Text>();
// Set Text component properties.
m_filenameText.color = new Color(1f, 1f, 1f, 0.75f);
m_filenameText.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
m_filenameText.fontSize = 32;
m_filenameText.lineSpacing = 1;
m_filenameText.alignment = TextAnchor.MiddleCenter;
m_filenameText.horizontalOverflow = HorizontalWrapMode.Overflow;
m_filenameText.verticalOverflow = VerticalWrapMode.Overflow;
//m_filenameText.transform.localPosition = new Vector3(-Screen.width / 2f, -Screen.height / 2f, 0f);
//m_filenameText.transform.localPosition = new Vector3(Screen.width * -0.4f, Screen.height -0.4f, 0f);
m_filenameText.transform.localPosition = new Vector3(0f, 0f, 0f);
m_filenameText.transform.localScale = Vector2.one;
m_filenameText.transform.rotation = Quaternion.Euler(Vector3.zero);
}
else
{
for (int i = 0; i < m_Canvas.transform.childCount; i++)
{
var child = m_Canvas.transform.GetChild(i);
//child.transform.localPosition = BarPosition;
//child.localScale = BarScale;
}
}
var barX = (progress - 0.5f) * Screen.width;
var leftX = -Screen.width / 2f;
var rightX = Screen.width / 2f;
var barY = -Screen.height / 2f;
m_leftBar.localPosition = new Vector3((leftX + barX) / 2f, barY, 0f) * BarScale;
m_rightBar.localPosition = new Vector3((barX + rightX) / 2f, barY, 0f) * BarScale;
m_leftBar.localScale = new Vector3((barX - leftX) * 0.01f, 0.2f, 1f) * BarScale;
m_rightBar.localScale = new Vector3((rightX - barX) * 0.01f, 0.2f, 1f) * BarScale;
if (ShowVideoFilename)
m_filenameText.text = VideoFilename; // TODO: Could try to update based on current frame number to make it dynamic.
else
m_filenameText.text = null;
//Debug.Log("Prog=" + (progress - 0.5f)+ " lx=" + leftX + " rx=" + rightX + " barX=" + barX);
}
public override void OnBehaviourPause(Playable playable, FrameData info)
{
if (m_Canvas == null) return;
while (m_Canvas.transform.childCount > 0)
{
var child = m_Canvas.transform.GetChild(0);
child.SetParent(null);
#if UNITY_EDITOR
Object.DestroyImmediate(child.gameObject);
#else
Object.Destroy(child.gameObject);
#endif
}
}
}
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor.Recorder.Timeline;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
// Contains clip data which is passed to the behavior at run time.
public class ProgressClip : PlayableAsset, ITimelineClipAsset
{
[Range(0f, 1f)] public float Alpha = 0.5f;
public Vector2 BarScale = new Vector2(0.875f * 0.95f, 0.875f * 0.95f);
public bool ShowVideoFilename = false;
public string VideoFilename;
// We dont support blending or anything fancy.
public ClipCaps clipCaps => ClipCaps.None;
private PlayableDirector m_playableDirector;
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
// 'owner' you can call GetComponent<PlayableDirector>(); on
var playable = ScriptPlayable<ProgressBehaviour>.Create(graph);
if (playable.GetBehaviour() != null)
SetAttributes(playable.GetBehaviour());
// Look for a recorder track that starts at the same time we do and steal its filename.
m_playableDirector = owner.GetComponent<PlayableDirector>();
return playable;
}
void SetAttributes(ProgressBehaviour progressBehavior)
{
FindVideoFilename();
progressBehavior.Alpha = Alpha;
progressBehavior.BarScale = BarScale;
progressBehavior.ShowVideoFilename = ShowVideoFilename;
progressBehavior.VideoFilename = VideoFilename;
}
private void FindVideoFilename()
{
VideoFilename = null;
if (m_playableDirector == null) return;
var timelineAsset = m_playableDirector.playableAsset as TimelineAsset;
foreach (var track in timelineAsset.GetOutputTracks())
{
var recorderTrack = track as RecorderTrack;
if (recorderTrack != null)
{
foreach (var clip in recorderTrack.GetClips())
{
//TODO: if (clip.start == myStart)? This finds the first recorder clip, but it might be a Timeline has multiple recorder clips...
var asset = clip.asset as RecorderClip;
if (asset)
{
VideoFilename = Path.GetFileName(asset.settings.OutputFile);
}
}
}
}
}
}
using UnityEngine;
using UnityEngine.Playables;
public class ProgressMixerBehaviour : PlayableBehaviour
{
Canvas m_Canvas;
public Canvas canvas => m_Canvas;
/// <inheritdoc cref="PlayableBehaviour.OnPlayableCreate"/>
public override void OnPlayableCreate(Playable playable)
{
var canvasGo = new GameObject("Video Progress Bar");
canvasGo.hideFlags = HideFlags.HideAndDontSave;
m_Canvas = canvasGo.AddComponent<Canvas>();
m_Canvas.renderMode = RenderMode.ScreenSpaceOverlay;
}
/// <inheritdoc cref="PlayableBehaviour.OnPlayableDestroy"/>
public override void OnPlayableDestroy(Playable playable)
{
#if UNITY_EDITOR
Object.DestroyImmediate(m_Canvas.gameObject);
#else
Object.Destroy(m_Canvas.gameObject);
#endif
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
// Progress bar track - holds progress bar clips.
[Serializable]
[DisplayName("Progress Bar Track")]
[TrackColor(111 / 255f, 43 / 255f, 111 / 255f)] // To ask UX
[TrackClipType(typeof(ProgressClip))]
public class ProgressTrackAsset : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
var mixer = ScriptPlayable<ProgressMixerBehaviour>.Create(graph, inputCount);
//mixer.GetBehaviour().canvas.sortingOrder = 1;
return mixer;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment