警告:此文章純屬分享Live2D Unity SDK開發&使用經驗,不涉及教學,亦不提供任何程式碼與專案細節。
零、前言
開始之前先幫不太熟悉這行的讀者科普一下,遊戲裡常見的2D動畫有兩大主流軟體:Spine、Live2D。
兩套軟體的強項各有不同,運作的邏輯也大不相同。
假設你今天要做一款手機上的橫向捲軸動作遊戲,可能有一些效能上的考量,或著說同一副骨架要反覆套用到許多角色或怪物身上,那Spine的骨架驅動、IK等就是較好的選擇,舉個例子:勝利女神 妮姬。
那Live2D本身是將角色立繪切分成好幾塊零件,再透過變形網格(Mesh)和參數驅動,雖然相較Spine成本會更高,但卻可以在保留原畫細節的同時,做到更多細膩的演出表現,也能用於滑鼠互動等相關內容,舉個例子:棕色塵埃2(好感度互動)。
那我會想寫這篇文章,主要還是希望記錄自己對於Live2D插件的學習歷程,內含大量踩過的坑,會盡量以最幽默且最淺顯易懂的方式描述這個過程,如有說錯的部分也歡迎指正!因為我還在學習,有大佬願意分享經驗跟想法,小弟都非常樂意吸收。
在開始之前我們先制定一下名詞規範,由於Live2D是透過把畫作每個部位(包含眉毛、大腿、小腿、手臂甚至手指)都拆分開來,給予參數控制變形跟位移,所以文章裡全程都會稱這些零件為「部位」。
現在搞定名詞規範,文章的最後也同樣會有一些專有名詞的註解,就讓我們正式開始吧!
一、過度依賴AI後發生的災難
老實說,剛拿到動畫師給的檔案時,我完全沒有頭緒該如何開始,導入後只看到素材下面大量子物件,為一直得慶幸的是子物件有被合理分類,所以我還能看出他們分別是幹嘛的,官網也有很多教學跟使用說明文件可以翻閱。
(圖片來源:點我)
這邊看得懂是一回事,但要怎麼設計程式結構,讓模型可以透過滑鼠點擊跟拖曳產生變化?沒有頭緒的我選擇了AI問到底。
我詢問Claude如何設計程式碼,他給的方案是寫統一的Live2DController,在想要互動的部位零件上面掛Collider2D(註1)去檢測滑鼠,然後在Live2DController裡面設置所有「部位」、要修改的參數、條件等,然後統一做參數更新、滑鼠檢測等操作邏輯,後面還順便把數值系統也給做了進去。
直接把東西做成「上帝程式」(註2),等我回過神要拆掉邏輯時,已經完全辦不到了。
可能不懂程式這行的人不知道為什麼要拆?寫成上帝程式又會造成什麼問題?我舉個不太恰當的例子,想像你準備為晚餐炒一桌的菜,晚餐的菜品應該要有三菜一湯跟白飯。
此時你靈機一動,想著反正最後食物都要進到胃裡,何不一開始就把他們伴在一起,所以你先煮好了白飯,接著在上面灑鹽炒空心菜,又把牛肉跟辣椒炒了進去,接著大手一揮把蛋給炒了下去,最後再把沙茶醬跟湯也加進去炒。
那麼恭喜你,你可能做出了蛋炒牛肉燴飯,這道菜不是不能吃(反正我不吃),但跟一開始要的三菜一湯完全不同了。
此時你家人打電話回來,說我想把空心菜換成高麗菜,排骨湯要換成蛤蠣湯,你當場愣在了原地,因為他們都伴在一起炒了,你不可能把他們分離了。
這就是你把程式邏輯全部大雜燴,通通塞在一個程式碼裡面的問題,先不提這道菜美不美味,但凡今天有個三長兩短,你發現味道不對也很難確定是哪個環節出錯,如果有人要更換需求,你也很難把原本寫好的邏輯做抽離更新。
那AI工具固然很方便,但AI最大的問題也在於結構設計,如果你直接跟AI講說我想要ABC三道菜,那他真的會把ABC炒成一道菜給妳,順便附加一些你用不到的功能,跟你說這叫「拓展擴充性」。
先說我個人還是贊成使用AI工具加快工作速度,就只是說,我們從AI那邊拿到的程式碼都強烈建議重新審視一遍,甚至整個程式結構都建議先想好後,自己打底再請AI幫你一口氣補好比較麻煩的地方,但要怎麼補我也是建議自己先想過啦!不然AI還是會造他自己的喜好來補邏輯。
回到主題,在我用AI炒出這坨蛋炒牛肉燴飯後,雖然嚴格上來說也是可以用,但因為企劃一直在變動,且我自己也注意到程式碼運作已經不流暢,所以我決定重頭構思新的設計跟邏輯。
二、總之先讓滑鼠能跟Live2D互動
新系統我先設計了四套基本元件:
1. Live2DInteraction_Part(互動小零件),負責可互動部位的邏輯檢測、條件以及控制參數。
2. Live2DController_Base(互動控制台),統一控管各個部位是否條件達成可以被互動?有沒有在互動?以及其他企劃需求的交互邏輯等。
3. Live2DInteraction_DB(靜態資料庫),屬於靜態程式,所有的計算、判斷方法都在這裡面。
4. Live2DInteraction_CubismBridge(參數橋接器),負責繼承Live2D官方Interface(註3),並在接收到數值計算結果後,跟官方負責更新參數的元件做串聯。
Part元件會掛在每一個需要被互動的「部位」,然後我一樣採用Collider2D檢測去判定滑鼠,會選Collider主要是因為我可以很簡單地自定檢測大小。
那我這邊不採用CubismRaycaster是因為企劃需求,某些時候專案點擊邏輯本身相當複雜,可能有很多狀況要條列做狀態機切割,因此採用Collider2D檢測比較好控制。
但跟上一次不同,這次我選用OnPointer跟OnDrag(註4)修改部位狀態機(註5),這種方法比較「輕巧」,相當於你在黑暗的房間門口,拿手電筒照射地面看有沒有老鼠在上面。
那我舊系統則是使用OnTrigger(註6)這種依賴物理引擎的方法,這方法就像你必須自己走到房間裡面,親自摸到老鼠才知道有東西在地上。
但因為我只需要知道「老鼠的動態就好」,既不需要知道老鼠摸起來如何,也不需要知道老鼠的重量,所以統一放棄使用OnTrigger檢測。
至於滑鼠移動部分,透過OnDrag事件,傳遞滑鼠與螢幕座標系的Vector2(二維向量)方向跟位移量,去計算變形參數X跟Y的調整。
同樣用簡單一點的方法解釋,想像你身穿動態捕捉裝置,在遠程操控一隻機器人開合跳,電腦會把你的動作記錄下來後,寫成一張小紙條傳給機器人,機器人就根據紙條上寫的模仿動作,這樣看起來就像是你在控制機器人。
在更簡單的一句話就是:你的滑鼠移動會給電腦一個數字,然後模型根據你給的數字做偏移變形。
搞定滑鼠檢測後,就是如何讓指定的部位動起來。
三、我叫你改!Live2D的參數修改
開始前先提一個觀念,Live2D採用LateUpdate(註7)更新參數,所以它更新的單位是以幀計算,那假設我們1秒有60幀,Live2D會一瞬間把你修改的參數蓋掉。
如果你說滑鼠一直都有在移動,也是以幀去當單位更新參數所以沒問題的話,那就不得不提一個常見的互動需求,假設你今天要幫角色脫衣服怎麼辦?
你前一「秒」剛把衣服脫掉,下一「幀」衣服就穿回去了,怎麼想都不對吧?所以我的做法相當土炮,透過檢測「部位」判定互動完後要不要鎖定參數,如果要,就讓他們的參數一直在LateUpdate更新,也因為Live2D官網的插件有提供參數修改的方案(CubismUpdateController),所以我只要把更新後的數值計算完後統一由他們的Component(註8)更新就好。
備註:這邊沒有採用CubismParameterStore的原因,是基於固定參數隨時會有讓玩家重置的需求考量,才會統一使用自己手動控制來提高掌控度。
那說到這邊,可能又會有個新問題,我互動之前可能要先打開某個參數,好比說我想摸角色的頭,那就必須先把手的開關參數打開,這部分依舊要靠相似的邏輯來處理。
我的做法是設置一個「前置條件」,讓「部位」自己決定互動前,需不需要先打開特定參數開關。
假設我今天要摸頭以前必須先把手長出來,那就再判定到滑鼠點擊的瞬間,先把參數打開來,等檢測到手的開關打開後,才允許玩家開始移動滑鼠摸頭就好。
整體流程就會變成:檢測滑鼠點擊 ⮕ Preparation Parameters(預備參數)推到指定數值 ⮕ 檢測滑鼠移動 ⮕ ParameterX/Y兩個參數變化 ⮕ 滑鼠放開 ⮕ ParameterX/Y回歸預設 ⮕ Preparation Parameters(預備參數)回歸預設。
那剩下的就是讓視覺效果變好,例如Preparation Parameters(預備參數)在打開跟關閉時,要做平滑參數變化才會好,也能讓玩家清楚地看到手伸出來跟縮回去的過程,而非唐突長出一隻手在頭上亂摸。
四、角色總不能是死魚對吧?那就讓它自己動起來!
角色必須呼吸跟眨眼,那眨眼邏輯相對簡單,因為Live2D有提供方法可以直接使用,詳細可參考官方文件(CubismEyeBlinkController)。
呼吸則有兩種做法:
1. 動畫師做成AnimationClip(註9)來循環播放。
2. 透過官方元件(CubismHarmonicMotionController)控制參數位移。
兩種方法各有各的好,做成AnimationClip可以讓整體呼吸更自然生動,而官方元件則是邏輯最簡單,還可以搭配Live2D的物理運算去做連動效果。
但需要注意的是,AnimationClip的運作可能會被物理運算的插件給影響,導致他的更新時機被物理運算結果給覆蓋,從而無法正常播放動畫。
而且AnimationClip官方有提供兩種做法,一個是用Unity原生的Animator去生成Controller控制,另一個則是透過Live2D官方腳本控制,有興趣可參考官方文件(CubismMotionController),原則上二擇一就好。
表情部分則是透過CubismExpressionController去做控制,動畫師正常都會附上表情列表,你只要根據當前需求去呼叫邏輯就好。
個人嘗試下來最好的解法是寫角色狀態機去做統一管理,包含到互動該部位會有什麼反應,會說什麼話之類的,這樣邏輯就會比較統一好管理,也可以針對某些額外數值(例如好感度)去切換當下反應。
五、後記,終究是要根據企劃需求去設計程式
老實說Live2D互動系統還有很多可以講,例如說:工具系統(部位指定要用什麼工具才能互動)、數值系統(互動期間如何計算數值增長)、自動化系統(該部位是否允許系統自己演出互動)、彈簧系統(玩家放開滑鼠後「部位」是否會平滑歸位)......等其他純程式控制的系統。
但因為這些系統相對複雜,同時也會牽涉到一些專案機密,所以文章的最後,我決定分享一下自己這段期間跟Live2D相愛相殺的心得感想。
老實說一開始全靠AI時,我完全就是技術債爆炸的情況,所以後來我重寫系統時,就在動畫師的建議下重新翻看官方文件很久,但也因為官方文件寫得很零散,導致我很多東西漏看,那又是另一個故事了。
但不管是做什麼系統設計,前提都是要先確認需求,學長經常跟我說:「要先解析專案需求,才能夠去設計程式架構,思考自己要怎麼串接資料,等確認完了才開始動工。」
我的舊系統之所以會誕生,就是建立在我沒有完全剖析企劃導致,當時只想著把互動無腦做出來,結果過度依賴AI導致整個程式碼變成一坨稀飯,畢竟AI不會剖析專案需求。
後來設計新系統,我先重新剖析了專案需求,接著才去逐一設計每個元件運作(當然過程還是有詢問學長的意見跟看法啦),然後又去大量翻閱Live2D官方文件,慢慢了解怎麼跟Live2D的插件串連,最後一步一腳印把系統建構出來。
在一次次跟動畫師的研究中踩了很多坑後,我的狀態也從畏懼且陌生Live2D,變成熟悉且掌握Live2D,了解他每個元件的功用,了解他如何調整控制參數邏輯。
這是一種難以言喻的成就感。
最後,雖然新系統還有很多可以改進的空間,但從最初的「上帝程式」到現在的「模塊化分工」,我覺得自己選擇重新設計是正確的。
但當然啦......也是因為專案本身時間還充裕啦!如果專案只給我兩個禮拜要生出Demo,我也不敢這樣說重寫就重寫,應該會硬著頭皮用舊系統繼續做,好在老闆很通情達理給了我時間研究(抹汗)。
下一集我會講DialogueSystem(劇情對話系統)的研究與程式設計。
ToBeContinue......
今日的註解:
註1. Collider2D:想像一下你在角色或物件外面套了一個透明的方塊、圓圈、或多邊形,但這個外框不是給玩家看的,而是給遊戲用來判斷接觸事件的,好比說滑鼠有沒有接觸到?角色有沒有撞牆?
註2. 上帝程式:今天你明明可以請水電工、油漆工、木工、建築師幫你完成工作,但你選擇把所有工作都壓在一個人身上,結果就是大樓出狀況了,所有東西就一起垮掉。
註3. Interface:你為了避免不同廠牌的汽車有不同的加油孔,於是你成立了一個國家級的規範委員會,明文規定所有汽車的加油孔都必須要做成圓形的,這樣不管這台汽車來自哪裡,你都能用同一隻油槍加油。
註4. OnPointer/OnDrag:OnPointer就像保全攝影機,偵測到你進入範圍就拉響警報,OnDrag就像追蹤用的無人機,你進到範圍後真的搶了東西開始逃跑,那台無人機就開始追殺你,不斷跟保全說你往哪跑了。
註5. 狀態機:狀態機(State Machine)就相當於「規則表」,用來決定角色目前處於什麼狀態,以及能不能切換到下一個狀態」,好比說我有一套吃飯規則,那我就會根據這套吃飯規則去動作。
註6. OnTrigger:你今天進到某人的地盤,踩到了他們家的看門狗,於是乎那隻看門狗開始對你咆哮,吸引了附近所有人的注意,但這家人以及這隻狗很奇怪,你一離開那個地盤狗就不叫了,人也不追究了。
註7. LateUpdate:想像你在編排舞台劇,所有演員(Update)都站定位以後,燈光師(LateUpdate)才把燈光打到定點,確保所有演員都在他的燈光照射下表演。
註8. Component:想像你收到一隻空殼娃娃,你開始在他身上安裝零件(Component),像是眼睛、耳朵、腿之類的,讓他可以執行看、聽、走等動作。
註9. AnimationClip:可以想像成是「動作腳本」,好比說你希望這角色有受傷動畫,於是你請動畫師把受傷這個動作,製作成一段Clip用來播放。