|
进阶教程
Playable API是Unity官方提供的一种新的创建工具、动画系统或其他游戏机制的方式。Playable可以通过一组API来创建一个Graph,而每个Graph可以由多个树形结构组成,每个树状结构都由一个Output节点作为根节点,叶子结点则由各种Playable组成。
详细的介绍大家可以参考官方文档。
下面直接介绍怎么利用Playable API来定制自己的动画系统,其中参考了Unity官方案例SimpleAnimation。GitHub工程地址:https://github.com/Unity-Technologies/SimpleAnimation
播放一段简单的动画
首先自定义一个CustomAnimationcontroller组件,然后需要创建一个PlayableGraph对象以及一个AnimationPlayableOutput节点。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
[RequireComponent(typeof(Animator))]
public class CustomAnimationController : MonoBehaviour
{
private PlayableGraph m_Graph;
private AnimationPlayableOutput m_Output;
void Start()
{
}
void Update()
{
}
private void OnDestroy()
{
}
}要播放一段动画,我们还必须要创建一个AnimationClip引用。然后,我们就可以构建一个动画树了。首先调用PlayableGraph.Create()方法构建一个PlayableGraph实例,并设置实例的时间模式为DirectorUpdateMode.GameTime。这样,PlayableGraph的时间更新是基于Time.time的,这样可以保证PlayableGraph的时间与游戏的时间保持同步。然后调用AnimationPlayableOutput.Create()函数创建一个Output节点,这里需要传递三个参数,分别是PlayableGraph实例,动画树的名字以及游戏对象绑定的Animator组件。
Playable API的两大组成部分是PlayableOutput和Playable,我们的动画对象也需要包装到一个Playable对象中,通过调用AnimationClipPlayable.Create()方法可以返回我们需要的Playable对象,传递的参数是Playable实例以及动画片段。
然后将动画片段的Playable实例作为输出结果传递给output节点,我们的动画树就创建好了。最后调用PlayableGraph实例的Play()方法即可让动画树开始播放。
public AnimationClip clip;
...
void Start()
{
m_Graph = PlayableGraph.Create();
m_Graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
m_Output = AnimationPlayableOutput.Create(m_Graph, &#34;Animation&#34;, GetComponent<Animator>());
AnimationClipPlayable clipPlayable = AnimationClipPlayable.Create(m_Graph, clip);
m_Output.SetSourcePlayable(clipPlayable);
m_Graph.Play();
}另外,游戏对象销毁时,我们还要记得销毁PlayableGraph实例,即m_Graph。所以,我们需要在OnDestroy中调用m_Graph的Destroy方法。
PlayableGraph可视化
为了让大家对PlayableGraph可以有个更加直观的认识,这里介绍一个Unity的PlayableGraph Visualizer插件。该工具由于尚未成熟,所以在Unity的PackageManager界面的默认选项中并没有提供下载途径。
正确的下载方式是:
- 点击菜单选项:Window > Package Manager;
- 点击Advanted按钮,在下拉列表中选择show preview packages,然后在搜索栏中输入playable;
- 找到PlayableGraph Visualizer选项然后点击右下角的install按钮;
安装完毕,点击window > analysis > playablegraph即可打开这个插件的显示界面。
如何让PlayableGraph实例显示在这个插件界面上,需要调用GraphVisualizerClient.Show()这个接口,需要传递一个PlayableGraph实例作为输入参数。为了让动画树显示更加直观,这里稍微修改以下代码:
void Start()
{
m_Graph = PlayableGraph.Create(&#34;CustomAnimationController&#34;);
GraphVisualizerClient.Show(m_Graph);
...
}点击unity的运行按钮,然后打开我们的插件,这时候可以看到下面的结果,左上角的名字即是我们的playable graph实例的名字。
创建一个动画混合树
我们知道,unity的动画状态机具有transition的功能,两个state之间通过拉一条线段来定义一个transition。Playable API没有transition的概念,但是提供给来应用层基本的Blend功能,而transition的本质是两个动画的Blend。因此,我们可以应用Playable API来实现自己的transition。
创建混合树我们需要用到AnimationMixerPlayable,以及至少两个动画。因此,我们新加一个AnimaitonMixerPlayable对象和一个AnimationClip引用。
public AnimationClip clip1;
private PlayableGraph m_Graph;
private AnimationPlayableOutput m_Output;
private AnimationMixerPlayable m_Mixer;将两个动画进行混合,其实是由两个动画的input weight来控制的。我们在Start函数中创建AnimationMixerPlayable的实例,创建方法是调用AnimationMixerPlayable的Create函数,需要传递两个参数,分别是PlayableGraph实例以及AnimationMixerPlayable节点的输入节点数量。AnimationMixerPlayable实例创建好之后,我们将它作为输出结果传输给output节点。接下来利用m_Mixer连接两个动画片段,构建完整的动画混合树。
...
m_Mixer = AnimationMixerPlayable.Create(m_Graph, 2);
m_Output.SetSourcePlayable(m_Mixer);
AnimationClipPlayable clipPlayable0 = AnimationClipPlayable.Create(m_Graph, clip0);
AnimationClipPlayable clipPlayable1 = AnimationClipPlayable.Create(m_Graph, clip1);
m_Graph.Connect(clipPlayable0, 0, m_Mixer, 0);
m_Graph.Connect(clipPlayable1, 0, m_Mixer, 1);
m_Mixer.SetInputWeight(0, 1);
m_Mixer.SetInputWeight(1, 0);
...实现两个动画的线性过渡,首先定义一个字段tranTime控制动画的过渡时间,deltaTime表示剩余的过渡时间,然后在Update函数中根据时间修改两个动画的input weight。
public float tranTime = 2;
private float leftTime;
...
void Start()
{
leftTime = tranTime;
...
}
void Update()
{
if (leftTime > 0)
{
leftTime = Mathf.Clamp(leftTime - Time.deltaTime, 0, 1);
float weight = leftTime / tranTime;
m_Mixer.SetInputWeight(0, weight);
m_Mixer.SetInputWeight(1, 1 - weight);
}
}
创建PlayableBehaviour
PlayableBehaviour是一个供用户自定义行为的类。我们可以对Playable进行直接的访问和控制。同时它也定义了一些回调函数来捕捉一些事件。例如:开始播放时的事件、销毁事件,我们想监控这些事件时就离不开PlayableBehaviour这个类。
更重要的是,它提供了一些在每一帧的动画计算流程上的回调。例如:PrepareFrame函数会在Prepare frame这个阶段回调。我们就可以在每一帧对Playable中的元素进行访问和设置,例如:Time和Weight。可以说这是在做自定义Blend中不可缺失的功能。
接下来,我们将实现在自定义的PlayableBehaviour类中控制所有在PlayableGraph中的节点。新建一个CustomAnimationControllerPlayable类,并继承自PlayableBehabiour。
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
public class CustomAnimationControllerPlayable : PlayableBehaviour
{
override public void PrepareFrame(Playable owner, FrameData info)
{
}
}首先我们重新组织一下代码的功能,我们将把构建动画树的工作移植到CustomAnimationControllerPlayable中来。为此,我们新建一个Initialize函数,输入参数有构建动画树的动画数组,CustomAnimationControllerPlayable从属的Playable以及PlayableGraph实例。新建三个私有属性,分别表示当前播放的动画的指引,距离下个动画剩余的时间以及AnimationMixerPlayable对象,该对象将作为从属Playable的输入节点。
private int m_CurrentClipIndex = -1;
private float m_TimeToNextClip;
private Playable mixer;
public void Initialize(AnimationClip[] clipsToPlay, Playable owner, PlayableGraph graph)
{
owner.SetInputCount(1);
mixer = AnimationMixerPlayable.Create(graph, clipsToPlay.Length);
graph.Connect(mixer, 0, owner, 0);
owner.SetInputWeight(0, 1);
for (int clipIndex = 0 ; clipIndex < mixer.GetInputCount() ; ++clipIndex)
{
graph.Connect(AnimationClipPlayable.Create(graph, clipsToPlay[clipIndex]), 0, mixer, clipIndex);
mixer.SetInputWeight(clipIndex, 1.0f);
}
}
...重写PrepareFrame函数,完成的工作是顺序播放所有的动画,在播放完一个动画之后自动播放在一个动画,当播放完所有动画之后又回重复从第一个动画开始播放。
override public void PrepareFrame(Playable owner, FrameData info)
{
if (mixer.GetInputCount() == 0)
return;
// Advance to next clip if necessary
m_TimeToNextClip -= (float)info.deltaTime;
if (m_TimeToNextClip <= 0.0f)
{
m_CurrentClipIndex++;
if (m_CurrentClipIndex >= mixer.GetInputCount())
m_CurrentClipIndex = 0;
var currentClip = (AnimationClipPlayable)mixer.GetInput(m_CurrentClipIndex);
// Reset the time so that the next clip starts at the correct position
currentClip.SetTime(0);
m_TimeToNextClip = currentClip.GetAnimationClip().length;
}
// Adjust the weight of the inputs
for (int clipIndex = 0 ; clipIndex < mixer.GetInputCount(); ++clipIndex)
{
if (clipIndex == m_CurrentClipIndex)
mixer.SetInputWeight(clipIndex, 1.0f);
else
mixer.SetInputWeight(clipIndex, 0.0f);
}
}相应修改一下CustomAnimationControllor脚本,代码如下:
public AnimationClip[] clipsToPlay;
PlayableGraph m_Graph;
void Start()
{
m_Graph = PlayableGraph.Create();
var custPlayable = ScriptPlayable<CustomAnimationControllerPlayable>.Create(m_Graph);
var playQueue = custPlayable.GetBehaviour();
playQueue.Initialize(clipsToPlay, custPlayable, m_Graph);
var playableOutput = AnimationPlayableOutput.Create(m_Graph, &#34;Animation&#34;, GetComponent<Animator>());
playableOutput.SetSourcePlayable(custPlayable);
playableOutput.SetSourceInputPort(0);
m_Graph.Play();
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
m_Graph.Destroy();
} |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|