[Unity] Playables學(xué)習(xí)整理 - 1

環(huán)境

  • Unity 2020、Unity 2021

Playables的定義

Playables包含三個(gè)部分:Playable、PlayableOutput、PlayableGraph

圖1 PlayableGraph
圖2 PlayableOutput
圖3 Playable

【Playable】代表各種可播放物的數(shù)據(jù)與行為。參考上圖,除了Output外的所有節(jié)點(diǎn)都是Playable。
【PlayableOutput】它將輸入Playable處理后的的狀態(tài)信息,反應(yīng)到場(chǎng)景中的Unity對(duì)象上,從而使播放效果呈現(xiàn)出來(lái)。
【PlayableGraph】它是Playables這套系統(tǒng)的容器與管理器。它負(fù)責(zé)創(chuàng)建、銷(xiāo)毀各種Playable、PlayableOutput的節(jié)點(diǎn),也管理節(jié)點(diǎn)間的關(guān)系。


從類(lèi)的角度看Playables(邏輯/行為部分)

圖4 Playables類(lèi)圖

??PlayableGraph中的節(jié)點(diǎn)只能是PlayableOutputPlayable類(lèi)型(它們的子類(lèi)型也可以,都?xì)w屬到這兩種類(lèi)型里)。PlayableOutput與Playable的結(jié)構(gòu)、實(shí)現(xiàn)方式都是相似的,只是用途不同,并且在目前的項(xiàng)目使用中基本用不到自定義的PlayableOutput,因此 后面只針對(duì)Playable做一些說(shuō)明。
??Playable是所有可播放物(見(jiàn)圖3)的基類(lèi)型(不是基類(lèi)),因此它們可以隱式轉(zhuǎn)換為Playable(通過(guò)PlayableHandle實(shí)現(xiàn))。Playable只能由Unity定義好后給我使用,我們無(wú)法完全自定義一個(gè)Playable結(jié)構(gòu)出來(lái),因?yàn)橐玫紺#側(cè)的internal接口,并在native層實(shí)現(xiàn)具體的邏輯。
??Unity為我們提供了拓展Playable的能力,那就是ScriptPlayable<T>:IPlayablePlayableBehaviour:IPlayableBehaviour。
??ScriptPlayable<T>是Unity定義好的一個(gè)Playable子類(lèi)型,它只是一個(gè)空殼,所有的邏輯都由PlayableBehaviour來(lái)實(shí)現(xiàn),而IPlayableBehaviour是我們可以自己實(shí)現(xiàn)拓展的。從設(shè)計(jì)模式角度來(lái)看,ScriptPlayable<T>就是一個(gè)適配器(Adapter),它將IPlayableBehaviour轉(zhuǎn)換為IPlayable給PlayableGraph使用。
(反過(guò)來(lái)說(shuō),我們要拓展Playable,只能實(shí)現(xiàn)IPlayableBehaviour或者繼承PlayableBehaviour,來(lái)定義自己的Behaviour類(lèi)。并用ScriptPlayable<T>將Behaviour進(jìn)行包裝后,傳遞給PlayableGraph使用。)
??PlayableHandle這個(gè)結(jié)構(gòu)在其他文章中幾乎沒(méi)有被提到過(guò),但是它很重要。Handle是Playable是這套系統(tǒng)訪問(wèn)native層的接口,它持有相關(guān)對(duì)象的指針。PlayableHandle是訪問(wèn)所有Playable的Handle,無(wú)論是基類(lèi)型Playable還是子類(lèi)型AnimationClipPlayable、AnimationMixerPlayable,都借助PlayableHandle訪問(wèn)native里的接口。


分析Unity的設(shè)計(jì)點(diǎn)

下面列舉幾個(gè),自己猜測(cè)出來(lái)的,Unity團(tuán)隊(duì)在Playables這套系統(tǒng)中的設(shè)計(jì)點(diǎn):

1、Playable與PlayableOutput
??它們都是空殼子,真正的接口在其對(duì)應(yīng)的Handle里(并且功能的實(shí)現(xiàn)是在native層)。相關(guān)的Extensions類(lèi)也只是把Handle的接口開(kāi)放出來(lái)。定義這些空殼,是為了在C#層能夠使用native提供的功能。

2、IPlayable與IPlayableOutput
??這兩個(gè)interface的存在是為了在C#中提供接口約束能力,并不承擔(dān)實(shí)際功能或代碼設(shè)計(jì)的目的。因?yàn)镻layables相關(guān)的基礎(chǔ)接口都采用struct實(shí)現(xiàn),而struct是不能繼承的。Unity底層通過(guò)Handle結(jié)構(gòu)模擬了一種struct繼承的實(shí)現(xiàn)方式(struct相互轉(zhuǎn)換),并通過(guò)定義interface對(duì)一些接口的使用進(jìn)行強(qiáng)制約束。

3、如何實(shí)現(xiàn)Playable與其子類(lèi)型的相互轉(zhuǎn)換
??如前所述,所有的Playable結(jié)構(gòu)(struct)都是空殼,只提供了訪問(wèn)native的接口,它們的實(shí)質(zhì)都在于PlayableHandle。只要PlayableHandle保存的指針是同一個(gè),創(chuàng)建新的基類(lèi)型或者子類(lèi)型的實(shí)例,即可完成相互轉(zhuǎn)換。
??為了更符合面向?qū)ο蟮挠梅?,Unity在每個(gè)子類(lèi)型中都通過(guò)聲明轉(zhuǎn)換操作符(implicit、explicit)的方式,實(shí)現(xiàn)Playable與子類(lèi)型的相互轉(zhuǎn)換。例:

public static implicit operator Playable(AnimationMixerPlayable playable);
public static explicit operator AnimationMixerPlayable(Playable playable);

4、IPlayableBehaviour存在的意義
??這里其實(shí)我沒(méi)搞很明白,甚至有些迷惑??!
??PlayableBehaviour是實(shí)現(xiàn)(implement)了IPlayableBehaviour的C#類(lèi),注意這里是類(lèi)(class)了,不是結(jié)構(gòu)體(struct)。從這個(gè)小點(diǎn)也可以看出,它本身不是Playable,而是用于拓展Playable的另一套東西。
??PlayableBehaviour是一個(gè)抽象類(lèi)(abstract),它為所有IPlayableBehaviour的接口都只提供了空實(shí)現(xiàn),即:空的虛方法(virtual)。這里我能理解到的用途是 簡(jiǎn)化拓展的代碼量,因?yàn)镮PlayableBehaviour提供的是一組生命周期的接口(類(lèi)似Awake、Start、Update),我們只需要實(shí)現(xiàn)自己需要的幾個(gè)即可,沒(méi)必要把所有接口都寫(xiě)出來(lái)。PlayableBehaviour的存在,就為我們提供了,可以按需實(shí)現(xiàn)的便利。
??Unity官方Scripting API文檔里介紹PlayableBehaviour,說(shuō)它是所有自定義Playable腳本的基類(lèi)。言下之意,我們拓展Playable時(shí),應(yīng)該繼承PlayableBehaviour,而不是實(shí)現(xiàn)IPlayableBehaviour。但是有兩個(gè)點(diǎn)湊在一起,使得這個(gè)設(shè)定很奇怪:

  • 在C#中(反編譯)能看到的Playable相關(guān)模塊里,沒(méi)有直接使用PlayableBehaviour的地方。PlayableHandleScriptPlayable<T>使用的都是IPlayableBehaviour。
  • PlayableBehaviour中有個(gè)方法,在IPlayableBehaviour中并沒(méi)有定義:public virtual void PrepareData(Playable playable, FrameData info)。文檔中說(shuō)當(dāng)Playable延遲時(shí),會(huì)調(diào)用PrepareData,但什么情況下算延遲不知道;在PrepareData中應(yīng)該做些什么事情也不知道。

令人困惑的也是兩點(diǎn):

  • 既然設(shè)定PlayableBehaviour為所以自定義的基類(lèi),那么為啥還要定義IPlayableBehaviour?
    這里不需要模擬繼承;IPlayableBehaviour里的接口又不全。
  • 所有Playable接口,接收的都是IPlayableBehaviour類(lèi)型,那么PrepareData是如何被調(diào)用的?

參考文章

Playable API:定制你的動(dòng)畫(huà)系統(tǒng)
The PlayableGraph
Playable

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容