Unity资源机制

1、概述

本文意在阐述Unity资源机制相关的信息,以及一些关于个人的理解与试验结果。另外还会提及一些因机制问题可能会出现的异常以及处理建议。大部分机制信息来源于官方文档,另外为自我验证后的结果。

2、资源

概述

Unity必须通过导入将所支持的资源序列化,生成AssetComponents后,才能被Unity使用。以下是Unity对Assets的描述:

Assets are the models,textures,sounds and all other “content”files from which you make your game。

资源(Asset)是硬盘中的文件,存储在Unity工程的Assets文件夹内。有些资源的数据格式是Unity原声支持的,有些资源则需要转换为源生的数据格式后才能被使用。

对象(UnityEngine.Object),代表序列化数据的集合,表示某个资源的具体实例。它可以是Unity使用的任何类型的资源,所有对象都是UnityEngine.Object基类的子类

资源与对象时一对多的关系。

名称

描述

支持格式

Audio Clip

音频剪辑

音频:.aif .wav .mp3 .ogg音轨:.xm .mod .it .s3m

Cubemap Texture

立方体贴图纹理

Flare

耀斑

Font

字体

.ttf

Material

材质

Meshes

网格

.FBX .dae .3DS .dxf .obj

Movie Texture

电影贴图

.mov .mpg .mpeg .mp4 .avi .asf (导入需要QuickTime)

Procedural Material Assets

程序材质资源

Render Texture

渲染纹理

Text Asset

文本资源

.txt .html .htm .xml .bytes

Texture 2D

二维纹理

PSD TIFF JPG TGA PNG GIF BMP IFF PICT

除此之外,想使用Unity不支持导入,或者未经导入的资源,只能使用IO Stream或者WWW 方法,这些将在下文对应栏目中说明。

注意:AssetBundle不是资源组件,故无法用资源组件的方式载入,只能使用WWW或者AssetBundle相关接口载入与读取

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所引用,并在程序第一次启动时被加载。

3、资源文件夹

 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文件夹,故无法通过路径访问以及更新资源。

依本文2.3章节所述,在程序启动时会为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的方式进行资源加载。

4、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不及时释放,将占用大量的内存,推荐搭配using方式使用,以下两种方式等价。

WWW www = new WWW(Application.streamingAssetsPath+"fileName");

try

{

yield return www;

www.text;

www.texture;

}

finally

{

www.Dispose();

}

using(WWW www = new WWW(

Application.streamingAssetsPath+"fileName"))

{

yield return www;

www.text;

www.texture;

}

如果载入的为Assetbundle且进行过压缩,则还会在内存中占用一份AssetBundle解压用的缓冲区Deompresion Buffer,AssetBundle压缩格式的不同会影响此区域的大小。

WWW.LoadFromCacheOrDownload

int version = 1;

WWW.LoadFromCacheOrDownload(PathURL+"/fileName",version);

使用此方式加载,将先从硬盘上的存储区域查找是否有对应的资源,再验证本地Version与传入值之间的关系,如果传入的Version>本地,则从传入的URL地址下载资源,并缓存到硬盘,替换掉现有资源,如果传入Version<=本地,则直接从本地读取资源;如果本地没有存储资源,则下载资源。此方法的存储路径无法设定以及访问。使用此方法载入资源,不会在内存中生成 WebStream(其实已经将WebStream保存在本地),如果硬盘空间不够进行存储,将自动使用new WWW方法加载,并在内存中生成WebStream。在本地存储中,使用fileName作为标识符,所以更换URL地址而不更改文件名,将不会造成缓存资源的变更。

保存的路径无法更改,也没有接口去获取此路径

5、 AssetBundle

概述

AssetBundles let you stream additional assets via the WWW class and instantiate them at runtime. AssetBundles are created via BuildPipeline.BuildAssetBundle.

AssetBundle是Unity支持的一种文件储存格式,也是Unity官方推荐的资源存储与更新方式,它可以对资源(Asset)进行压缩,分组打包,动态加载,以及实现热更新,但是AssetBundle无法对Unity脚本进行热更新,因为其需要在打包时进行编译。

Assetbundle打包

平台兼容性

AssetBundle适用于多种平台,但不同平台所使用的AssetBundle并不相同,在创建AssetBundle时需要通过参数来指定目标平台,其关系如下表

Standalone

WebPlayer

IOS

Android

Standalone

WebPlayer

IOS

Android

创建API

public enum BuildAssetBundleOptions

{

None = 0,

//Build assetBundle without any special option.

UncompressedAssetBundle = 1,

//Don't compress the data when creating the asset bundle.

CollectDependencies = 2,

//Includes all dependencies.

CompleteAssets = 4,

//Forces inclusion of the entire asset.

DisableWriteTypeTree = 8,

//Do not include type information within the AssetBundle.

DeterministicAssetBundle = 16,

//Builds an asset bundle using a hash for the id

ForceRebuildAssetBundle = 32,

//Force rebuild the assetBundles.

IgnoreTypeTreeChanges = 64,

//Ignore the type tree changes when doing the incremental build check.

AppendHashToAssetBundleName = 128,

//Append the hash to the assetBundle name.

ChunkBasedCompression = 256

//Use chunk-based LZ4 compression when creating the AssetBundle.

}

AssetBundleManifest manifest =

BuildPipeline.BuildAssetBundles("OutputPath",BuildAssetBundleOptions,tragetPlatform);

在Unity的5.3版本中,简化了AssetBundle的打包方式,只留下了一个api与寥寥几个设置参数,而之前最让人头痛的资源依赖管理,也被默认进行处理。 而在每个Asset文件的Inspector面板上都会多出一个Asset Labels的设定栏:

AssetBundle name:需要将此资源打包的AssetBundle名称

AssetBundle Variant:需要将此资源打包的AssetBundle的变体名

Variant

Variant是5.3以后新添加的一个概念,这个值其实是一个尾缀,将添加在对应AssetBundle的名称之后,如:ddzgame.hd,hd就是Variant(从此以后AssetBundle的尾缀已经跟其文件类型本身没有任何联系)。

自动打包脚本

从以上可知,如果需要一个一个的对资源设置AssetBundle Name与Variant实在太过繁琐与麻烦,也可能出现纰漏,好在可以通过脚本去批量设置这两个参数:

AssetImporter assetImporter = AssetImporter.GetAtPath("path");

assetImporter.assetBundleName = "Assetbundle Name";

assetImporter.assetBundleVariant = "Assetbundle Variant";

其中path是资源在Assets目录下的路径。

Scene打包

Scene打包跟资源打包无异,唯一需要注意的是:Scene只能与Scene打入同一个AssetBundle内,而无法与其他资源打入同一个AssetBundle。

PS:AssetBundle内的Scene需要在AssetBundle加载后,通过SceneManager来加载。

AssetBundle依赖

依赖机制

假设有AssetBundleA与 AssetBundleB两个AssetBundle,AssetBundle中的资源引用了AssetBundleB中的资源,则称AssetBundleA依赖于AssetBundleB。具体实例请看下图注意被依赖AssetBundle需要加载的时机

注意其依赖的机制: AssetBundle中保存有其中所有资源的GUID,FileID等序列化信息,AssetBundle只会在内存中寻找其依赖资源所在的AssetBundle,并自动从中加载出所需资源。具体可参考本文2.3章节

 Manifest

在前面有提到,在5.3中,Unity会自动处理AssetBundle中资源的依赖关系。在默认情况下,如果AssetBundle间有交叉的资源引用,不会再重复打包,在打包AssetBundle后,会发现其在输出目录多出了一个与目录名称相同的无后缀AssetBundle文件,其为自动生成的AssetBundleManifest文件,其内保存有此次生成的所有AssetBundle之间的依赖关系与清单。我们可以在载入这个AssetBundle后使用以下方法获得此对象。

AssetBundle.LoadAsset("AssetBundleManifest");

Manifest保存有重要的依赖信息,在载入AssetBundle时,可以通过Manifest查询其是否有依赖的AssetBundle,然后我们手动对其进行管理,避免依赖项丢失而出现bug

string[] fullnames = AssetBundle.GetDirectDependencies(fullname);

string[] fullnames = AssetBundle.GetAllDependencies(fullname);

Direct方法会返回所有直接依赖的AssetBundle名称数组,All方法会返回所有依赖的AssetBundle名称数组,fullname包括名称与Variant。推荐使用Direct方法做递归处理,避免重复载入。

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等)

AssetBundle卸载

卸载API

AssetBundle.Unload(bool unloadAllLoadedObjects);

AssetBundle只有唯一的一个卸载函数,传入的参数用来选择是否将已经从此AssetBundle中加载的资源一起卸载。另外,已经从AssetBundle中加载的资源可以通过Resources.UnloadAsset(Object)卸载。如果想通过Resources.UnloadUnusedAssets()卸载从AssetBundle加载的资源,一定要先将AssetBundle卸载后才能生效。

资源卸载总览

内存关系图

当AssetBundle被卸载后,实例ID与其文件GUID和本地ID之间的映射会被删除, 即其无法被其后加载的依赖于它的资源所查找及引用。详情请参考本文2.3章节

 案例分析

案例1 游戏切换到后台一段时候切回,出现shader或者Texture丢失。

在移动平台,当程序切到主界面或者在后台长时间运行时,GPU会自动对后台程序的资源进行清理。如果shader或者Texture是从AssetBundle中加载出来,而此AssetBundle已经被卸载的话,Unity无法在程序恢复时从内存中加载这些资源,从而造成丢失。有人会问,这些资源不是已经加载到内存中了么?但是,他们在被加载到GPU之后会被从内存中清除。因此要防止此状况最稳健的方法,就是在场景切换前,不要卸载掉其所属的AssetBundle。

案例2 当经常使用AssetBundleB.Unload(false)卸载时,有时会发现AssetBundle中的资源在内存中有多份同时存在。

问题的根源在于从AssetBundle中加载出来的资源,在该AssetBundle卸载之后与其的联系就断开了。

例如:从AssetBundleA中加载出来一个Prefab p1,p1依赖资源tex1也会自动加载到内存中。然后用AssetBundle.Unload(false)卸载AssetBundleA,此时p1与AssetBundleA的联系断开。之后,从AssetBundleA中加载Prefab p2,p2也依赖资源tex1,那么在加载p2时tex1会再次被加载到内存中,导致重复。

Unity资源处理机制(Assets/WWW/AssetBundle/...)读取和加载资源方式详解相关推荐

  1. unity 手机端和编辑器下播放带有透明通道的视频详解

    unity 手机端和编辑器下播放带有透明通道的视频详解 在项目开发中,可能会涉及到视频的播放,普通的视频播放如果大家不会的请自行百度,今天主要讲解带有透明通道的视频的播放问题,尤其是在手机端的处理,下 ...

  2. php中读取大文件实现方法详解

    php中读取大文件实现方法详解 来源:   时间:2013-09-05 19:27:01   阅读数:6186 分享到:0 [导读] 本文章来给各位同学介绍php中读取大文件实现方法详解吧,有需要了解 ...

  3. python医学图像读取_对python读取CT医学图像的实例详解

    需要安装OpenCV和SimpleItk. SimpleItk比较简单,直接pip install SimpleItk即可. 代码如下: #coding:utf-8 import SimpleITK ...

  4. AssetBundle——外部加载资源Asset

    几篇很不错的文章 AssetBundle创建到使用入门 全面理解Unity加载和内存管理 实用的创建AssetBundle的脚本 相关资源 相关的共享资源下载  本共享包括创建assetbundle的 ...

  5. Assetbundle打包及加载入门详解(二)

    接着上篇,本节写如何更加科学的打包,以及打包中的一些依赖关系. 科学打包: 一般来说会把物体打成一个assetbundle包,把材质打成另个包,然后在加载的时候都加载出来. 当然如果是有的模型会频繁更 ...

  6. 【TensorFlow】TFRecord数据集的制作:读取、显示及代码详解

    在跑通了官网的mnist和cifar10数据之后,笔者尝试着制作自己的数据集,并保存,读入,显示. TensorFlow可以支持cifar10的数据格式, 也提供了标准的TFRecord 格式. te ...

  7. TensorFlow学习笔记(二十四)自制TFRecord数据集 读取、显示及代码详解

    在跑通了官网的mnist和cifar10数据之后,笔者尝试着制作自己的数据集,并保存,读入,显示. TensorFlow可以支持cifar10的数据格式, 也提供了标准的TFRecord 格式,而关于 ...

  8. 4-5:TCP协议之连接管理机制(三次握手、四次挥手详解)

    文章目录 一:TCP三次握手过程和状态变迁 (1)三次握手过程和状态变迁过程详解 (2)为什么必须要三次握手? A:只有三次握手才可以阻止重复历史连接的初始化(主要原因) B:同步双方初始序列号 C: ...

  9. linux权限可被登录用户读取,Linux 用户及权限详解

    Linux 用户及权限详解 用户 , 组 ,权限 安全上下文(secure context): 权限: r,w,x 文件: r : 可读,可以使用类似cat 等命令查看文件内容. w : 可写,可以编 ...

最新文章

  1. 一起学nRF51xx 20 -  移植SDK蓝牙例程
  2. Caffe学习系列(1):安装配置ubuntu14.04+cuda7.5+caffe+cudnn
  3. 接口java_JAVA 初识接口
  4. 【PHP】Sublime下PHP网站开发指南
  5. Android实现相册分享功能,Android系统自带分享功能的实现(可同时分享文字和图片)...
  6. 博文视点大讲堂第18期:从草根到巨人——互联网时代的LAMP开源架构
  7. The most impressed error
  8. CentOs 开启ssh服务
  9. 大数据资源共享网盘下载
  10. 企业微信会话存档功能
  11. 一.python实现AI拟声---pycharm工具下载及python环境配置
  12. firefox、IE下的几个不同属性的方法调用
  13. python微信群管理开禁言_微信群主怎么禁言一个人?操作方法介绍!
  14. Linux 0.11-shell 程序读取你的命令-43
  15. Phonetic symbol 单元音 - 短元音 ɒ(新)/ ɔ(旧) 与 长元音 ɔː
  16. 您需要知道Rails中的erb以及如何掌握它
  17. BT技术概念 — 一些术语的意思
  18. 2021年低压电工新版试题及低压电工证考试
  19. 台式机如何通过无线网卡释放热点?
  20. 基于ssm技术的校自助阅览室的设计与实现毕业设计源码242326

热门文章

  1. adb 清理内存_adb命令查看手机应用内存使用情况
  2. Defcon 2019 Qualify: redacted puzzle Writeup
  3. 【产品经理三节课】第4章 产品调研入门
  4. 下载Synechococcus elongatus UTEX 2973(accession no.为GCA_000817325.1 )的基因组注释文件,统计其中染色体序列(CP006471.1)前10
  5. 「PAT乙级真题解析」Basic Level 1033 旧键盘打字 (问题分析+完整步骤+伪代码描述+提交通过代码)
  6. 关于消防设施的RFID资产管理,RFID消防设施资产管理-新导智能
  7. Revit API:找到轮廓族的路径
  8. Android开发之黑科技安装APP无启动图标
  9. 如何给抖音视频选择配乐?音乐是抖音作品重要的组成部分
  10. 做游戏,学编程(C语言) 15 太鼓达人