Playable API:定製你的動畫系統 簡單使用

前言

有關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一共以下幾種:

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章