通过验证发现,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 不应当被重命名,不应当被放入某个别的命名空间,否则就必须要改代码才能解决引用丢失的问题。