探索官方Timeline插件Default Playables
起因
- 看电影时必然能看到下方字幕,所以我能确定字幕轨的实现效果。我也能确定字幕轨的实现肯定要用到Playable轨,但在写自定义Playable轨前,我并不清楚从哪里开始操作。我先在网上搜到了一篇博文,我先按着这篇博文操作,但在博文最后讲到脚本的部分让我犯了难。因为博文的实现轨道只用到一个脚本,和我印象中官方视频里用插件Default Playables生成的四个脚本(Data、Mixer、Track、Clip)不同,猜想博文应该是把好几个功能写在一起了。暂时无法理解这部分博文,所以我又回去打开了官方视频,下载了插件Default Playables,看看主讲人是怎么操作的。
探索
- 插件Default Playables下载导入Unity后,能看到它自带了好几个轨道。其中Light Control轨就是视频演示所用轨,Light Control轨的实现效果是播放时将灯光颜色变为自定颜色,播完完毕灯光回到默认颜色。第一次摸索,我按着视频操作生成了一样的LightControl轨,实现了视频演示功能。但LightControl功能不是我关注的重点,它的4个脚本(如下图)怎么组织才是我关心的,如果不用插件,我应该怎么写这4个脚本呢。
- 不考虑实现自定义Inspector面板的Editor脚本,只考虑Data、Mixer、Track、Clip这4个脚本,先来试着右键创建它们。从下图能看到,Unity只提供了两种选择,都各创建一个,去看看它们的默认脚本长啥样。
-
- 比较NewPlayableBehaviour脚本和NewPlayableAsset脚本,能看到两者继承的父类明显不同;NewPlayableBehaviour脚本中有很多方法,只看方法名就能感觉它们应该和编辑播放内容有关;而NewPlayableAsset脚本中只有一个CreatePlayable方法,这个方法名对还没理解Playable的我来说,简直摸不着头脑。
- 照着现成的Light Control轨,写好自己的4个脚本。能大概清楚如果我要写Data、Mixer,应该选NewPlayableBehaviour创建;要写Track、Clip,应该选NewPlayableAsset创建。对脚本的内容增删测试,能大概明白这4个脚本的基本写法、各自功能,以及这4者怎么关联在一起的。
- 探索到这里,我最多的问题是不明白常见到的定义和方法的部分代码:参数graph是谁?老看到的playable又是谁?Mixer脚本中的ProcessFrame方法中的playable.GetInputCount(),它为什么一定要求输入总数?这个输入又是谁?为什么要用到inputWeight?虽然我有去查网上的资料,但因为网上资料理论多实例少,它们不能马上回答我的种种疑惑。
-
对插件Default Playables探索这里暂时停止在“实现Light Control轨的4个脚本”处。虽然有些重要的新问题暂时无法解决。但当前重点是实现字幕轨,对Playable的探索先放在实现字幕轨后,而且我想在实现字幕轨过程中应该能得到一些和Playable有关的线索。(我在实现字幕轨后,再回头比较插件Default Playables实现成品,有了进一步理解和总结。再配合Playable Graph工具,才慢慢理解了让我困惑不已的Playable,这要下次探索才能具体展开了。)
实现字幕轨
环境
- Unity2017.4.39
实现过程
- 有了Default Playables探索经验,再次回到最开始的参考博文,我看出博文将Data、Mixer这两个脚本写在了一个脚本,Track、Clip他没有写出,而是覆盖了Unity提供参考的Playable轨。
- 我设想的字幕轨是这样的:有一条专门的轨道叫字幕轨,轨上可以放很多Clip,每一个Clip就是一行字幕内容。
- 不用插件Default Playables,我最先创建的是SubtitileTrack脚本,如下。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; using UnityEngine.UI; [TrackColor(0.854f, 0.274f, 0.811f)] [TrackClipType(typeof(SubtitileClip))] [TrackBindingType(typeof(Text))] [System.Serializable] public class SubtitileTrack : TrackAsset { public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) { return ScriptPlayable<SubtitileMixerBehaviour>.Create(graph, inputCount); } }
-
- TrackColor:轨道色,只能用小数表示,无法写成 215 / 255f 这种格式。
- TrackClipType:指定对应Clip脚本。这里是SubtitileClip。
- TrackBindingType:绑定对象。这里是Text。
- 方法CreateTrackMixer(...)指明了它需要Mixer和Mixer是谁。这里的Mixer是SubtitileMixerBehaviour。
- 接着,实现的是SubtitileClip脚本,如下。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; [System.Serializable] public class SubtitileClip : PlayableAsset, ITimelineClipAsset { public SubtitileBehaviour template = new SubtitileBehaviour(); public ClipCaps clipCaps { get { return ClipCaps.Blending; //选择None,clip当然不会支持快捷键淡入淡出操作 } } public override Playable CreatePlayable(PlayableGraph graph, GameObject go) { var playable = ScriptPlayable<SubtitileBehaviour>.Create(graph, template); return playable; } }
-
- 这里的template就是SubtitileBehaviour脚本暴露在Inspector面板上的地方,即要写的字幕内容。
- clipCaps:可选项如下图左,对应下图右橙框,选择要Clip的Inspector面板上的哪一块功能。如果选择None,Clip当然不会支持快捷键的淡入淡出编辑操作。
- 接下来是SubtitileBehaviour脚本,如下。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using UnityEngine.UI; [Serializable] public class SubtitileBehaviour : PlayableBehaviour { public string content; }
-
- 务必要有[Serializable],不然Clip的Inspector面板上会看不到任何东西。
- 接下来是复杂一点的SubtitileMixerBehaviour脚本,如下。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using UnityEngine.UI; public class SubtitileMixerBehaviour : PlayableBehaviour { private Text trackBinding = null; public override void OnGraphStart(Playable playable) { // 这个playable就是SubtitileMixerBehaviour } public override void OnGraphStop(Playable playable) { } public override void OnBehaviourPlay(Playable playable, FrameData info) { } public override void OnBehaviourPause(Playable playable, FrameData info) { } public override void PrepareFrame(Playable playable, FrameData info) { } public override void ProcessFrame(Playable playable, FrameData info, object playerData) { trackBinding = playerData as Text;
if (trackBinding == null)
{
return;
}
} }
-
- 这里的ProcessFrame(...)方法就是实现播放时字幕轨如何显示的地方,暂时不知道实现怎么写,先空着。
- 经测试,上面方法的执行顺序如下。
- 到这里,就能够拖出新鲜热乎的字幕轨摆在Timeline面板上了。我是这样摆的。
-
- 点击其中一个Clip,它的Inspector面板长这样。
- 只差重点实现了。最开始参考博文它的实现就是简单的gameObject.SetActive,但我照搬过来是无法成功的,因为这篇博文没有严格分出Mixer功能。最后我在youtube上翻到了别人实现的字幕轨视频,按着视频写才明白Mixer到底是什么和怎么用。视频演示了为什么要有Mixer—— 不用Mixer时,先将第一句字幕Clip移到播放开头后几秒的位置,按理来说开头就不会有字幕出现,但仍然会出现第一句字幕。虽然可以用空字幕填补没字幕出现时的空隙,但我们还有更聪明的做法,这时就出现了Mixer,实现重点是用InputWeight。
- 以下是SubtitileMixerBehaviour完整脚本。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using UnityEngine.UI; public class SubtitileMixerBehaviour : PlayableBehaviour { private Text trackBinding = null; public override void OnGraphStart(Playable playable) { // 这个playable就是SubtitileMixerBehaviour } public override void OnGraphStop(Playable playable) { } public override void OnBehaviourPlay(Playable playable, FrameData info) { } public override void OnBehaviourPause(Playable playable, FrameData info) { } public override void PrepareFrame(Playable playable, FrameData info) { } public override void ProcessFrame(Playable playable, FrameData info, object playerData) { trackBinding = playerData as Text; if (trackBinding == null) { return; } string content = ""; int inputCount = playable.GetInputCount(); for (int i = 0; i < inputCount; i++) { float inputWeight = playable.GetInputWeight(i); if (!Mathf.Approximately(inputWeight, 0f)) { ScriptPlayable<SubtitileBehaviour> inputPlayable = (ScriptPlayable<SubtitileBehaviour>)playable.GetInput(i); SubtitileBehaviour input = inputPlayable.GetBehaviour(); content = input.content; } } trackBinding.text = content; trackBinding.gameObject.SetActive(trackBinding.text != ""); } }
-
- inputCount:指这种轨上有多少个clip:比如只有一个轨,轨上有3个clip,则inputCount打印为3;如果有两个轨,一个轨上有3个clip,另一个轨上有1个clip,则inputCount打印依次为3和1。
-
inputWeight:一般来说,播到哪个clip,哪个clip的inputWeight就是1。如下图,第一个clip特意做了淡入效果,可以看到此时它的inputWeight约为0.6,如果不做淡入,则inputWeight为1。
效果
- 我做了一个简单的字幕轨应用效果。如下,场景中我放了一个Cube和一个Sphere,底下是字幕,右上按钮点击播放。
下载
- 我的字幕轨工程 G站链接。
没有提及的
- 这里对于Playable只是介绍了我遇到的种种问题,仍然没有具体讲Playable到底是什么,下次探索会展开。