【进阶】Unity资源解决方案之AssetBundle

阵子研究了一下Untiy的AssetBundle,把相关的一些知识点整理跟大家分享一下。


1、什么是AssetBundle

AssetBundle是Unity pro提供的一种用来存储资源的文件格式,它可以存储任意一种Unity引擎能够识别的资源,如Scene、Mesh、Material、Texture、Audio、noxss等等,同时,AssetBundle也可以包含开发者自定义的二进制文件,只需要将自定义文件的扩展名改为.bytes,Unity就可以把它识别为TextAsset,进而就可以被打包到AssetBundle中。Unity引擎所能识别的资源我们称为Asset,AssetBundle就是Asset的一个集合。

AssetBundle的特点:

压缩(缺省)、动态载入、本地缓存;

 

2、AssetBundle VS Resource

AssetBundle作为Unity官方推崇的资源更新方案,与传统的Resource差异如下:

a、Resource放在Resources目录下,resources.assets文件,单个文件有2GB限制,首次必须全部下载;

b、AssetBundle创建需要通过Editor脚本创建,支持动态下载,是Unity Web Caching License唯一可以缓存的内容;

 

3、AssetBundle的适用平台与跨平台性

AssetBundle适用于多种平台,包括网页应用、移动应用、桌面应用等,可以动态更新,但不同平台所使用的AssetBundle并不相同,在创建离线AssetBundle的时候需要通过参数来指定目标平台,相容关系如表所示:

 

Standalone

WebPlayer

ios

android

Standalone

WebPlayer

 

 

ios

 

 

 

android

 

 

 

 

4、AssetBundle的工作流程(与flash加载swf类似)

a、创建AssetBundle;

b、上传到Server;

c、游戏运行时根据需要下载(或者从本地cache中加载)AssetBundle文件;

d、解析加载Assets;

e、使用完毕后释放;

5、创建AssetBundle

5.1如何创建AssetBundle

Unity引擎提供了创建AssetBundle的API,通过编译管线BuildPipeline来创建AssetBundle文件,总共有三种方法:

a、BuildPipeline.BuildAssetBundle(mainAsset : Object, assets : Object[], pathName : string, options : BuildAssetBundleOptions = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, targetPlatform : BuildTarget = BuildTarget.WebPlayer) : bool

该API将编辑器中的任意类型的Assets打包成一个AssetBundle,适用于对单个大规模场景的细分;

b、BuildPipeline.BuildStreamedSceneAssetBundle(level : string[], locationPath : string, target : BuildTarget) : String

该API将一个或多个场景中的资源及其所有依赖以流加载的方式打包成AssetBundle,一般适用于多单个或多个场景进行集中打包;

c、BuildPipeline.BuildAssetBundleExplicitAssetNames(assets : Object[], assetNames : string[],pathName : string, options : BuildAssetBundleOptions = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, targetPlatform : BuildTarget = BuildTarget.WebPlayer) : bool

该API功能与a相同,但创建的时候可以为每个Object指定一个自定义的名字。(一般不太常用)

5.2关于BuildAssetBundleOptions

a、CompleteAssets

使每个Asset自身完备,包含所有的Components;

b、CollectDependencies

包含每个Asset依赖的所有其他Asset;

c、DisableWriteTypeTree

在AssetBundle中不包含类型信息。需要注意的是,如果将AssetBundle发布到web平台上,则不能使用这个选项;

d、DeterministricAssetBundle

使每个Object具有唯一的、不变的HashID,便于后续查找可以用于增量发布AssetBundle;

e、UncompressedAssetBundle

不进行数据压缩。如果使用这个选项,因为没有压缩/解压的过程,AssetBundle发布和加载会更快,但是AssetBundle也会更大,导致下载变慢。

5.3AssetBundle之间的依赖

如果游戏中的某个资源被多个资源引用(例如游戏中的Material),单独创建AssetBundle会使多个AssetBundle都包含被引用的资源(这里跟flash编译选项中的链接选项有些像),从而导致资源变大,这里可以通过指定AssetBundle之间的依赖关系来减少最终AssetBundle文件的大小(把AssetBundle解耦)。

具体方法是在创建AssetBundle之前调用BuildPipeline.PushAssetDependencies和BuildPipeline.PopAssetDependencies来创建AssetBundle之间的依赖关系,它的用法就是一个,后压入中的元素依赖内的元素。(记得要pop!)

举个例子:

http://km.oa.com/files/post_photo/310/202310/686453832dbffa6f98c3327080da35f8.jpg

现在游戏内有mat1和mat2两个Material,他们使用了相同的Texture zhuanqiang

不使用依赖

http://avocado.oa.com/fconv/files/201406/50d4ecb7750d7d86950a4a14a715e924.files/image002.jpg

使用依赖

http://avocado.oa.com/fconv/files/201406/50d4ecb7750d7d86950a4a14a715e924.files/image003.jpg

6、远端Server的AssetBundle下载

Unity引擎提供了两种方式从服务器下载AssetBundle文件,分别是缓存机制和非缓存机制。

6.1缓存机制

通过WWW.LoadFromCacheOrDownload (url : string, version : int)接口来下载AssetBundle,下载后的AssetBundle会自动被保存到Unity引擎的缓存区内,该方法是Unity推荐的AssetBundle下载方式。下载AssetBundle的时候,该接口会先在本地缓存中查找该文件,看其之前是否被下载过,如果下载过,则直接从缓存中加载,如果没有,则从服务器尽享下载。这样做的好处是可以节省AssetBundle文件的下载时间,从而提高游戏资源的载入速度(还可以节省下载流量)。同时开启多个Coroutine进行WWW的LoadFromCacheOrDownload操作(缓存中),经测试开启的WWW现成越多,速度会越快,但是需要考虑时机的机器火平台的承载能力。如果一定要从网上下载资源的话,线程数最好设为5个(别人的经验),很多平台也有自己的限制,例如有的浏览器只能同事加载6个等等。

 

需要注意的是,Unity提供的默认缓存大小是根据发布平台不同而不同的(可以向Unity购买Caching license支持)。目前对于web player的网页游戏,默认缓存大小为50M;对于PC上的客户端或者IOS¥Android上的移动游戏,默认缓存大小为4GB。

代码:

WWW www = WWW.LoadFromCacheOrDownload (Url, 1);

yield return www;

AssetBundle ab = www.assetBundle;

 

6.2非缓存机制

通过创建一个WWW实例来对AssetBundle文件下载,下载后的AssetBundle文件将不会进入Unity的缓存区。使用这种方法每次都会从远端服务器下载。

代码:

WWW www = new WWW(Url);

yield return www;

AssetBundle ab = www.assetBundle;

 

7、载入AssetBundle对象

7.1通过WWW类方法和属性

直接通过WWW.assetBundle属性来创建AssetBundle,注意:通过WWW加载的AssetBundle在解析Asset之前一定要先调用WWW.assetBundle;

 

7.2通过API动态创建

AssetBundle.CreateFromFile接口从磁盘创建一个AssetBundle文件的内存对象,该方法仅支持非压缩格式的AssetBundle。

 

7.3通过API动态创建

AssetBundle.CreateFromMemory接口可以从内存数据流创建一个AssetBundle内存对象。主要用于对数据的加解密上。

例如:

WWW www = new WWW(url);

yield return www;

byte[] encrypedData = www.bytes;

byte[] decryptedData = YourDecryptionMethod(encrypedData);//解密函数

AssetBundle ab = AssetBundle.CreateFromMemory(decrypedData);

 

8、从AssetBundle中加载Assets

8.1Assets的加载

AssetBundle.Load (name : string) : Object 从bundle中加载名为name的对象;

AssetBundle.Load (name : string, type : Type) : Object 从bundle中加载被指定类型的名为name的对象;

AssetBundle.LoadAsync (name : string, type : Type) : AssetBundleRequest 异步地从bundle中加载被指定类型的名为name的对象(异步加载需要Unity Pro专业版);

AssetBundle.LoadAll (type : Type) : Object[] 加载所有包含在asset bundle中且继承自type的对象;

AssetBundle.LoadAll () : Object[] 加载所有包含在asset bundle中的对象;

AssetBundle.mainAsset 主资源在构建资源boundle时指定(只读),该功能可以方便的找到bundle内的主资源。例如,你也许想有一个角色的预制体并包括所有纹理、材质、网格和动画文件。但是完全操纵角色的预设体应该是你的mainAsset并且易于被访问;

例如:

//开始下载
WWW www = new WWW(url);
//等待下载完成 
yield return www;
//获取指定的主资源并实例化
Instantiate(www.assetBundle.mainAsset);

 

8.2AssetBundle中加载Level

Application.LoadLevel 该接口可以通过名字或者索引载入AssetBundle文件中包含的对应场景,当夹在新场景时,所有之前加载的GameObject都会被销毁;

Application.LoadLevelAsync 该接口的作用与Application.LoadLevel相同,不同的是该接口是异步加载,即加载时主线程可以继续执行;

Application.LoadLevelAdditive 该接口不同与Application.LoadLevel的是并不销毁之前加载的GameObject;

Application.LoadLevelAdditiveAsync 该接口的作用与Application.LoadLevelAdditive相同,不同的是该接口是异步加载,即加载时主线程可以继续执行;

例如:

WWW www = new WWW(url);

yield return www;

AssetBundle ab = www.assetBunlde;

Application.loadLevel("Level1");

 

9、AssetBundle与内存

内存一直都是开发者关注的一个重点,如果要了解清楚内存的使用情况。

http://avocado.oa.com/fconv/files/201406/50d4ecb7750d7d86950a4a14a715e924.files/image004.jpg

9.1加载AssetBundle对内存的影响

Unity引擎在使用WWW方法时会分配一系列的内存空间来存放WWW实例对象、WebStream数据。该数据包括原始的AssetBundle数据、解压后的AssetBundle数据以及一个用于解压的Decompression Buffer。一般情况下,Decompression Buffer会在原始的AssetBundle解压完成后自动销毁,但需要注意的是,Unity会自动保留一个Decompression Buffer不被系统回收,这样做的好处是不用过于频繁的开辟和销毁解压Buffer,从而在一定程度上降低CPU的消耗。

当把AssetBundle解压到内存后,开发者可以使用WWW.assetBundle属性来获取AssetBundle对象,从而可以得到各种Assets,进而对这些Assets进行加载或者实例化操作。加载过程中,Unity会将AssetBundle中的数据流转变为引擎可以识别的信息类型(纹理、材质、对象等)。加载完成后,开发者可以对其进行进一步的操作,比如对象的实例化、纹理和材质的复制和替换等。

9.2AssetBundle的卸载

1)AssetBundle.Unload(true) 该接口会强制卸载掉所有AssetBundle中加载的Asset,包括AssetBundle的映射结构、自身对Web Stream的引用以及从AssetBundle创建出来的所有资源,该接口不推荐使用。

2)AssetBundle.Unload(false) 该接口会释放AssetBundle内的序列化数据,但是任何从这个AssetBundle中实例化的物体都将完好。当然,你不能从这个AssetBundle中加载更多物体。

Resources.UnloadUnusedAssets该接口会卸载掉没有使用的Assets,作用范围是整个系统。

3)对于实例化出来的GameObject,可以调用GameObject.Destory()接口来卸载。该接口会延后到一个合理的时机进行处理。

注意:这是U3D没有处理好的一个环节。在WWW加载资源完毕后,对资源进行instantiate后,对其资源进行unload,这时问题就发生了,instantiate处理渲染需要一定的时间,虽然很短,但也是需要1,2帧的时间。此时进行unload会对资源渲染造成影响,以至于没有贴图或 者等等问题发生。解决办法:自己写个时间等待代码,等待个0.5秒到1秒之后再进行Unload。这样就不会出现instantiate渲染中就运行unload的情况了。

 

10关于其他

10.1在AssetBundle中嵌入脚本

AssetBundle中的资源上如果Attach了脚本,打包的时候该脚本是不会被打到AssetBundle中的,其实这里只是保存了一个类似于指针的关联,如果需要把脚本也动态打到AssetBundle中,还需要做一番工作。

首先,将脚本预先编译成assembly,把assembly保存成.bytes文件,这样Unity会把它识别为TextAsset,就可以将这个TextAsset打包到AssetBundle中了,载入后可以通过反射机制使用该脚本,代码如下:

AssetBundle bundle = WWWW.assetBundle;

TextAsset txt = bundle.load("MyBinaryAsText", typeof(TextAsset)) as TextAsset;

byte[] bytes = txt.bytes;

var assembly = System.Reflection.Assembly.Load(bytes);

需要注意的是,IOS平台不支持动态载入脚本。

10.2AssetBundle的版本控制

AssetBundle使用WWW.LoadFromCacheOrDownload(string url, int version, uint crc)加载,其中的第二个参数-version可以用来做版本控制,该参数强制用户从服务器下载一个更高版本的AssetBundle。我们可以通过第三个参数crc来实现AssetBundle的内容校验,当crc不为0的时候,Unity会校验AssetBundle的CRC码,如果不等,则说明文件损坏,Unity会重新下载该文件。对于crc的获取,(老版本没有提供方法,只能通过LoadFromCacheOrDownload传一个错误的crc,从log中获取),新版本在BuildAssetBundle的时候增加了一个out类型的参数,该参数会返回正确的crc码,打包的时候可以记录下来以供后面使用。

 

10.3关于Editor和Runtime之间共享资源

1)Unity提供了一种可以公用的类——noxssableObject,适用于描述动态划分场景;通过代码划分场景——>打包多个AssetBundle——>将划分信息记录在ScriptableObject中,并保存至Asset——>载入时先载入划分信息,再根据这个划分信息载入AssetBundle。

2)使用XML文件。

10.4关于编辑器扩展

Unity3D可以通过事件触发来执行你的编辑器代码,但是我们需要一些编译器参数来告知编译器何时需要触发该段代码。 [MenuItem(XXX)]声明在一个函数上方,告知编译器给Unity3D编辑器添加一个菜单项,并且当点击该菜单项的时候调用该函数。触发函数里可以编写任何合法的代码,可以是一个资源批处理程序,也可以弹出一个编辑器窗口。代码里可以访问到当前选中的内容(通过Selection类),并据此来确定显示视图。与此类似,[ContextMenu("XXX")]可以向你的上下文菜单中添加一个菜单项。 当你编写了一些Component脚本,当它被附属到某个GameObject时,想在编辑视图即可在Scene视图观察到效果,那么你可以把[ExecuteInEditMode]写在类上方来通知编译器,该类的OnGUI和Update等函数在编辑模式也也会被调用。我们还可以使用[AddComponentMenu("XXX/XXX")]来把该脚本关联到Component菜单中,点击相应菜单项即可为GameObject添加该Component脚本。

为了避免不必要的包含,Unity3D的运行时和编辑器类分辨存储在不同的Assemblies里(UnityEngine和UnityEditor)。Editor目录下的脚本会在其它脚本之后进行编译,这方便了你去使用那些运行时的内容。而那些目录下的脚本是不能访问到Editor目录下的内容的。所以,你最好把你的编辑器脚本写在Editor目录下。

 

10.5关于差量发布

在5.2中介绍了创建AssetBundle的参数,其中的d选项在选中的时候可以使相同的内容两次发布出来的文件是完全一样的,我们在创建AssetBundle的时候选择上这个参数,那么就可以做差量了。测试数据如下:

没有选择该参数:

http://avocado.oa.com/fconv/files/201406/50d4ecb7750d7d86950a4a14a715e924.files/image005.jpg

选择了该参数后:

http://avocado.oa.com/fconv/files/201406/50d4ecb7750d7d86950a4a14a715e924.files/image006.jpg

10.6关于项目中应用

项目中使用AssetBundle做开发可以使用宏进行隔离,接口封装尽量采用异步接口,通过引用计数cache机制,确定ab的释放时机。大体流程如下:

a、确定加载ab次数(资源数)

b、加载ab

c、成功后根据资源url引用计数减去对应资源数

d、引用计数为0的时候调用AssetBundle的Unload(false)

代码中使用的地方可以通过封装的GetObject获取已经加载的对象,使用完成后可以调用Resources的UnloadUnUsedAssets释放资源。

 

10.7关于AssetBundle的粒度控制

AB的粒度越小,差量更新的冗余就会越小,粒度越大,差量更新的冗余就会越大。但是,并不是说粒度越小就越好,粒度小了,(运行时)加载的时候会增加IO次数、解压次数(AB一般选择压缩格式)和申请内存的次数,导致加载时长变长。因此粒度的控制是一个时间与空间平衡的选择过程。经过实验,大体有一个数据可以用来参考,1M左右的AssetBundle包加载性能最好,冗余也可以接受。

10.8关于AssetBundle的压缩选择

AssetBundle压缩与不压缩的差异主要有两方面:

a、外存(安装包的大小或者安装后占用磁盘空间的大小)

b、加载方式的选择(能否使用同步方法)

这里如果一些对性能要求特别高,资源又不大的AB可以采用非压缩方式,通过CreateFromFile加载AB包,性能最高又不会产生大量的内存。对于其他的资源文件,建议压缩处理,压缩与非压缩在磁盘占用上会有4倍左右的大小差别,如果都采用非压缩格式,有可能会导致你的磁盘占用达到一个非常恐怖的量级。

10.9AssetBundle在外存优化中的应用

安装后的磁盘构成基本上是资源的内存值(Resources目录下资源),举个粒子,一张真彩色的1024*1024的图片放到Resources目录下安装后占用的内存为4M(4*1024*1024)。如果你的游戏是一个2D的,又包含很多的图片资源,这样会使你的安装包在用户机器上安装需要大量的磁盘空间,这里你可以把它们打成AB包放到用户的手机上,这样就磁盘占用就会小很多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unity AssetBundleUnity 引擎中一种用于打包和管理资源的机制。通过使用 AssetBundle,我们可以将资源打包成一个个独立的包,然后在运行时根据需要动态加载和卸载这些资源,从而实现游戏资产的按需加载,节省内存和加载时间。 进阶的实现 AssetBundle 框架包括以下步骤: 1. 资源分类与打包:根据游戏的需求,将资源按照类型进行分类,并使用 Unity 的打包工具将资源打包成 AssetBundle。 2. 资源加载与管理:在游戏运行时,通过 AssetBundle 的加载接口加载需要的资源,可以使用 WWW 或 UnityWebRequest 进行加载,并使用 AssetBundleManifest 来管理加载的 AssetBundle。 3. 资源缓存与卸载:加载并使用资源后,可以将资源缓存在内存中,以便后续快速访问。当资源不再需要时,可以使用 Unload 方法释放资源,以节省内存。 4. 异步加载与加载进度:为了防止资源加载阻塞游戏主线程,可以使用异步加载的方式加载资源,并通过回调函数获取加载进度信息,以便显示加载界面或进度条。 5. 资源更新与热更:在游戏发布后,如果需要更新或替换某些资源,可以使用 AssetBundle 的版本控制机制,根据服务器资源版本信息判断是否需要更新,并进行资源差异化下载和替换。 6. 跨平台适配:Unity AssetBundle 可以在不同平台(如 Windows、iOS、Android)上使用相同的打包和加载方式,因此可以方便地实现跨平台适配。 通过以上步骤,我们可以实现一个简单的 AssetBundle 框架,实现资源的按需加载、缓存、卸载、异步加载和更新等功能。这样可以大幅减少游戏的内存占用和加载时间,提高游戏性能和用户体验。 ### 回答2: Unity assetbundleUnity引擎中用于打包和加载资源的一种方式。在进阶的简单实现assetbundle框架中,我们需要完成以下几个步骤: 1.资源打包:首先,我们需要将需要打包的资源准备好,包括场景、模型、纹理、音效等。然后,使用Unity的AssetBundle API对这些资源进行打包。这一步的关键是给资源设置正确的AssetBundleName和Variant。 2.资源加载:在游戏运行时,我们可以通过AssetBundleManager来加载和管理assetbundle资源。AssetBundleManager是一个自定义的管理器类,使用字典来存储已加载的assetbundle,以便快速获取。 3.动态加载:使用AssetBundleManager加载资源后,可以通过AssetBundleManager提供的接口来实例化、替换、销毁已加载的资源。在需要使用资源的地方,使用Instantiate方法来实例化预制体,或者直接使用LoadAsset方法来加载非预制体资源。加载完成后,可以使用UnityEngine.Object类型来获取具体的资源。 4.资源卸载:当资源不再需要使用时,可以调用AssetBundleManager提供的接口来卸载资源。这样,可以释放内存并减少资源的加载数量。需要注意的是,在卸载资源时,需要考虑其他依赖资源的情况,避免出现因卸载资源而导致其他资源无法使用的问题。 5.异常处理:在加载资源的过程中,可能会出现一些异常情况,例如资源不存在、加载失败等。我们需要在框架中做好异常处理的逻辑,在这些情况下给出合适的提示信息,并采取相应的措施,以保证游戏的正常运行。 综上所述,一个简单的assetbundle框架的实现包括资源打包、加载、动态加载、资源卸载以及异常处理等步骤。这样的实现可以提高游戏的性能和加载速度,减少内存占用,并且便于资源的管理和更新。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值