本文将详细介绍Unity工程的文件夹结构,以及动态加载资源的技术要点
一、Unity项目的文件夹结构
1.工程文件夹
在新建工程时,Unity会创建所有必要的文件夹。第一级文件夹有Assets,Library,Logs,Packages,ProjectSettings。
- Assets:最主要的文件夹,保存着所有游戏用到的资产
- Library:用于存放引擎必须的程序集和缓存资源。Library不存在时会自动生成,不需要也不建议上传到版本仓库(如SVN或Git仓库)中去
- Logs:用于存放使用时产生的日志
- Packages:目前大部分Unity的官方功能扩展都通过扩展包提供,Packages文件夹虽与扩展包有关,但里面只保存配置文件
- ProjectSettings:所有的工程设置,包括共工程对应的Unity版本都在ProjectSettings文件夹中。不能直接改动该文件夹中的内容,不然会造成版本兼容性问题
- (Temp):如果该工程正在被编辑,则会多出一个Temp(临时)文件夹,一旦工程关闭,该文件夹会自动消失
2.资产文件夹
由于Asset和Resource的含义相近,而且它们在Unity中都有特定的含义,因此翻译时将Asset称为资产,Resource称为资源,以示区分。
在Unity中,Asset(资源)和Resource(资源包)是两个不同的概念。
Asset(资源)是指在Unity项目中使用的各种资源文件,比如模型、纹理、材质、音频等。这些资源可以通过Unity编辑器导入到项目中,然后在代码中进行引用和使用。Assets文件夹是存放这些资源文件的默认文件夹。
Resource(资源包)是一个特定的文件夹,用于存放可在运行时加载的资源。可以将资源文件放在Resource文件夹中,并使用Resources类提供的方法在运行时加载这些资源。Resource文件夹可以位于Assets文件夹中的任意位置。
区别在于,Asset是在编辑器中管理和引用的资源,而Resource是在运行时动态加载的资源。Asset可以提前导入并在代码中直接引用,而Resource需要在代码中进行加载操作。另外,Asset在项目构建时会被打包到最终的应用程序中,而Resource则不会被自动打包,需要在运行时按需加载。
需要注意的是,Unity官方推荐使用AssetBundle来管理和加载运行时资源,因为它提供了更灵活和高效的资源管理方式。而Resource文件夹的使用在某些情况下可能会导致资源加载效率较低或管理不便。
Assets文件夹下所有的文件都是资产的一部分,但某一些资产不会被“打包”到最终发布的程序中,而其他资产则会被“打包”
要理解Assets文件夹的结构,首先要了解Assets文件夹下的几个特殊文件夹
文件夹 | 是否被打包 | 说明 |
Editor | 否 | 存放Unity编辑器专用的脚本和资源,如开发期用的扩展工具 |
Plugins | 是 | 存放第三方程序库 |
Resources | 全部 | 资源文件夹。该文件夹下所有资源都会被压缩并打包。只有此文件夹下的内容才可以用Resource.Load加载 |
Streaming Assets | 全部 | 该文件下的所有资源会被打包到最终的发布版中,但会保持原样,不会被压缩和加密。不需要让Unity处理的文件(如一些数据配置文件)适合放在此文件夹 |
除了以上特殊文件夹,在其他非特殊文件夹中的资产,Unity会根据是否引用了该资源而决定是否打包。
所有非编辑器专用的脚本资产文件都会被打包。这是由于非组件脚本也可能会被引用,不能依据是否挂载到物体上来确定一个脚本是否被用到。
被打包的资产都可以看作是发布的程序的一部分,但它们都是只读的,不能在运行时改写。换句话说,以上文件夹都不能用于做热更新。
二、META文件
在游戏的开发阶段会存在大量原始的资源和素材,如何管理它们是引擎需要考虑的。市面上的游戏引擎对原始资源的管理由以下两种主流方案。
- 引擎统一打包和管理所有资产。添加新资源时,通过统一的导入流程打包到专门的文件中,原始文件不再使用。
- 虽然引擎管理所有资产,但依旧会使用原始资源文件。一些必要信息(如模型的导入设置)会写在另外的配置文件中。
无论哪种方案,都必须对所有资产统一管理,而不能使用未处理、无记录的原始资源。
Unity采用的方案明显是第二种,它会对Assets文件夹下的所有文件生成一个名称相同,扩展名为meta的文件,包括文件夹也会生成对应的META文件。META文件是一个文本文件,里面记录了很多必要的信息,包括资产唯一标识符GUID、引用关系和资源导入设置的信息等。
其中资产唯一标识符GUID非常重要,它在资源初次导入时生成,有了它就能准确定位资源文件,文件的改名、移动和内容修改都不会使GUID变化。
脚本的META文件内容通常比较简单,只有十几行,而某些资源(如3D模型动画)往往有上千行,里面记录了必要的设置信息。
理解了META文件的重要性,在实际中还要注意以下几点:
- META文件与原始资源文件要一起管理。例如,新增Assets文件或文件夹时,一定要联通生成的META文件一同提交到版本仓库
- 重命名和移动文件要在Unity内进行,这样可以保证相应的META文件自动完成相应操作
- 不能直接复制META文件,否则会导致GUID重复。复制资产时应尽量在Unity内用复制命令(Ctrl+D)进行,这样会自动生成GUID不同的META文件
- 用脚本操作资产时要注意META文件的同步,尽量使用Unity提供的API,而不要使用原始的文件进行读写操作。这一点主要针对编辑器脚本,因为编辑器脚本有时会修改源文件的内容
三、动态加载和释放资源
一般简单的方法时使用公开变量加拖曳的方法引用资源,实际上动态加载资源的方法适用范围更广,也方便在运行时切换不同的资源。
1、动态加载资源
位于Resources文件夹下的资源都可以动态加载。动态加载资源的方法主要有Resources.Load()和Resources.LoadAll()两种,前者用于加载单个文件,而后者可以加载一个文件夹内的所有资源,结果以数组形式返回。
值得一提的是,重复加载相同的文件不会导致文件被多次加载,引擎可以判断哪些资源已经被加载过了。
2、卸载资源
加载的资源会占用内存空间,不再使用资源的时候应当卸载,卸载方法有以下两个。
//卸载一个资源 public static void UnloadAsset(Objetc assetToUnload); //自动卸载所有未使用的资源 public static AsyncOperation UnloadUnusedAssets();
UnloadAsset()用于强制卸载一个资源,不管它是不是正在被使用。如果卸载了正在使用的资源,则会直接影响当前场景的表现。
UnloadUnusedAssets()方法会用异步方法自动卸载未被使用的资源。但问题是,如果脚本中有一个变量正在引用着某个资源,或是场景中某个忘记销毁的物体引用着某个资源,则该资源会印在使用中而不会被自动卸载。
可以看出,卸载背后隐含的问题要比加载多得多,对编程方法提出了更高的要求
3、代码示例
以下用一段简单的代码演示加载和卸载资源的编程方法
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestResources : MonoBehaviour { void Start() { //加载资源// //与之资源用GameObject表示,路径不包含Resources和扩展名 GameObejct go = Resources.Load<GameObject>("Prefab/Cube"); //资源加载和实例化是不同的 GameObject go2 = Instantiate(go); //加载其他类型的资源 Texture2D image= Resources.Load<Texture2D>("Images/1"); Debug.Log(image.name); //卸载资源// //强制卸载 TestResources.UnloadAsset(image); //销毁物体 Destroy(go2); } }