Unity资源加载的一些问题

Unity小白,决定开始一点点积累一些相关知识。

参考:http://blog.csdn.net/swj524152416/article/details/54022282




Unity中资源动态加载的几种方式比较
unity会自动将场景需要引用到的资源打包到安装包里,没有到的不会跟进去。我们在编辑器里看到的Asset中的文件结构只是工作于编辑器环境下的,在游戏中unity会重新组织数据库。这是我们一定会遇到一个需求,即动态的加载我们自己的文件,而且想维护这个文件存储和加载的位置,并且是各种自定义的文件。
unity资源的类型:
1.Unity内置的常用asset,fbx\jpg...
2.textasset: txt、binary等,对应了它的TextAsset类,可以直接读入文本或者二进制byte
3.scriptable object它是序列化的Object实例,例如把一个Object实例保存成scriptable object,读入进来后就直接在内存中变成那个实例
4.asset bundle它是一个资源压缩包,里面包含了一堆资源
动态资源的存放
有时我需要存放一些自己的文件在磁盘上,例如我想把几个bundle放在初始的安装里, unity有一个streaming asset的概念,用于提供存储接口的访问。我们需要在编辑器建立一个StreamingAssets名字的文件夹,把需要我们放在客户磁盘上的动态文件放在这个文件夹下面,这样安装后,这些文件会放在用户磁盘的指定位置,这个位置可以通过Application.streamingAssetsPath来得到。
Unity必须通过导入将所支持的资源序列化,生成AssetComponents后,才能被Unity使用。
GUID与fileID(本地ID)
Unity会为每个导入到Assets目录中的资源创建一个meta文件,文件中记录了GUID,GUID用来记录资源之间的引用关系。还有fileID(本地ID),用于标识资源内部的资源。资源间的依赖关系通过GUID来确定;资源内部的依赖关系使用fileID来确定。
InstanceID(实例ID)
Unity为了在运行时,提升资源管理的效率,会在内部维护一个缓存表,负责将文件的GUID与fileID转换成为整数数值,这个数值在本次会话中是唯一的,称作实例ID(InstanceID)。
程序启动时,实例ID缓存与所有工程内建的对象(例如在场景中被引用),以及Resource文件夹下的所有对象,都会被一起初始化。如果在运行时导入了新的资源,或从AssetBundle中载入了新的对象,缓存会被更新,并为这些对象添加相应条目。实例ID仅在失效时才会被从缓存中移除,当提供了指定文件GUID和fileID的AssetBundle被卸载时会产生移除操作。
卸载AssetBundle会使实例ID失效,实例ID与其文件GUID和fileID之间的映射会被删除以便节省内存。重新载入AssetBundle后,载入的每个对象都会获得新的实例ID。
资源的生命周期
Object从内存中加载或卸载的时间点是定义好的。Object有两种加载方式:自动加载与外部加载。当对象的实例ID与对象本身解引用,对象当前未被加载到内存中,而且可以定位到对象的源数据,此时对象会被自动加载。对象也可以外部加载,通过在脚本中创建对象或者调用资源加载API来载入对象(例如:AssetBundle.LoadAsset) 
对象加载后,Unity会尝试修复任何可能存在的引用关系,通过将每个引用文件的GUID与FileID转化成实例ID的方式。一旦对象的实例ID被解引用且满足以下两个标准时,对象会被强制加载:
实例ID引用了一个没有被加载的对象。
实例ID在缓存中存在对应的有效GUID和本地ID。
如果文件GUID和本地ID没有实例ID,或一个已卸载对象的实例ID引用了非法的文件GUID和本地ID,则引用本身会被保留,但实例对象不会被加载。在Unity编辑器中表现为空引用,在运行的应用中,或场景视图里,空对象会以多种方式表示,取决于丢失对象的类型:网格会变得不可见,纹理呈现为紫红色等等。
MonoScripts
一个MonoScripts含有三个字符串:程序库名称,类名称,命名空间。 
构建工程时,Unity会收集Assets文件夹中独立的脚本文件并编译他们,组成一个Mono程序库。Unity会将Assets目录中的语言分开编译,Assets/Plugins目录中的脚本同理。Plugin子目录之外的C#脚本会放在Assembly-CSharp.dll中。而Plugin及其子目录中的脚本则放置在Assembly-CSharp-firstpass.all中。 
这些程序库会被MonoScripts所引用,并在程序第一次启动时被加载。
Assets
为Unity编辑器下的资源文件夹,Unity项目编辑时的所有资源都将置入此文件夹内。在编辑器下,可以使用以下方法获得资源对象:AssetDatabase.LoadAssetAtPath("Assets/x.txt"); 
注意:此方法只能在编辑器下使用,当项目打包后,在游戏内无法运作。参数为包含Assets内的文件全路径,并且需要文件后缀。Assets下的资源除特殊文件夹内,或者在会打入包内的场景中引用的资源,其余资源不会被打入包中。
Resources
资源载入:Assets下的特殊文件夹,此文件夹内的资源将会在项目打包时,全部打入包内,并能通过以下方法获得对象:
Resources.Load("fileName"); 
Resources.Load("fileName"); 
注意:函数内的参数为相对于Resource目录下的文件路径与名称,不包含后缀。Assets目录下可以拥有任意路径及数量的Resources文件夹,在运行时,Resources下的文件路径将被合并。
例:Assets/Resources/test.txt与 Assets/TestFloder/Resources/test.png在使用Resource.Load("test")载入时,将被视为同一资源,只会返回第一个符合名称的对象。如果使用Resource.Load(“test”)将返回text.txt;
如果在Resources下有相同路径及名称的资源,使用以上方法只能获得第一个符合查找条件的对象,使用以下方法能或得到所有符合条件的对象:
Object[] assets = Resources.LoadAll("fileName"); 
TextAsset[] assets = Resources.LoadAll("fileName"); 
相关机制
在工程进行打包后,Resource文件夹中的资源将进行加密与压缩,打包后的程序内将不存在Resource文件夹,故无法通过路径访问以及更新资源。
在程序启动时会为Resource下的所有对象进行初始化,构建实例ID。随着Resource内资源的数量增加,此过程耗时的增加是非线性的。故会出现程序启动时间过长的问题,请密切留意Resource内的资源数量。
卸载资源:所有实例化后的GameObject 可以通过Destroy函数销毁。请留意Object与GameObject之间的区别与联系
Object可以通过Resources中的相关Api进行卸载
Resources.UnloadAsset(Object);//卸载对应Object 
Resources.UnloadUnusedAssets();//卸载所有没有被引用以及实例化的Object 
注意以下情况:
Object obj = Resources.Load("MyPrefab"); 
GameObject instance = Instantiate(obj) as GameObjct; 
...... 
Destroy(instance); 
Resources.UnloadUnusedAssets(); 
此时UnloadUnusedAssets将不会生效,因为obj依然引用了MyPrefab,需要将obj = null,才可生效。
StreamingAssets
概述:StreamingAssets文件夹为流媒体文件夹,此文件夹内的资源将不会经过压缩与加密,原封不动的打包进游戏包内。在游戏安装时,StreamAssets文件件内的资源将根据平台,移动到对应的文件夹内。StreamingAssets文件夹在Android与IOS平台上为只读文件夹. 
你可以使用以下函数获得不同平台下的StreamingAssets文件夹路径:
Application.streamingAssetsPath 
请参考以下各平台下StreamingAssets文件夹的等价路径,Application.dataPath为程序安装路径。Android平台下的路径比较特殊,请留意此路径的前缀,在一些资源读取的方法中是不必要的(AssetBundle.LoadFromFile,下详)
Application.dataPath+"/StreamingAssets"//Windows OR MacOS 
Application.dataPath+"/Raw" //IOS 
"jar:file://"+Application.dataPath+"!/assets/" //Android 
文件读取:StreamingAssets文件夹下的文件在游戏中只能通过IO Stream或者WWW的方式读取(AssetBundle除外)
IO Stream方式
using(FileStream stream =  
File.Open(Application.streamingAssetsPath+"fileName", 
FileMode.Open)) 

//处理方法 

WWW方式(注意协议与不同平台下路径的区别)
using(WWW www = new WWW( 
Application.streamingAssetsPath+"fileName")) 

yield return www; 
www.text; 
www.texture; 

AssetBundle特有的同步读取方式(注意安卓平台下的路径区别)
string assetbundlePath = 
#if UNITY_ANDROID 
Application.dataPath+"!/assets"; 
#else 
Application.streamingAssetsPath; 
#endif  
AssetBundle.LoadFromFile(assetbundlePath+"/name.unity3d"); 
PersistentDataPath
Application.persistentDataPath 
Unity指定的一个可读写的外部文件夹,该路径因平台及系统配置不同而不同。可以用来保存数据及文件。该目录下的资源不会在打包时被打入包中,也不会自动被Unity导入及转换。该文件夹只能通过IO Stream以及WWW的方式进行资源加载。
WWW载入资源:WWW是一个Unity封装的网络下载模块,支持Http以及file两种URL协议,并会尝试将资源转换成Unity能使用的AssetsComponents(如果资源是Unity不支持的格式,则只能取出byte[])。WWW加载是异步方法。
byte[] bytes = WWW.bytes; 
string text = WWW.text; 
Texture2D texture = WWW.texture; 
MovieTexture movie = WWW.movie; 
AssetBundle assetbundle = WWW.assetBundle; 
AudioClip audioClip = WWW.audioClip; 
new WWW:每次new WWW时,Unity都会启用一个线程去进行下载。通过此方式读取或者下载资源,会在内存中生成WebStream,WebStream为下载文件转换后的内容,占用内存较大。使用WWW.Dispose将终止仍在加载过程中的进程,并释放掉内存中的WebStream
WWW.LoadFromCacheOrDownload
int version = 1; 
WWW.LoadFromCacheOrDownload(PathURL+"/fileName",version); 
使用此方式加载,将先从硬盘上的存储区域查找是否有对应的资源,再验证本地Version与传入值之间的关系,如果传入的Version>本地,则从传入的URL地址下载资源,并缓存到硬盘,替换掉现有资源,如果传入Version<=本地,则直接从本地读取资源;如果本地没有存储资源,则下载资源。此方法的存储路径无法设定以及访问。使用此方法载入资源,不会在内存中生成 WebStream(其实已经将WebStream保存在本地),如果硬盘空间不够进行存储,将自动使用new WWW方法加载,并在内存中生成WebStream。在本地存储中,使用fileName作为标识符,所以更换URL地址而不更改文件名,将不会造成缓存资源的变更。 
保存的路径无法更改,也没有接口去获取此路径
AssetBundle: AssetBundle是Unity支持的一种文件储存格式,也是Unity官方推荐的资源存储与更新方式,它可以对资源(Asset)进行压缩,分组打包,动态加载,以及实现热更新,但是AssetBundle无法对Unity脚本进行热更新,因为其需要在打包时进行编译。
AssetBundle加载
加载方式

之前已经提及,不再详细说明,使用WWW 或者 AssetBundle相关API加载,其中AssetBundle的API只能进行本地加载。
AssetBundle.LoadfromMemory(byte[] bytes)
此API是一个例外,用来对加密的Assetbundle进行读取,可以结合WWW使用。
压缩LZMA(Ziv-Markov chain algorithm)格式
Unity打包成AssetBundle时的默认格式,会将序列化数据压缩成LZMA流,使用时需要整体解包。优点是打包后体积小,缺点是解包时间长,且占用内存。
LZ4格式:5.3新版本添加的压缩格式,压缩率不及LZMA,但是不需要整体解压。LZ4是基于chunk的算法,加载对象时只有响应的chunk会被解压。
压缩格式在打包时通过AssetBundleOption参数选择
AssetBundle加载后会在内存中生成AssetBundle的序列化架构的占用,一般来说远远小于资源本身,除非包含复杂的序列化信息(复杂多层级关系或复杂静态数据的prefab等)
Unity资源加载的一些问题
AssetBundle.Unload(bool unloadAllLoadedObjects);AssetBundle只有唯一的一个卸载函数,传入的参数用来选择是否将已经从此AssetBundle中加载的资源一起卸载。另外,已经从AssetBundle中加载的资源可以通过Resources.UnloadAsset(Object)卸载。如果想通过Resources.UnloadUnusedAssets()卸载从AssetBundle加载的资源,一定要先将AssetBundle卸载后才能生效。