一:原因
通過驗證發現,Unity 是通過 meta 文件來索引資源,生成唯一的 guid,僅和具名的相對資源路徑有關,和文件內容無關。
同一目錄下不能存在同名的目錄和文件,因此可以保證生成的 guid 的唯一。
如果存在 monoscript 找不到了的話:
- 在 Assets 中的可能是相對路徑變更導致,或者腳本被重命名
- 在 Dll 中的則和打包前所在路徑無關,可能是命名空間或類名變更,還跟 dll 的 guid 相關
prefab 本身屬於 Unity 資源,之前掛載的配置信息都以 yml 格式的配置格式存儲,因此可以通過腳本批量替換資源的索引來實現。
下面是 prefab 中記錄 monoscript 資源信息的例子:
二:生成原理
假如 monoscript 在 Assets 中時,Unity 只需要通過其 guid 就能做唯一性確定,其 fileID 統一被 Unity 統一為 11400000。
如果在 Dll 中,這時候 fileID 就能排上用場了,上圖中在 m_Script 對應的資源信息中, guid 為 Unity 根據 dll 路徑計算的 guid,fileID 則為根據 dll 中的 (命名空間 + 類名)計算出的唯一標志。
在 UnityEditor 中應該是可以通過:
AssetDatabase.LoadAllAssetsAtPath + AssetDatabase.TryGetGUIDAndLocalFileIdentifier 獲取 guid 和 fileID。
fileID 也可以直接計算:
將 "s\0\0\0" + spacename + classname 作為密鑰通過 csharp 的 MD4 散列化后取前四個字節,然后通過小端序單字節讀取,即 (0<<8)|byte 得到新的 32 位帶符號整數,即為新的 fileID。
大白話就是 MD4 加密后單字節拼接成串,取前四個字符,然后首尾鏡像翻轉,此時再弄成 32 位的二進制,用帶符號的方式讀取,32 位的話也就是 4 Gb 多 。
帶符號讀取也就是說 fileID 取值區間為 負的20多億 到 正的20多億,因此如果有 20多億 個 dll 中的mono類在同一個項目,那 unity 就有一半的機率崩潰。
三:解決方案
知道了 Unity 索引 monscript 的方式,那么就可以通過腳本的方式批量替換 prefab 中舊的索引來達到修復在新工程中使用的目的。
這里有個需要注意的問題就是需要一次性成功替換所有缺失的 monoscript , UnityEditor 不會在你每替換成功一個缺失的 script 或屬性后在編輯器中做出正確的呈現。
如果是非 moscript 比如是某些掛在對象上的引用路徑,那么就必須在腳本中預先替換正確,否則 UnityEditor 會自動將其置空,導致信息丟失。
四:思考
假如通過動態掛載腳本,可能就不會產生引用丟失的問題。
但會產生新的問題,因為掛載的類名是在代碼中寫死,也就是說這個 mono 不應當被重命名,不應當被放入某個別的命名空間,否則就必須要改代碼才能解決引用丟失的問題。