Unity 中 prefab 挂载的 monoscript 脚本丢失

一:原因

通过验证发现,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 中旧的索引来达到修复在新工程中使用的目的。

const repPrefab = url =>
read(url)
.map(repMonoScript(["8f154857129e6754d89d0b85120f3d6d","1647811386"],["10f249957fdaec046913066eefc2e56c","2108208475"]))
.map(repMonoScript(["8f154857129e6754d89d0b85120f3d6d","1829626281"],["10f249957fdaec046913066eefc2e56c","-295734741"]))
.map(repMonoScript(["8f154857129e6754d89d0b85120f3d6d","601656639"],["ecffc06ff217de3489dc39cc78b7bb2a","2033745238"]))
.map(repScriptReference)
.map(write(url));
prefbArr.forEach(repPrefab);

这里有个需要注意的问题就是需要一次性成功替换所有缺失的 monoscript , UnityEditor 不会在你每替换成功一个缺失的 script 或属性后在编辑器中做出正确的呈现。

如果是非 moscript 比如是某些挂在对象上的引用路径,那么就必须在脚本中预先替换正确,否则 UnityEditor 会自动将其置空,导致信息丢失。

四:思考

假如通过动态挂载脚本,可能就不会产生引用丢失的问题。

但会产生新的问题,因为挂载的类名是在代码中写死,也就是说这个 mono 不应当被重命名,不应当被放入某个别的命名空间,否则就必须要改代码才能解决引用丢失的问题。

posted on 2021-07-15 21:30  Lowki  阅读(3047)  评论(0编辑  收藏  举报