导航

【Unity】Timeline探索记(2)第一个例子——字幕轨

Posted on 2020-08-23 09:23  Caiger  阅读(1875)  评论(0编辑  收藏  举报

探索官方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,底下是字幕,右上按钮点击播放。

下载

没有提及的

  • 这里对于Playable只是介绍了我遇到的种种问题,仍然没有具体讲Playable到底是什么,下次探索会展开。