最近的工做跟 UI 打交道比較多, 各類坑.數據庫
今天從 Prefab 的序列化功能來講說 System.Diagnostics.Conditional 的妙用.安全
咱們作 UI 面對各類按鈕, 組件的獲取方式大體也就兩種, 一種直接序列化到 Prefab 中, 另外一種是在代碼中去獲取 : 編輯器
序列化函數
tagObj = transform.Find("Child/Cube").gameObject; // 代碼獲取
各有各的好處 :測試
序列化很直接, 代碼都不用寫了, 而且隨便你拖動 UI 到哪一個節點, 都能正確引用. 不過問題也很明顯, 過了幾天連本身都找不着北了, 有過長期維護經驗的人應該瞭解.spa
代碼獲取的方式從維護性來講, 直接就能讓看的人知道那個組件在哪一個節點上, 理解起來更簡單. 但是若是節點路徑發生變動, 就要出問題了, 這在頻繁改動的 UI 設計上來講很要命. 特別是若是使用封裝好的函數進行安全獲取, 就更難發現問題了, 好比:設計
// 封裝好的函數, 連錯都不報 public static GameObject GetGameObject(Transform from, string find) { var trans = from.Find(find); if(trans) { return trans.gameObject; } return null; }
若是從效率的角度來看, 應該是序列化 Prefab 的效率要高於使用代碼查找的效率的, 由於序列化對象是一個惟一ID, 而代碼查找時 Transform 的子對象是在列表中的, 因此 Find 函數查找效率是跟 List 同樣的 : code
這裏用 tagObj 引用了本身, 看到序列化對象的 ID 就是指向上面的 m_GameObject 的, 應該就是個內部 GUID 了. 從效率上看略勝一籌, 由於我在以前的項目碰到過有幾千個 Child 的節點, 而後經過數據庫獲得的幾千個數據對節點下面的對象進行查找, 那效率簡直酸爽, 有這種需求的必定要先厲遍 Transform 把全部節點都放到 Dictionary 裏引用啊, 血的教訓...orm
第二點, 存儲大小 :對象
若是序列化到 Prefab 裏面, 它使用了 ID 做爲引用, 增長的存儲大小基本是個定值, 而且打包以後還能進行壓縮等, 實際佔用空間很是小, 並且做爲資源它能夠熱更.
代碼若是寫在 C# 中, Find("XXX/ooo") 裏面的字符會被放到靜態域中, 這就很要命了, 由於到項目後期不只因爲過多的代碼, 更多的字符串致使程序包過大, 咱們真的碰到過超過 IOS 50多M限制的單個 DLL 問題...固然若是寫在 Lua 中的話就跟資源同樣了, 你能編譯成二進制, 你能壓縮, 萬能的 Lua. 不過按照通常狀況, "XXX/ooo" 字符通常都會比 ID 更長特別是 UI 層級很深的狀況, 有中文的時候就更糟了.
假設咱們有了 Lua, 你問問開發人員他們願意寫一大堆 Find 代碼, 仍是簡單拖一拖了事? 由於 Lua 在使用上跟 C# 沒有什麼不一樣, 若是使用了序列化誰都懶得再去寫代碼了... 這真的是少有的 Unity 官方功能比民間方法更牛的特例.
這樣看來序列化真是又簡單又高效, 還省空間, 主要問題仍是在後期維護上, 好比咱們的變量叫 tagObj, 製做的人可能引用的對象名稱是 Cube, 名稱上徹底沒有關聯性, 在一些不可預知的狀況下若是引用丟失了, 連製做者都不知道原來引用的是哪一個對象的狀況多的是. 咱們能把兩種模式結合起來的話就行了, 好比在組件上面加個註釋 :
恩, 不錯, 還有更好的 :
System.Diagnostics.Conditional 能夠根據編譯條件決定是否編譯, 它在非編輯器下就跟註釋同樣是不會加進去的, 而在編輯器下你是能夠經過 GetCustomAttributes 獲取的, 那麼它的用處就大了 :
1. 在編輯器下檢查序列化對象是否是丟了, 丟了的話找 Attribute 而後經過 loadPath 去自動獲取.
2. 在編輯器下檢查序列化對象跟 loadPath 的對象是否是一致, 檢查數據正確性.
這些看似簡單的功能, 在發佈版本以前可能救很多人的命呢, 避免了每次抓人去祭天. 經過這個應用方法添加了一些信息, 並能提供上述功能支持.
測試一下 :
-------- 編輯器 --------
-------- 發佈後 --------