骨骼動畫實現祕密!閒魚 Flutter 互動引擎告訴你


骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

作者|馬驍(塵蕭)

出品|阿里巴巴新零售淘系技術部


前言


代表骨骼動畫是一種通過控制骨骼參數來實現多幀動畫的方式,區別於 GIF 的不連貫和序列幀的體積大,骨骼動畫有較好的靈活性和流暢性。目前骨骼動畫已經被大規模地在遊戲和動畫中所使用,大有一種取代幀動畫的趨勢,Candy 互動引擎對骨骼動畫的支持自然是必不可少的一環。


從工具入手


動畫是互動中很重要的一環,通過恰到好處的動畫形式往往可以給用戶更加新鮮的體驗。由於動畫製作是一項需要和 UED 高度合作的工作,面對 UED 無盡的參數變更,選擇一個好用的工具就變得至關重要,畢竟誰也不希望自己在開開心心的敲代碼的時候 UED 過來找你調動畫參數。參考行業目前的工具鏈體系,並從中選擇出一套適合我們的工具是比較好的選擇。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

先看曾被 Flutter 官方推薦過的骨骼動畫製作工具 Flare,其優勢在於對於 Flutter 的支持較為完善。但是問題在於這套工具對於設計師來說比較陌生,而且.flr格式是一個全新的格式不夠通用,所以最終我們放棄了這個方案。

為了配合集團現有的互動工具鏈,我們把目光放到了前端領域,白鷺引擎(Egret)是一個在前端領域小有名氣的遊戲引擎,其配套的工具體系包括DragonBone(骨骼動畫)、Feather(粒子動畫)等。

這套工具在互動領域已經被使用了多年,對於設計師來說比較好上手。使用這套體系最大的問題是在 Flutter 上沒有相應的實現,但是其產物的文檔非常完善,所以我們最終選擇在 Flutter 上解析並實現相應的 Runtime。


基礎知識


要進行骨骼動畫的製作必然要先對骨骼動畫本身要有一個基礎瞭解,骨骼動畫的詳細介紹可以參考 DragonBone 官方教程,對於骨骼的幾個關鍵概念我們還是必須先進行了解。

  • 骨架(Armature):骨架是骨骼的集合,骨架中至少包含一個骨骼。
  • 骨骼(Bone):骨骼是骨骼動畫的基本組成部分,骨骼之間存在父子關係,父親的變換會影響到孩子。一般通過骨骼的旋轉、縮放、平移等變換即可形成動畫。
  • 插槽(Slot):插槽是圖片的容器,是骨骼和圖片的橋樑。一根骨骼可以掛載多個插槽,可以視作骨骼是插槽的父節點,骨骼的變換會影響插槽。
  • 顯示對象(DisplayData):顯示對象通常為圖片。一個插槽中可以有多個顯示對象,但同時只會有一個被顯示,通過修改當前顯示的對象可以形成幀動畫。

顧名思義”骨骼“就是骨骼動畫的核心部件,正是因為這種模仿生物的骨骼的設計,使得設計師可以通過調整骨骼的參數,讓角色做出豐富且自然的動作。

我們要進行骨骼動畫的渲染肯定不能脫離 Candy 遊戲系統去完成,那麼隨之我們的第一個問題就誕生了,骨骼動畫的核心部件“骨骼”在 Candy 中到底應該扮演一個什麼樣的角色呢?


骨架渲染


問題1:每一根骨骼在 Candy 中的角色是什麼?

上一篇文章中也有提到 Candy 遊戲系統是由四大元素構成的:

  • Game:遊戲類,負責整個遊戲的管理,Scene的加載管理以及各子系統管理與調度。
  • Scene:遊戲場景類,負責遊戲場景中各遊戲對象的管理。
  • GameObject:遊戲對象類,遊戲世界中游戲對象的最小單位,遊戲世界中的任何物體都是GameObject。
  • Component:遊戲組件類,表示遊戲對象的能力屬性,比如SpriteComponent表示精靈組件,表示繪製精靈的能力。

由於骨骼是包含了父子關係的樹形結構,而 GameObject 也是一個樹形結構,我們很自然地會想到每一根骨骼就是一個 GameObject 每一個插槽就是對應的 Component 。

因為在繪製時,後繪製的對象一定是覆蓋在最上層的,所以以樹狀結構進行繪製最大的問題就是——父子間的繪製的順序是一定的。如下圖的繪製順序是身體 -> 衣服 -> 披風,或者是衣服 -> 披風 -> 身體,無論是哪一種顯然都是錯誤。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

我們的解決方法是將這顆樹進行拍平為列表,我們把每一個插槽(Slot)都作為了一個 GameObject ,並根據 Zorder 進行排序,那麼我們最終會得到一個排好序的插槽列表,在渲染的時候根據插槽列表依次進行渲染即可。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

這樣的做法會帶來一個新的問題,插槽的位置信息數據都是相對數據,在使用樹狀的結構進行渲染的時候並不是問題,但是現在拍平之後,渲染的位置該如何確定呢?

問題2:骨骼中的位置信息和最終渲染的位置信息如何對應?

因為骨骼中的參數都是相對值,這樣做的好處在於在改變父骨骼位置時,子骨骼天然就會受到父骨骼的影響變換位置。

所以其實這個問題就是如何把相對值變為絕對值,我們可以通過一些數學計算來完成這件事,具體的原理就不在此展開講解。

在 Flutter 中,通過自定義了一個 Transform 類並封裝了相應的變換函數來即可實現座標的轉換,這樣做的好處在於可以重載相應的運算符以便做動畫的時候進行使用。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

解決了上述兩個問題,我們其實就已經知道了該如何渲染一個骨架。下面這張 Candy 實現骨骼動畫的架構圖,其中分為三個部分。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

Parser層:考慮到骨骼動畫的編輯器有很多,為了兼容市面上不同的編輯器,我們增加了一層解析層將不同編輯器生成的產物,轉化為我們預定好的相對通用的骨骼結構數據。

Data層:Data層是一個相對通用的骨架數據,其內部包括了骨骼數據、插槽數據、展示對象數據、動畫數據等,通過骨架數據我們可以知道最終應該渲染什麼內容。由於我們第一個兼容的編輯器是Dragonbone,所以這些數據中屬性的定義大多參照了Dragonbone中的定義,這裡就不將每一個屬性都展開來說了。

Render層:一個骨架就是一個獨立的GameObject,骨架中的每一個插槽都會對應一個子GameObject。骨架中的骨骼起到的是輔助計算渲染座標的作用,我們通過插槽所屬的骨骼計算出渲染時要用的絕對座標並填到相應的TransformComponent中。最後,顯示對象中的圖片使用SpriteComponent進行渲染到正確的位置上。


動畫實現


骨骼動畫其實是由每一根骨骼的多個屬性動畫複合而成的,簡單骨骼動畫針對每一根骨骼及插槽其實可以拆分為以下幾個動畫:

  • 骨骼(插槽)的位移動畫
  • 骨骼(插槽)的旋轉動畫
  • 骨骼(插槽)的縮放動畫
  • 插槽的透明度動畫

這些簡單動畫都可以歸納為補間動畫,我們只需要在遊戲每一次Update的時候將對應的屬性值改變,自然就形成了動畫的效果。

那麼每一個時刻的值應該是多少呢?這就需要一個插值器來告訴我們,Flutter的Animation對於插值器提供了很好的支持,回憶一下使用Animation的時候,是不是通過每一次觸發刷新了之後從Animation中取出value值來賦值到相應的地方,同理使用在這也是一樣的。

因為骨骼動畫會有很多的關鍵幀,所以這裡使用了Flutter中的一種特殊的Animation——TweenSequence。TweenSequence 可以傳入一個 List<tweensequenceitem>>items 每一個TweenSequenceItem都可以設置一個補間動畫和相應的權重。/<tweensequenceitem>

在保證每一個骨骼的動畫總幀數相同的情況下,可以直接使用每兩個關鍵幀之間包含的幀數作為權重,相應的前一關鍵幀幀的值則為起始值,後一幀關鍵幀的作為終止值。舉個例子:


<code>///Transform2 為自己定義的一個數據結構,只要重載了相應的運算符,一樣可以被Animation所使用TweenSequenceItem<transform2> _parseTransformAnimation(TransformFrame cur,TransformFramenext, {int duration,}) {if(cur != null&& next!= null&& cur.duration != null) {finalAnimatable<transform2> tween = Tween<transform2>(begin: cur.transForm,end: next.transForm,);if((cur.duration != null&& cur.duration > 0) ||(duration != null&& duration > 0)) {returnTweenSequenceItem<transform2>(        tween: tween,        weight:        duration == null? cur.duration?.toDouble() : duration.toDouble(),);}}returnnull;}/<transform2>/<transform2>/<transform2>/<transform2>/<code>

動畫效果


這裡以閒魚幣中的撈魚小人為例子(可以通過 “閒魚首頁 -> 右上角簽到圖標” 進入閒魚幣池塘進行體驗)。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你


性能表現


我們使用乾淨的 Demo 工程渲染多個上圖中的小人進行測試,測試機型:iPhoneXs。

骨骼動畫實現秘密!閒魚 Flutter 互動引擎告訴你

骨骼數量能一定程度上也能衡量動畫的複雜度(還與變換次數相關),可以發現大於1000根骨骼時性能開始出現衰減,並隨著骨骼數量的增加逐漸明顯,在3000根骨骼以上時出現明顯卡頓。這一性能已經完全可以滿足App中內嵌中小型遊戲的需求(其中內存增加問題會在後續的性能篇中進行闡述)。


現狀和展望


目前Candy已經實現了對基礎骨骼動畫、粒子動畫、屬性動畫的支持,並且已經在閒魚幣業務中落地使用,後續會應用在更多的場景之中。隨著場景的增加,我們面臨的挑戰也就越來越多。

▐ 動畫賦能 app

一個 App 必定不會有很多的遊戲內容,我們實現的動畫如果僅在遊戲場景下使用那其實落地的場景就會很有限,所以我們將 Candy 引擎中的動畫部分封裝為了Widget,使得Flutter App可以天然無縫地使用動畫。

▐ 更多動畫能力的支持

Lottie 是一種深受設計師以及開發同學喜愛的方式,對於 Lottie 的支持我們也已經開始進行開發,等待完成後再與大家分享。

▐ 從動畫升級為互動

互動是由一個個動畫組合而成的,與傳統的動畫最大的差距在於互動需要有可交互性,在用戶發生不同交互時要進行不同動畫的切換,這也就意味著我們需要有編排各個動畫之間的關係的能力。

這是一套很完整的體系,需要有相應的邏輯編排工具以及端側對於動態邏輯編排的實現,我們目前正在與集團中前端互動小組的同學合作複用前端現有的工具鏈,但是前端比起 Flutter 在動態邏輯方面有天生的優勢,所以我們希望結合閒魚團隊的 Fass 以及 Flutter-dx 去構建這套體系。

在完成之後也會有相應的文章與大家分享我們的做法和心路歷程,同時也歡迎大家和我們一起進行探討,碰撞出更多的火花。


We are hiring


淘系技術部依託淘系豐富的業務形態和海量的用戶,我們持續以技術驅動產品和商業創新,不斷探索和衍生顛覆型互聯網新技術,以更加智能、友好、普惠的科技深度重塑產業和用戶體驗,打造新商業。我們不斷吸引用戶增長、機器學習、視覺算法、音視頻通信、數字媒體、移動技術、端側智能等領域全球頂尖專業人才加入,讓科技引領面向未來的商業創新和進步。

請投遞簡歷至郵箱:[email protected]


分享到:


相關文章: