今天我们再来看看Timeline中的Track,并且动手实现一个老年迪斯科场景。

探索TimelinePlayableAPI,让Timeline为所欲为_ide

探索TimelinePlayableAPI,让Timeline为所欲为_sed_02

Track

Timeline可以添加很多Track来对不同的对象做控制。

  • Track   在Clip上控制动画,可以在有Animator组件但是没有AnimatorController的情况下直接播放动画。
  • Audio Track   在Clip上控制音频播放。
  • Activtion Track   在Clip上控制物体是否在场景中Active,GameObject.SetActive()的作用
  • Control Track   在Clip上可以控制和时间相关的元素,可以控制粒子系统,克隆物体,控制另一个timeline等。厉害的是我们放在Control Track上的例子特效,可以随意地拖动Timeline来预览效果,对特效兄弟来说是应该很强大很爽的地方。
  • Playable Track   控制继承自BasicPlayableBehaviour的clip
      (在2017.4.0f1版本里原先的BasicPlayableBehaviour因为性能原因已经被弃用,建议采用PlayableAsset和PlayableBehaviour)

如何有了这篇文章

虽然上面提供的几个track已经有很强大的功能了,但是实际操作中我们会发现这些远远不够,官方的Mannul上也没有更多的介绍,有效的资料是少之又少。那么官方演示的demo里的对话系统是怎么通过Timeline实现的呢?
于是我开始寻找,在Youtube上找到了Unite Europe2017上的演讲视频,Extending Timeline with your own playables(https://www.youtube.com/watch?v=uBPRfcox5hE&t=2331s)。

研究了一番,里面的例子就是LightControl将自己的理解分享给大家。让大家都能在Timeline里为所欲为。

深度理解Timeline

探索TimelinePlayableAPI,让Timeline为所欲为_ide_03

Timeline是建立在Playables API(https://docs. ​​unity3d​​.com/Manual/Playables.html)上的PlayableGraph系统,在这套系统中Timeline相当于是一个函数的作用,我们从Assset传入InputData,Timeline处理后输出OutputData到对应的Component。

这套系统中有4个关键部分,Data,Clip,Mixer,Track
我希望我的这张图可以帮助大家快速对应和理解这4个部分。

Timeline也是一个Templete模板,可以方便快速地被复用。

  • The Data

用来存放数据,需要被序列化。继承自PlayableBehaviour

using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class LightData : PlayableBehaviour
{
public float range;
public Color color;
public float intensity;
[HideInInspector]
public Transform lookTarget;

public override void OnPlayableCreate(Playable playable)
{
var duration = playable.GetDuration();
if (Mathf.Approximately((float)duration, 0))
{
throw new UnityException("A Clip Cannot have a duration of zero");
}
}
}
  • The Clip

  也就是asset,继承自PlayableAsset。因为是asset,所以不能直接与场景中的物体关联,需要关联场景中的物体时要用ExposedReference来声明,并且通过Resolve方法赋值。

using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class LightClip : PlayableAsset
{
public LightData templete = new LightData();
public ExposedReference<Transform> lookTarget;
// Factory method that generates a playable based on this asset
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
{
var playable = ScriptPlayable<LightData>.Create(graph, templete);
LightData clone = playable.GetBehaviour();
clone.lookTarget = lookTarget.Resolve(graph.GetResolver());
return playable;
}
}
  • The Mixer

  Mixer控制当前Track中所有的clip的行为,根据每个clip不同的输入权重,计算需要的结果。 Mixer在Timeline中算是最重要的部分了。


using UnityEngine;
using UnityEngine.Playables;

// A behaviour that is attached to a playable
public class LightMixer : PlayableBehaviour
{
private float m_defaultRange = 1;
private float m_defaultIntensity = 1;
private Color m_defaultColor = Color.white;
private Quaternion m_defaultRotation = Quaternion.identity;
private Light m_trackBinding;

private bool m_isFirstFrameProcess = true;

// Called when the owning graph starts playing
public override void OnGraphStart(Playable playable)
{
}

// Called when the owning graph stops playing
public override void OnGraphStop(Playable playable)
{
m_isFirstFrameProcess = false;
if (m_trackBinding == null)
return;
m_trackBinding.range = m_defaultRange;
m_trackBinding.color = m_defaultColor;
m_trackBinding.intensity = m_defaultIntensity;
m_trackBinding.transform.rotation = m_defaultRotation;
}

// Called when the state of the playable is set to Play
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
}

// Called when the state of the playable is set to Paused
public override void OnBehaviourPause(Playable playable, FrameData info)
{

}

// Called each frame while the state is set to Play
public override void PrepareFrame(Playable playable, FrameData info)
{
}

public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
m_trackBinding = playerData as Light;
if (m_trackBinding == null)
return;

if (!m_isFirstFrameProcess)
{
m_isFirstFrameProcess = true;
m_defaultRange = m_trackBinding.range;
m_defaultColor = m_trackBinding.color;
m_defaultIntensity = m_trackBinding.intensity;
m_defaultRotation = m_trackBinding.transform.rotation;
}

int inputCount = playable.GetInputCount();
float blendRange = 0;
Color blendColor = Color.clear;
float blendIntensity = 0;
Quaternion blendRotation = Quaternion.identity;

float totalWeight = 0;
float greatestWeight = 0;
int currentInputs = 0;

for (int i = 0; i < inputCount; i++)
{
ScriptPlayable<LightData> playableInput = (ScriptPlayable<LightData>)playable.GetInput(i);
LightData input = playableInput.GetBehaviour();

float inputWeight = playable.GetInputWeight(i);
blendRange += input.range * inputWeight;
blendColor += input.color * inputWeight;
blendIntensity += input.intensity * inputWeight;
if (input.lookTarget != null && inputWeight > 0)
blendRotation *= Quaternion.LookRotation((input.lookTarget.position - m_trackBinding.transform.position) * inputWeight);

totalWeight += inputWeight;

if (inputWeight > greatestWeight)
greatestWeight = inputWeight;

if (!Mathf.Approximately(inputWeight, 0f))
currentInputs++;
}

m_trackBinding.range = blendRange + m_defaultRange * (1 - totalWeight);
m_trackBinding.color = blendColor + m_defaultColor * (1 - totalWeight);
m_trackBinding.intensity = blendIntensity + m_defaultIntensity * (1 - totalWeight);
m_trackBinding.transform.rotation = blendRotation;
}
}
  • The Track

  把Mixer计算的结果输出,可以指定输出到绑定的物体上。继承自TrackAsset。有好几个特性可以绑定,


  • TrackColor : 定义在编辑器中Track的标识颜色
  • BindingObejctType : 当前Track上的Mixer输出后的数据要传递的对象类型
  • TrackClipType : 当前Track上的Clip的类型


using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

[TrackColor(1f, 0f, 0f)]
[TrackClipType(typeof(LightClip))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
return ScriptPlayable<LightMixer>.Create(graph, inputCount);
}
}

实践

通过上面的探究,我们要做老年disco效果,那就来定制一个自己的track,专门用来控制Light,我们叫LightControl

  • 创建一个新的场景添加两个角色,一红一蓝
  • 新建一个ParticleSytem,烘托一下Disco的气氛
  • 创建一个新的Timeline,DiscoTimeline
  • 在DiscoTimeline上新建2个AnimationTrack,一个控制蓝角色的动画,一个控制红角色的动画
  • 在DiscoTimeline新建一个ControlTrack,控制刚才创建的ParticleSystem
  • 在场景中新建一个SpotLight和一个PointLight
  • 在DiscoTimeline中新建一个TrackGroup,命名为PlayableLight
  • 在DiscoTimeline中新建我们的LightControl Track,将SpotLight与这个Track绑定,命名为SpotLight
  • 在DiscoTimeline中新建我们的LightControl Track,将PointLight与这个Track绑定,命名为PointLight
  • 将我们自定义的Clip添加2段到SpotLightTrack上,在一个Clip上的LookAtTarget上绑定蓝角色,另一个Clip上的LookAtTarget绑定到红角色
  • 将我们自定义的Clip添加2段到PointLightTrack上,设置变换的灯光颜色和强度。 最后的Timeline就是这样

探索TimelinePlayableAPI,让Timeline为所欲为_sed_04