所做的毕设是由多个同学按模块分工完成的一个项目,之前几届同学做的时候都是各自任意命名,导致最后模块整合为项目时报了一堆资源冲突的错,只能靠人力手动调整解决问题,这时候才感受到Unity对项目合并功能的支持何其孱弱。
Unity其实本身并不提供合并这一功能,不过是可以把某一个模块打包导出,然后在另外一个将之模块导入。
然而Unity所谓的导出导入,并不是想当然那样:导出的包被封装成了一个整体,位于其专属的命名空间,不与导入项目的其他资源发生冲突。事实上,在Unity中导入一个包,几乎就是把里面全部的资源文件解压到当前的Assets目录下了,简单粗暴地几近于Ctrl C + Ctrl V!压根不存在任何命名空间、资源分隔等有关理念,这也正是命名冲突的根本原因所在。
1. 命名冲突的类型
可能造成命名冲突,可以分成三种类型:
- 普通资源的冲突
- Resources资源的冲突
- 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. 解决方案总结
为了避免命名冲突,需要注意的便是以下三个地方:
- Assets普通资源分目录存放
- Resouces文件夹下再多放一层目录作为区分
- 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
模块一样。
这种解决方法毕竟过于简单粗暴,冗余度就容易比较大,所以不适用于成熟规范的项目,但对于简单的模块分工,还是足够简单有效的。