Unity项目合并时命名冲突问题

所做的毕设是由多个同学按模块分工完成的一个项目,之前几届同学做的时候都是各自任意命名,导致最后模块整合为项目时报了一堆资源冲突的错,只能靠人力手动调整解决问题,这时候才感受到Unity对项目合并功能的支持何其孱弱。

Unity其实本身并不提供合并这一功能,不过是可以把某一个模块打包导出,然后在另外一个将之模块导入。

然而Unity所谓的导出导入,并不是想当然那样:导出的包被封装成了一个整体,位于其专属的命名空间,不与导入项目的其他资源发生冲突。事实上,在Unity中导入一个包,几乎就是把里面全部的资源文件解压到当前的Assets目录下了,简单粗暴地几近于Ctrl C + Ctrl V!压根不存在任何命名空间、资源分隔等有关理念,这也正是命名冲突的根本原因所在。

1. 命名冲突的类型

可能造成命名冲突,可以分成三种类型:

  1. 普通资源的冲突
  2. Resources资源的冲突
  3. C#命名空间冲突

1.1. 普通资源的冲突

  • 这个是最容易理解的,假设张三建立了一个场景名为main.unity,他将之置于Scenes目录下,也就是说,这个资源的路径时/Scenes/main.unity。而李四恰好也有一个同路径的场景。那么在两个项目合并的时候,这两个场景文件从理论上来说都应该放在/Scenes/main.unity这个位置的,可是这个位置到底该放张三还是李四的场景,便有了冲突。
  • 其对应的解决方法也很容易想到,比如,张三的全部资源,一律放在/ZhangSan/目录下,李四同理,这样合并成大项目后,对应只用了也会各居其位,互不干扰。

1.2. Resources资源的冲突

  • Unity的Resource资源可以存在于Assets根目录或者任意子目录下,这两者完全是等价的,也就是说路径为/ZhangSan/Resources/a.xml/ZhangSan/Config/Resources/b.xml的两个文件分别可以用以下两个语句加载:

    TextAsset textAsset = Resources.Load("a") as TextAsset;
    TextAsset textAsset = Resources.Load("b") as TextAsset;

    反正我是实在搞不清Unity为什么要把Resources的加载设计成这个鬼样子,不分Resouces位置也就算了,后缀名都不不分,你自己想要一个a.xml和一个a.txt都不允许。总之,自己都容易一不小心把两个资源的名称给弄冲突了。
    所以,如果仅仅时做到了前面提到的普通资源分文件夹还不够,这样假如张三有/ZhangSan/xxx/Resources/foo.xml,李四又有/LiSi/xxx/Resouces/foo.txt。这样,如果代码里面有Resouces.Load("foo") as TextAsset这样的语句,那么该返回foo.xml还是foo.txt呢?

  • 解决办法我反正想了半天也只想到个蠢到不行的办法,就是不管你的Resouces是在根目录还是子目录下,都一律在Resouces目录下再加一层目录做隔离。也就是张三Resouces的资源都放在在/ZhangSan/Resources/ZhangSan/或者/ZhangSan/xxx/Resources/ZhangSan/下面,这样使用代码加载的时候也成了Resouces.Load("ZhangSan/foo")的形式了。

1.3. C#命名空间冲突

  • 比如还是张三,他有一个脚本Player.cs控制某个场景,而李四也有一个同名脚本Player.cs。如果按照前面提过的命名规则,这两个脚本合并后会位于不同文件夹,不会抢位置。但实际上,Unity认为所有的脚本不管位于什么位置,都视为全部在同一目录下。这样,张三给他场景中的物体绑定的Player.cs脚本,系统怎么知道是哪一个呢?另一方面,从纯编程语言的角度看,如果Player.cs中具有方法public static void foo();,那么当我在其他地方使用Player.foo();的时候,到底时张三还是李四的类中的方法被调用了呢?

  • 这种冲突并不是资源的冲突,而是C#语言的的命名空间范畴的冲突,解决方法也不能靠建文件夹。推荐的方法是,张三的编写的脚本统一放在ZhangSan这个namespace,比如他的Player.cs,就成了

    namespace ZhangSan
    {
    public class Player : MonoBehaviour
    {
    public static void foo() {
    ...
    }
    ...
    }
    }

    这样一来,当在Unity中给物体添加脚本的时候,这次Unity会先让你选择ZhangSan命名空间,然后时其下的Player脚本。而当其他地方的代码想调用foo的时候,也就成了ZhangSan.Player.foo(),从而与调用李四的代码区分了开来。

2. 解决方案总结

为了避免命名冲突,需要注意的便是以下三个地方:

  1. Assets普通资源分目录存放
  2. Resouces文件夹下再多放一层目录作为区分
  3. C#脚本对应存放在不同命名空间

大概可以做成如下的样子:

Assets/
├── LiSi
│   ├── Pictures
│   │   └── xyz.png
│   ├── Resouces
│   │   └── LiSi
│   │       └── abc.xml
│   ├── Scenes
│   │   └── main.unity
│   └── Scripts
│       └── Player.cs(namespace LiSi)
└── ZhangSan
    ├── Pictures
    │   └── xyz.png
    ├── Resouces
    │   └── ZhangSan
    │       └── abc.xml
    ├── Scenes
    │   └── main.unity
    └── Scripts
        └── Player.cs(namespace ZhangSan)

当然,如果有公用的资源,这可以构建出一个名为Common的模块出来,就如同ZhangSan模块一样。

这种解决方法毕竟过于简单粗暴,冗余度就容易比较大,所以不适用于成熟规范的项目,但对于简单的模块分工,还是足够简单有效的。