聊聊Unity里的嵌套Prefab

今天正好和朋友聊到嵌套Prefab這個話題,發現這個其實是一個很多項目都需要但是Unity並沒有提供內置支持的功能。在過去的項目中我們也實踐過不同的解決方案,也了解過其他團隊的一些做法,在這裡正好整理一下,供大家參考/吐槽。

ps. 博客蹭一波流量 聊聊Unity里的嵌套Prefab | Loading & Learning

Nested Prefab

嵌套其實一個很常見的需求:多個Prefab同時需要一個共同的子Prefab。但問題在於保存時,整個prefab會成為一個整體,子prefab和原來的就斷開連接了。這時候如果需要統一修改子prefab就做不到,其實也就失去了prefab的意義所在。

下面我將分享ABC三策來「解決」這個問題,之所以不說上中下是因為各有優劣。

Prefab vs. Anchor

這個是我們目前用的解決方案: 用一個空的GameObject然後貼一個腳本去保存路徑:

編輯的時候點一下Load載入,編輯完了之後點Save保存。這個本質其實是空掛點和Prefab的來回切換,有幾個好處:

  • Scene里儘可能都用Anchor而非Prefab,這樣Scene本質上就是一個空殼,能極大的避免多人協作帶來的衝突(想起做第一個項目的時候簡直淚目);
  • 運行時可以按需載入,或者使用同一的Manager來後台非同步載入;
  • Prefab可以拆的比較細,這樣能減緩載入帶來的卡頓;
  • 載入時自動載入子Prefab;保存時同理。

當然也有不可避免的缺點,最大的一個就是必須拋棄原生的Prefab機制:如果直接使用Inspector上方自帶的Select/Revert/Apply就會破壞這套流程,只能使用Component上丑爆了的按鈕。

ps. 我曾經試圖用自己的腳本去接管原生的Prefab按鈕,後來發現只有uPrefabs的思路靠譜…不過這樣會帶來其他的問題,且聽下文分解。

Cook & PropertyModification

這裡分享一個兄弟團隊的思路,他們的解決方案基於poor mans nested prefabs:父Prefab保存了子Prefab的引用;在編輯時獲取子Prefab信息後直接利用Editor API來「繪製」子Prefab;在打包的時候加入一步COOK步驟,根據引用將子Prefab實例化出來。

當然這個做法還比較簡陋:原來的代碼其實只處理了MeshRenderer的情況;如果想在UGUI里使用,那麼預覽部分就要重新打磨下。但接下來要說的重點其實是:如果不同的Prefab里引用的子Prefab需要有區別,應該怎麼做?

其實答案已經在Editor API中:PrefabUtility里的GetPropertyModifications、SetPropertyModifications等介面。有了這些信息,我們可以在不同父Prefab中保存同一個子Prefab和不同的修改項。

如何維護指向子Prefab內的引用

順便引出另一個問題:如何在父Prefab上保存對子Prefab元素的引用?如果父Prefab上的public Text hp直接指向子Prefab里的文字,這樣在保存的時候會引用失敗。

這裡提出一個巧妙的封裝public Ref<Text> hp

public class Ref<T> where T : UnityEngine.Object {n [SerializeField]n private T obj;n [SerializeField]n private GameObject target;n [SerializeField]n privat string path;nn public T GetObj();n}n

在使用時hp.GetObj().text = xxx如果子Prefab未實例化(這種情況只可能發生在編輯器模式下),那麼根據path自動載入;如果已經實例化的情況下直接根據target和path去找到對應的Component就行了。

誇了這麼多,現在要說說這個思路的缺點:整套方法實在是過於「精巧」,在使用中容易撞到奇葩的情況…

  • 子Prefab的結構變化會導致Ref<T>失效;
  • 打包的時候實例化需要消耗不少時間(備份老的、搜索並實例化、打包、還原),同時包體會變大一些;
  • 子Prefab本身的修改會不會和保存下來的PropertyModifications衝突?而且我發現導出的PropertyModifications其實包含了蠻多無用信息

uPrefabs

uPrefabs是一套非常強大的Prefab增強插件:

  • 支持單獨的Component的Save/Revert
  • 完整的嵌套Prefab解決方案

其實它實現的思路和上面說的有些相同。當時我還特別好奇它是如何接管到原生Prefab的按鈕,後來發現丫直接重載了整個GameObject面板,然後像素級去重新繪製了整個面板,可以說是非常的喪心病狂了…

不過這個插件的缺點也十分明顯——太太太太太太卡了…有興趣的朋友可以自行測試一下(逃

Conclusion

當然不同團隊的解決方案肯定是有所區別的,整理完上面的三個方法我發現其實有不少相同的地方:

  • 方法一、二的都是在Editor下儘可能lazy Instantiate(當然方法二的Ref<T>用起來更加優雅);
  • 方法二、三都使用了同樣的思路來支持Component級別的diff,並利用COOK來解決打包時的展開;
  • 三個方法都選擇保存路徑來解耦Prefab引用。

硬要說的話,其實三個方法依次下來是越來越「精巧」,同時「成本」也在不斷升高(咦,我怎麼想到了蘇聯製造vs.美國製造的笑話)。

最後歡迎大家討論和分享更好的思路~下期


推薦閱讀:

從零開始學基於ARKit的Unity3d遊戲開發系列10
大萌喵的Unity後期特效第一發---鏡頭炫光與光暈(Flare)
(Unity) 為被 Lua 隔斷的 C# 實現添加 Profiler 支持
Unite 2016 針對移動設備端的Unity應用優化
從零開始學基於ARKit的Unity3d遊戲開發系列6

TAG:Unity游戏引擎 |