前言
有關Playable的介紹,官方有篇中文的文章,大家可以優先看一下,這裏就不過多描述了。
文章鏈接:https://connect.unity.com/p/playable-api-ding-zhi-ni-de-dong-hua-xi-tong
官方Demo:https://github.com/Unity-Technologies/SimpleAnimation
Playables API:https://docs.unity3d.com/Manual/Playables.html
準備
由於是動畫系統嘛,所以肯定要有動畫。我們先在場景中創建一個Cube,然後選中Cube在Animation面板中爲其創建幾個簡單動畫即可(當然了,有現成資源的小夥伴可以跳過這步了),Demo中我創建了三個名爲Jump,Rotate,Scale的Animation文件。同時由於剛剛Create Animation的操作,Unity會在我們的Cube上自動添加Animator組件,並且關聯了一個Animator的Controller文件。Animator組件需要保留(驅動Playable Graph的實際上依然是Animator組件),但是Controller我們暫時用不到,先刪除它。
同時Unity提供了一個查看Playable結構的工具:PlayableGraph Visualizer,我們打開Package Manager,在Advanced中選中Show Preview Packages,然後找到PlayableGraph Visualizer,下載它。下載好後可以在Window-Analysis-PlayaleGraph Visualizer打開它。
AnimationPlayable
接下來自然是要利用Playable使我們的Cube播放動畫了,我們先創建一個腳本組件(PlayableTest)掛載在Cube上。
我們先創建幾個AnimationClip變量用於關聯我們的Animation文件
public AnimationClip jumpAnimationClip;
public AnimationClip rotateAnimationClip;
public AnimationClip scaleAnimationClip;
AnimationPlayableOutput與AnimationClipPlayable
接下里我們來看看一個最簡單的AnimationPlayable的實現
PlayableGraph m_graph;
void Start()
{
m_graph = PlayableGraph.Create("TestPlayableGraph");
var animationOutputPlayable = AnimationPlayableOutput.Create(m_graph, "AnimationOutput", GetComponent<Animator>());
var jumpAnimationClipPlayable = AnimationClipPlayable.Create(m_graph, jumpAnimationClip);
//AnimationPlayableOutput只有一個輸入口,所以port爲0
animationOutputPlayable.SetSourcePlayable(jumpAnimationClipPlayable, 0);
m_graph.Play();
}
void OnDisable()
{
// 銷燬graph中所有的Playables和PlayableOutputs
m_graph.Destroy();
}
PlayableGraph類似於一個Playable的容器,我們往裏面添加了一個用做動畫輸出的AnimationPlayableOutput和一個關聯動畫的AnimationClipPlayable,並用SetSourcePlayable將其關聯起來(一個PlayableOutput只能有一個SourcePlayable)。
運行後,就可以看見我們的Cube播放了我們設置的Jump動畫,同時查看PlayaleGraph Visualizer來更直觀的瞭解,如圖:
通過該視圖我們可以查看每個節點的相關信息,例如播放狀態,速度,時間等。
如果我們要手動控制動畫的播放或暫停,可以使用Playable的Play和Pause方法,如:
jumpAnimationClipPlayable.Play();
jumpAnimationClipPlayable.Pause();
AnimationPlayableUtilities
此外Unity還提供了一個工具類:AnimationPlayableUtilities,例如上面例子中Start裏面好幾行的代碼,我們可以使用它來只用一行代碼實現
void Start()
{
var jumpAnimationClipPlayable = AnimationPlayableUtilities.PlayClip(GetComponent<Animator>(), jumpAnimationClip, out m_graph);
}
AnimationMixerPlayable
如果想要多個動畫同時播放,我們也可以用AnimationMixerPlayable實現Blend Tree來混合動畫。
[Range(0, 1)] public float weight;
PlayableGraph m_graph;
AnimationMixerPlayable m_mixerAnimationPlayable;
void Start()
{
m_graph = PlayableGraph.Create("TestPlayableGraph");
var animationOutputPlayable = AnimationPlayableOutput.Create(m_graph, "AnimationOutput", GetComponent<Animator>());
//inputCount=2,即有兩個輸入節點
m_mixerAnimationPlayable = AnimationMixerPlayable.Create(m_graph, 2);
animationOutputPlayable.SetSourcePlayable(m_mixerAnimationPlayable, 0);
var jumpAnimationClipPlayable = AnimationClipPlayable.Create(m_graph, jumpAnimationClip);
var rotateAnimationClipPlayable = AnimationClipPlayable.Create(m_graph, rotateAnimationClip);
//使用Connect方法連接Playable節點,如下面的jumpAnimationClipPlayable第0個輸出口連接到m_mixerAnimationPlayable的第0個輸入口
m_graph.Connect(jumpAnimationClipPlayable, 0, m_mixerAnimationPlayable, 0);
m_graph.Connect(rotateAnimationClipPlayable, 0, m_mixerAnimationPlayable, 1);
//同時可以利用Disconnect方法來斷開連接,如斷開m_mixerAnimationPlayable第0個輸入端
//m_graph.Disconnect(m_mixerAnimationPlayable, 0);
m_graph.Play();
}
void Update()
{
//設置不同輸入節點的權重
m_mixerAnimationPlayable.SetInputWeight(0, weight);
m_mixerAnimationPlayable.SetInputWeight(1, 1 - weight);
}
運行之後,我們可以通過改變weight的值,來改變兩個動畫的權重,PlayaleGraph如下(線條越白說明該節點的權重越高):
通過AnimationMixerPlayable來進行混合,並且通過Input weight來控制混合過程。爲了保證動畫的準確性,AnimationMixerPlayable的混合權重在內部會保證和爲1。
AnimationLayerMixerPlayable
我們還可以利用AnimationLayerMixerPlayable來實現類似於Animator中的Layer功能,例如角色的邊跑邊射擊的效果,而且可以運行時動態的增加、刪除Layer。使用方法與AnimationMixerPlayable類似,就不過多介紹了。
AnimatorControllerPlayable
我們在使用AnimationMixerPlayable或者AnimationLayerMixerPlayable的時候,除了混合AnimationClipPlayable,我們還可以利用AnimatorControllerPlayable來混合Animator的Controller。
首先,Playable可以和Controller疊加分層動畫。在動畫狀態機中Layer是Static的。所以利用Playable和Animator controller混合就可以起到動態添加你想要的Layer的作用。
其次,Playable可以和Controller進行混合,你可以讓它們按一定的權重進行Blend。
再者,Playable可以和Controller互相CrossFade。例如:我們有一把武器,想要讓武器來告訴角色該怎麼使用這把武器。所以我們創建一個Animator controller放在武器上,當角色拿起武器後,就可以CrossFade到武器的動畫狀態機上。這可以讓大大降低我們的動畫系統的複雜度,因爲動畫的CrossFade不在侷限於一個狀態機裏了。
最後,二個Controller可以進行混合。例如:你可以從一個狀態機Crossfade到另一個狀態機上。
在代碼實現上,我們只需要將之前的AnimationClipPlayable替換爲AnimatorControllerPlayable即可
//關聯Animator的Controller文件
public RuntimeAnimatorController animatorController;
void Start()
{
......
var animatorPlayable = AnimatorControllerPlayable.Create(m_graph, animatorController);
m_graph.Connect(animatorPlayable, 0, m_mixerAnimationPlayable, 1);
......
}
AudioPlayable
AudioPlayable可以實現聲音的播放,使用方法可以說和AnimationPlayable一模一樣,只不過需要傳入一個AudioSource組件,然後把AnimationClip替換爲AudioClip,簡單的示例如下:
public AudioClip jumpAudioClip;
void Start()
{
....
var audioOutput = AudioPlayableOutput.Create(m_graph, "AudioOutput", GetComponent<AudioSource>());
var audioMixerPlayable = AudioMixerPlayable.Create(m_graph, 1);
var jumoAudioClipPlayable = AudioClipPlayable.Create(m_graph, jumpAudioClip, true);
m_graph.Connect(jumoAudioClipPlayable, 0, audioMixerPlayable, 0);
audioMixerPlayable.SetInputWeight(0, 1);
audioOutput.SetSourcePlayable(audioMixerPlayable);
m_graph.Play();
}
ScriptPlayable
ScriptPlayableOutput暫時沒有看見過多的介紹,暫時保留。
測試了下將用戶自定義的Playable(也就是下面會講解到的PlayableBehaviour)連接到ScriptPlayableOutput,每幀額外會調用ProcessFrame方法。但是無法像連接在AnimationPlayableOutput上那樣實現動畫的播放。
PlayableBehaviour
PlayableBehaviour可以讓我們自定義Playable,可以對Playable進行直接的訪問和控制。同時它也定義了一些回調函數來捕捉一些事件。例如:開始播放時的事件、銷燬事件。
而且它還提供了一些在每一幀的動畫計算流程上的回調。例如:可以用PrepareFrame函數在每一幀對Playable中的元素進行訪問和設置。
下面就用一個例子來說明:新建一個腳本,名爲AnimationQueuePlayable,繼承PlayableBehaviour,腳本如下,原理很簡單,就是利用AnimationMixerPlayable綁定多個AnimationClipPlayable,然後在PrepareFrame中利用設置權重來設置當前播放的動畫,達到循環播放的效果。
public class AnimationQueuePlayable : PlayableBehaviour
{
int m_currentClipIndex = -1;
float m_timeToNextClip;
AnimationMixerPlayable m_mixerPlayable;
public void Initialize(AnimationClip[] clipArray, Playable owner, PlayableGraph graph)
{
owner.SetInputCount(1);
m_mixerPlayable = AnimationMixerPlayable.Create(graph, clipArray.Length);
graph.Connect(m_mixerPlayable, 0, owner, 0);
owner.SetInputWeight(0, 1);
//根據clipArray創建AnimationClipPlayable並連接
for (int clipIndex = 0 ; clipIndex < m_mixerPlayable.GetInputCount() ; ++clipIndex)
graph.Connect(AnimationClipPlayable.Create(graph, clipArray[clipIndex]), 0, m_mixerPlayable, clipIndex);
}
public override void PrepareFrame(Playable owner, FrameData info)
{
int ClipCount = m_mixerPlayable.GetInputCount();
if (ClipCount == 0)
return;
m_timeToNextClip -= info.deltaTime;
if (m_timeToNextClip <= 0.0f)
{
m_currentClipIndex++;
if (m_currentClipIndex >= ClipCount)
m_currentClipIndex = 0;
var currentClip = (AnimationClipPlayable) m_mixerPlayable.GetInput(m_currentClipIndex);
//SetTime(0),從頭開始播放動畫
currentClip.SetTime(0);
m_timeToNextClip = currentClip.GetAnimationClip().length;
}
//利用權重來設置當前播放的Clip
for (int clipIndex = 0; clipIndex < ClipCount; ++clipIndex)
m_mixerPlayable.SetInputWeight(clipIndex, clipIndex == m_currentClipIndex ? 1 : 0);
}
public override void OnGraphStart(Playable playable)
{
Debug.Log("Graph.Play()");
}
public override void OnGraphStop(Playable playable)
{
Debug.Log("Graph.Stop()");
}
public override void OnPlayableCreate(Playable playable)
{
Debug.Log("Playable.Create()");
}
public override void OnPlayableDestroy(Playable playable)
{
Debug.Log("Playable.Destroy()");
}
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
Debug.Log("Playable.Play()");
}
public override void OnBehaviourPause(Playable playable, FrameData info)
{
Debug.Log("Playable.Pause()");
}
public override void PrepareData(Playable playable, FrameData info)
{
Debug.Log("PrepareData");
}
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
//當連接在ScriptPlayableOutput的時候,會每幀調用
Debug.Log("ProcessFrame");
}
}
接着我們就可以利用ScriptPlayable<T>.Create()的方法進行創建我們自定義Playable。
void Start()
{
......
var playQueuePlayable = ScriptPlayable<AnimationQueuePlayable>.Create(m_graph);
var playQueue = playQueuePlayable.GetBehaviour();
playQueue.Initialize(new []{jumpAnimationClip, rotateAnimationClip, scaleAnimationClip}, playQueuePlayable, m_graph);
animationOutputPlayable.SetSourcePlayable(playQueuePlayable, 0);
......
}
運行效果如下:
經過測試PrepareFrame的頻率和Update是一致,而且若不通過AnimationMixerPlayable組件,而是直接將AnimationClipPlayable連接到我們AnimationQueuePlayable上,通過修改權重,無法正常的循環播放(不清楚是不是漏了什麼設置)。而且不用PlayableBehaviour把AnimationQueuePlayable的代碼全部移到外面,利用Update控制也沒啥問題。所以個人感覺PlayableBehaviour的功能更像是把代碼封裝到一個類裏,方便頻繁使用,也使代碼整潔,類似於函數的功能。
SimpleAnimationPlayable
SimpleAnimationPlayable是官方Demo提供的一個ScriptPlayable,裏面爲我們封裝好了代碼,只需要我們在GameObject上添加SimpleAnimation組件,就可以簡單便捷的實現Playable的大部分功能。
簡單使用:
還是我們之前的Cube,我們先刪除我們原先的PlayableTest組件,添加SimpleAnimation組件。然後在Animation選項上關聯上我們的AnimationClip,運行就會播放我們關聯上的動畫了。
然後我們可以寫個新的組件用來管理SimpleAnimation,例如我們要添加多個動畫,可以在SimpleAnimation組件上修改Animations,也可以自己調用SimpleAnimation的AddClip方法
SimpleAnimation.AddClip(AnimationClip, Name);
要播放動畫可以使用其Play方法
SimpleAnimation.Play(Name);
若要按順序播放多個動畫,可以使用PlayQueued方法
PlayerSimpleAnimation.Play(Name1);
PlayerSimpleAnimation.PlayQueued(Name2);
PlayerSimpleAnimation.PlayQueued(Name3);
若要混合動畫可以使用Blend方法
SimpleAnimation.Play("Scale");
//Default對應的動畫權重從0到1花費5秒時間
SimpleAnimation.Blend("Default", 1, 5);
//由於Scale的權重也是1,所以最後兩個動畫的權重分別爲0.5
若要是一個動畫淡出到另個動畫,可以使用CrossFade方法
SimpleAnimation.Play("Scale");
//花費5秒時間,從Scale動畫淡出爲Default動畫
SimpleAnimation.CrossFade("Default", 5);
總結
Playable就是利用代碼創建一個個的Playable節點,然後進行組合連接,最終輸出到PlayableOutput上。
PlayableOutput和Playable一共以下幾種: