[UnrealEngine] Plugin製作相關注意事項與心得分享

posted in: 未分類 | 0

前言

寫出一套好用好維護的程式碼相信會是所有程式設計師無意識中追求的目標,而把自己的功能包裝成Plugin是一個非常直接達成這個目的的手段,因為設計的過程會強迫把自己置更高、更抽象的位置往下俯瞰整個遊戲系統並定位出自己的功能應該在哪個位置。

只是,並不是任何的程式碼都適合放進Plugin中。因此接下來我的目的主要是想根據自己過去的經驗,分享一些自己在寫各種Plugin時的大原則。雖然這些想法不一定適用於所有的情境,應該也會有許多我沒考慮到的狀況,但基本上也就只是目前在我有限的經驗中現階段的想法。如果有人有不一樣的觀點也歡迎一起討論,或許可以碰撞出一些東西也說不定。

順帶一提,這邊的Plugin討論的主要是CodePlugin,其他類型的Plugin,例如Asset Only或BP Only,並不在接下來的討論範圍內,除非我有另外指明。

目前我在Unreal Marketplace已經有上架六項產品,內容包括Quest、Interact、Dialogue以及Tween,有興趣的可以參考: https://www.unrealengine.com/marketplace/en-US/profile/horizon-studio

另外我也會不定期的在我的FB頁面上分享一些開發日誌,通常是跟遊戲技術相關的話題,有興趣的歡迎追蹤:https://www.facebook.com/dorgonresearches

本文開始

Unreal中的程式碼我們可以非常粗爆的大致分層如下:EngineModule->EnginePlugin->GamePlugin->GameModule->GameFeaturePlugin。其中EngineModule、EnginePlugin我們頂多修修bug不可能做太大的修改;GameModule則是看專案團隊的風格會有不同的構成方式,我們等一下再回來聊這部份;而GamePlugin跟GameFeaturePlugin則是我們接下來要討論的重點。

EnginePlugin 、GamePlugin相對直觀,大家應該不會有什麼疑惑,但GameFeaturePlugin這個概念或許就有人沒聽過了。這個功能是5.0所引入的最大變革之一,對程式相關職能的人而言,特別是寫Gameplay相關的,我覺得其重要性甚至更勝於Nanite跟Lumen,因為相關機制會從根本性上的改變了我們程式碼的架構方式以及思考模式。

就設計理念看來,他是屬於DLC概念的實現,跟Patch不同,Patch需要蓋掉原本的內容,而DLC必須要做到「熱插拔」的機制。 就傳統UE4 project,我們要達到DLC的這項要求非常麻煩,因為我們只能對「Content-Only」plugin包出dlc包,而哪些dlc需要開啟或關閉則需要你在BaseGame中做出一些可以管理的設計。在GameFeature Plugin 機制出現後,我們總算不需要做這些勞力活了。注:這邊的BaseGame指的是 ${PROJECT_ROOT}/Source 下面所有的Runtime GameModule以及${PROJECT_ROOT}/Content下面所有會打包進去的Asset。

有些人會困惑於我們建出的 GameFeature Plugin 沒辦法跨專案共用,但這個理解並沒有錯,基本上GameFeature Plugin需要依賴於BaseGame而存在,因此我們也可以理解成他是專屬於project的Plugin。若你的功能想要跨專案共用,你要寫的是一般的GamePlugin而不是GameFeature。

在理解了GamePlugin跟GameFeaturePlugin的不同點之後,讓我們先來思考一下,Plugin到底是什麼?就字面上來看的話,形象一點解釋,他就像是一個插頭,把這個插頭插進我們的專案之後就能夠提供各種我們遊戲專案會需要的功能。雖然Plugin不像DLC需要做到「熱插拔」,但大家期望的是功能的插入跟移除是相對簡單的。因此每個GamePlugin在設計上要能做到獨立運作,也就是說在設計上只能依賴於Engine或EnginePlugin,依賴於其他同層級的GamePlugin會明顯造成遊戲專案的使用障礙。移除簡不簡單這件事取決定Plugin的功能與細部設計這邊先不展開討論,但對於功能的插入而言,應該沒有人會希望開啟某個Plugin前還需要去閱讀文件看清楚有哪些相依Plugin需要下載吧?若我們設計的Plugin有依賴於其他同層級的GamePlugin的情形發生,可能要思考看看哪些功能要下放到GameModule給專案自己實作,哪些功能需要整合進同一個GamePlugin中。

由上面的討論我們知道,Plugin在本質上就是對於能獨立運作功能的封裝,而這些功能來自於各種需求各異的不同專案,因此Plugin在設計上必須要放在一個更抽象的位置以滿足不同專案的需求。因此我不建議在Plugin中實現任何的框架機制,因為那個是Engine該做的事;若只是薄薄一層的實作,主要目的只是更改某些Engine的預設設定的話,那個則是專案GameModule該做的事,我們不該假設我們的設定會比Engine的預設更通用;若是覺得Engine的某些Function實作有bug或不夠完美,所以想繼承下來做override來做workaround,那個也是專案該做的事,我們可以寫一些文檔分享給其他團隊說明為什麼我們想做這些修改,而不是寫成某個Plugin要求大家先繼承某個class再做某些事,因為那不一定適用於所有的專案,再次強調,我們不該假設我們的實作會比Engine的實作更具有通用性,若你覺得你的目的是修掉Engine Bug,你該做的是上Github送PullRequest給Engine。

那麼到底什麼東西適合寫成一個Plugin,最好的判斷方法是:先假設把這個Plugin拿到UnrealMarketplace上賣時,我們是否能夠明確的指出這個Plugin想解決的是什麼問題?受眾是誰、有多少人可能會需要這個功能……等等類似的問題,若無法回答這些問題,代表相關功能的想法還不夠成熟,建議先拉回GameModule中等待各種需求的磨練。

那麼在把功能寫在GameModule時我們該注意什麼呢?就我所知,根據專案團隊的風格會有不同的構成方式,有人喜歡使用一個大module放入所有的功能、也有人喜歡依功能的性質導入一些模組化的設計,但不可否認的是,由於這邊的程式碼非常接近Gameplay、更迭會非常快速,有可能一個遊戲企畫的改變導致某個系統在瞬間變成一個沒用的東西。基於這種變動頻繁的特性,因此有些人會覺得不需要在GameModule的層級上浪費時間進行遊戲邏輯的模組化設計,反正就是想辦法用最快的方法將功能做出來,不要因為模組化而導入的各種限制而拖慢了組裝系統的時間。不過我自己倒是有不同的觀點,在GameModule這個層級做模組化反而是我們用來對抗變化的手段,良好的模組化設計應當要考慮到當你想把某個功能移除時需要耗費多少的力氣,理想狀況是直接把該Module移除就行,不過這部份就屬於不同系統的實作細節了,就不再往下討論。除此之外,我認為模組化的練習可以幫助我們慢慢的把相對不成熟的想法粹練出來。我們不需要一開始就用Plugin的高度在思考問題,在個人經驗不足或需求還不明確的情況下很容易陷入寸入難行的地步;相對的在GameModule層級進行設計的話,我們可以先以更貼近專案的需求的方式來思考怎麼進行抽象化的設計。

結論

以上寫了這麼多,大致下可以簡單用以下幾點描述:

    1. 每個GamePlugin在設計上要能做到獨立運作,也就是說在設計上只能依賴於Engine或EnginePlugin。

    2. 不建議在Plugin中實現任何的框架機制,因為那個是Engine該做的事,不該假設我們的設定會比Engine的預設更通用。

    3. 功能不要急著寫成Plugin,若你無法回答寫這個Plugin想解決的是什麼問題、受眾是誰時,先把功能放回GameModule磨練。

Leave a Reply

Your email address will not be published. Required fields are marked *