大家好,又见面了,我是你们的朋友全栈君。
一:AssetBundle介绍
AssetBundle是将资源使用Unity提供的一种用于存储资源的压缩格式打包后的集合,它可以存储任何一种Unity可以识别的资源,如模型,纹理图,音频,场景等资源。也可以加载开发者自定义的二进制文件。他们的文件类型是.assetbundle/.unity3d,他们先前被设计好,很容易就下载到我们的游戏或者场景当中。
一般情况下AssetBundle的具体开发流程如下:
(1)创建Asset bundle,开发者在unity编辑器中通过脚本将所需要的资源打包成AssetBundle文件。
(2)上传服务器。开发者将打包好的AssetBundle文件上传至服务器中。使得游戏客户端能够获取当前的资源,进行游戏的更新。
(3)下载AssetBundle,首先将其下载到本地设备中,然后再通过AsstBudle的加载模块将资源加到游戏之中。
(4)加载,通过Unity提供的API可以加载资源里面包含的模型、纹理图、音频、动画、场景等来更新游戏客户端。
(5)卸载AssetBundle,卸载之后可以节省内存资源,并且要保证资源的正常更新。
二:AssetBundle多平台打包
2.1创建AssetBundle
(1)只有在Asset窗口中的资源才可以打包,我们单击GameObject->Cube,然后在Asset窗口创建一个预设体,命名为cubeasset,讲Cube拖到该预设体上。
(2)单击刚创建的预制件cubeasset,在编辑器界面右下角的属性窗口底部有一个名为”AssetBundle”的创建工具。接下来创建即可,空的可以通过单击菜单选项”New….”来创建,将其命名为”cubebundle”,名称固定为小写,如果使用了大写字母之后,系统会自动转换为小写格式。
2.2 打包AssetBundle
AssetBundle创建之后需要导出,这一个过程就需要编写相应的代码实现,从Unity5.x之后,提供了一套全新简单的API来实现打包功能。大大简化了开发者手动遍历资源自行打包的过程,更加方便快捷。需要在Asset目录下创建Editor目录,表示该脚本是对于编辑器的一个扩展。脚本写完之后,也不需要进行挂载,会自动在Unity的菜单栏中生成。单击子菜单,既可以进行打包AssetBundle。具体实现代码如下:
代码语言:javascript复制using UnityEditor;
public class ExportAssets : MonoBehaviour {
[@MenuItem("Test/Build Asset Bundles")]
static void BuildAssetBundles(){
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath,BuildAssetBundleOptions.UncompressedAssetBundle,BuildTarget.StandaloneOSXUniversal);
}
}
所有的AssetBundle已经被导出,此时每一个AssetBundle资源会有一个和文件相关的Mainfest 的文本类型的文件,该文件提供了所打包资源的CRC和资源依赖的信息。
除此之外,还有一个AssetBundle文件会在生成的时候被创建,记录者整个资源列表以及列表之间的关系。
2.3. AssetBundle的压缩类型
Unity3D引擎为我们提供了三种压缩策略来处理AssetBundle的压缩,即:
- LZMA格式
- LZ4格式
- 不压缩
LZMA格式:
在默认情况下,打包生成的AssetBundle都会被压缩。在U3D中,AssetBundle的标准压缩格式便是LZMA(LZMA是一种序列化流文件),因此在默认情况下,打出的AssetBundle包处于LZMA格式的压缩状态,在使用AssetBundle前需要先解压缩。
使用LZMA格式压缩的AssetBundle的包体积最小(高压缩比),但是相应的会增加解压缩时的时间。
LZ4格式:
Unity 5.3之后的版本增加了LZ4格式压缩,由于LZ4的压缩比一般,因此经过压缩后的AssetBundle包体的体积较大(该算法基于chunk)。但是,使用LZ4格式的好处在于解压缩的时间相对要短。
若要使用LZ4格式压缩,只需要在打包的时候开启 BuildAssetBundleOptions.ChunkBasedCompression即可。
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath,
BuildAssetBundleOptions.ChunkBasedCompression);
不压缩:
当然,我们也可以不对AssetBundle进行压缩。没有经过压缩的包体积最大,但是访问速度最快。
若要使用不压缩的策略,只需要在打包的时候开启BuildAssetBundleOptions.UncompressedAssetBundle即可。
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath,
BuildAssetBundleOptions.UncompressedAssetBundle);
代码语言:javascript复制static void BuildAssetBundlesAndroid(){
Object obj = AssetDatabase.LoadMainAssetAtPath("Assets/Test.png");
BuildPipeline.BuildAssetBundle(obj, null,
Application.streamingAssetsPath "/Test.assetbundle",
BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
| BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.StandaloneWindows);
}
三:AssetBundle资源加载和卸载
3.1 AssetBundle加载
在5.3版本中的新AssetBundle系统中,旧有的一些动态加载API已经被新的API所取代,具体内容如下:
4.x-5.2版本中的AssetBundle.CreateFromFile方法,在5.3版本中变成了AssetBundle.LoadFromFile方法。
4.x-5.2版本中的AssetBundle.CreateFromMemory方法,在5.3版本中变成了LoadFromMemoryAsync方法。
4.x-5.2版本中的AssetBundle.CreateFromMemoryImmediate方法,在5.3版本中变成了LoadFromMemory方法。
因此,本小节之后的内容将使用新版API。
使用AssetBundle动态加载资源首先要获取AssetBundle对象,第二步才是从AssetBundle中加载目标资源。因此本小节将主要关注如何在运行时获取AssetBundle的对象,关于如何从AssetBundle中加载资源将在下一小节中分析。
要在运行时加载AssetBundle对象主要可以分为两大类途径:
- 先获取WWW对象,再通过WWW.assetBundle获取AssetBundle对象
- 直接获取AssetBundle
下面我们就具体分析一下这两种途径:
(1)先获取WWW对象,再通过WWW.assetBundle加载AssetBundle对象:
在先获取WWW对象,在获取AssetBundle的这种方式中,我们可以使用以下两个API来实现这个功能。
- public WWW(string url),直接调用WWW类的构造函数,目标AssetBundle所在的路径作为其参数,构造WWW对象的过程中会加载Bundle文件并返回一个WWW对象,完成后会在内存中创建较大的WebStream(解压后的内容,通常为原Bundle文件的4~5倍大小,纹理资源比例可能更大),因此后续的AssetBundle.LoadAsset可以直接在内存中进行。
- public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0),WWW类的一个静态方法,调用该方法同样会加载Bundle文件同时返回一个WWW对象,和上一个直接调用WWW的构造函数的区别在于该方法会将解压形式的Bundle内容存入磁盘中作为缓存(如果该Bundle已在缓存中,则省去这一步),完成后只会在内存中创建较小的SerializedFile,而后续的AssetBundle.LoadAsset需要通过IO从磁盘中的缓存获取。
● public static AssetBundle CreateFromFile(string path);
通过未压缩的Bundle文件,同步创建AssetBundle对象,这是最快的创建方式。创建完成后只会在内存中创建较小的SerializedFile,而后续的AssetBundle.Load需要通过IO从磁盘中获取。加载时候选择未压缩即可,Unity2017新版本也可以加载压缩的AssetBundle.
● public static AssetBundleCreateRequest CreateFromMemory(byte[] binary);
通过Bundle的二进制数据,异步创建AssetBundle对象。完成后会在内存中创建较大的WebStream。调用时,Bundle的解压是异步进行的,因此对于未压缩的Bundle文件,该接口与CreateFromMemoryImmediate等价。
● public static AssetBundle CreateFromMemoryImmediate(byte[] binary);
该接口是CreateFromMemory的同步版本。
注:5.3下分别改名为LoadFromFile,LoadFromMemory,LoadFromMemoryAsync并增加了LoadFromFileAsync,且机制也有一定的变化,可详见Unity官方文档。
代码语言:javascript复制AssetBundle bundle= AssetBundle.LoadFromFile(Application.streamingAssetsPath "/cube.assetbundle");
Material mat = (Material)bundle.LoadAsset ("Red");
GetComponent<MeshRenderer> ().material = mat;
IEnumerator GetData(){
WWW data = new WWW ("file://" Application.streamingAssetsPath
"/cube.assetbundle");
yield return data;
Material mat = (Material)data.assetBundle.LoadAsset ("Red");
GetComponent<MeshRenderer> ().material = mat;
}
Hash128 hash = new Hash128 ();
WWW data=WWW.LoadFromCacheOrDownload("file://" Application.streamingAssetsPath "/cube.assetbundle",new Hash128(1,1,1,1));
yield return data;
Material mat = (Material)data.assetBundle.LoadAsset ("Red");
GetComponent<MeshRenderer> ().material = mat;
若使用www加载本地AssetBundle则需要在路径前加“file://”即:
“file://” Application.dataPath “/ASB/b.unity3d”
代码语言:javascript复制IEnumerator downloadTexture(string url){
WWW data = new WWW (url);
yield return data;
AssetBundle bundle = AssetBundle.CreateFromMemoryImmediate
(data.bytes);
sphere.GetComponent<Renderer> ().material.mainTexture
= (Texture)bundle.LoadAsset ("01");
bundle.Unload (false);
}
接口对比:new WWW与WWW.LoadFromCacheOrDownload
前者的优势
● 后续的Load操作在内存中进行,相比后者的IO操作开销更小;
● 不形成缓存文件,而后者则需要额外的磁盘空间存放缓存;
● 能通过WWW.texture,WWW.bytes,WWW.audioClip等接口直接加载外部资源,而后者只能用于加载AssetBundle;
前者的劣势
● 每次加载都涉及到解压操作,而后者在第二次加载时就省去了解压的开销;
● 在内存中会有较大的WebStream,而后者在内存中只有通常较小的SerializedFile。(此项为一般情况,但并不绝对,对于序列化信息较多的Prefab,很可能出现SerializedFile比WebStream更大的情况)
我们自己压缩的AssetBundle:
我们自己也可以使用第三方库或工具对生成的AssetBundle包文件进行压缩,如果需要这样做,则我们最好不要再使用Unity3D对AssetBundle进行压缩,因此在打包时选择开启BuildAssetBundleOptions.UncompressedAssetBundle。
在运行时需要加载AssetBundle对象时,使用LoadFromFileAsync方法进行异步加载。
3.2 AssetBundle内部资源加载
新版的AssetBundle中,加载资源的API已经变成了以下的几个:
- LoadAsset:从资源包中加载指定的资源
- LoadAllAsset:加载当前资源包中所有的资源
- LoadAssetAsync:从资源包中异步加载资源
//1 同步加载,根据名称进行加载
Texture mat = (Texture)data.assetBundle.LoadAsset ("1");
GetComponent<MeshRenderer> ().material.mainTexture = mat;
data.assetBundle.Unload (false);
//2同步加载所有的GameObject类型
GameObject[] obj = data.assetBundle.LoadAllAssets<GameObject> ();
//3Async加载,根据名称进行加载
AssetBundleRequest req= data.assetBundle.LoadAssetAsync("1");
yield return req;
if (req.isDone) {
Texture tex = (Texture)req.asset;
}
//4 LoadAllAssets的yibu版本
AssetBundleRequest req1=data.assetBundle.LoadAllAssetsAsync
<GameObject>();
从AssetBundle加载资源的常用API
- public Object LoadAsset(string name, Type type);
通过给定的名字和资源类型,加载资源。加载时会自动加载其依赖的资源,即Load一个Prefab时,会自动Load其引用的Texture资源。
- public Object[] LoadAllAsset(Type type);
一次性加载Bundle中给定资源类型的所有资源。
- public AssetBundleRequest LoadAssetAsync(string name, Type type);
该接口是Load的异步版本。
注:5.x下分别改名为LoadAsset,LoadAllAssets,LoadAssetAsync,并增加了LoadAllAssetsAsync。
资源卸载
资源卸载部分的变化不大,使用的仍然是Unload方法。
该方法会卸载运行时内存中包含在bundle中的所有资源。
当传入的参数为true,则不仅仅内存中的AssetBundle对象包含的资源会被销毁。根据这些资源实例化而来的游戏内的对象也会销毁。
当传入的参数为false,则仅仅销毁内存中的AssetBundle对象包含的资源。
四:AssetBundle服务器下载
我们采用下面的案例,场景进入的时候,依次从服务器给三个已经创建的对象加载纹理,材质,以及根据预设创建一个新的对象。
服务器我们简单的使用python进行搭建,进入到存放asset资源目录下,输入以下命令就可以搭建一个简单的文件服务器:
python -m SimpleHTTPServer 8080
浏览器输入 http://127.0.0.1:8080/ 可以查看搭建的情况。
具体过程如下:
(1)新建一个场景,创建立方体,球体和一个空对象,分别用来测试下载纹理,材质和预设体。
(2)分别将材质和纹理图片,预设体创建成为AssetBundle资源,具体的命名自己把握,后来需要根据名称进行获取。
(3)打包成功之后,将内容添加到对应的服务器之中,下面开始脚本的编写。
(4)新建脚本如下,分别测试三个内容的下载。
代码语言:javascript复制public class DownloadAsset : MonoBehaviour {
public GameObject goCube; //演示修改纹理
public GameObject goSphere;//演示修改材质
public Transform newPosition;//演示根据预设体创建对象
//url=IP 文件名
private string url1="http://127.0.0.1:8080/image";
private string url2="http://127.0.0.1:8080/mater";
private string url3="http://127.0.0.1:8080/cubebundle";
// Use this for initialization
void Start () {
//downTexture ();
downMat ();
//downPrefab ();
}
void downTexture()
{
StartCoroutine (download_Texture(url1));
}
IEnumerator download_Texture(string url){
WWW downAsset = new WWW (url);
yield return downAsset;
goCube.GetComponent<Renderer> ().material.mainTexture
= (Texture)downAsset.assetBundle.LoadAsset ("01");
downAsset.assetBundle.Unload (false);
}
void downMat(){
StartCoroutine (download_Mat(url2));
}
IEnumerator download_Mat(string url){
WWW downAsset = new WWW (url);
yield return downAsset;
goCube.GetComponent<Renderer> ().material
= (Material)downAsset.assetBundle.LoadAsset
("Red");
downAsset.assetBundle.Unload (false);
}
void downPrefab(){
StartCoroutine (download_Prefab(url3));
}
IEnumerator download_Prefab(string path){
WWW downloadAsset = new WWW (path);
yield return downloadAsset;
GameObject gpPrefabs = (GameObject)Instantiate
(downloadAsset.assetBundle.LoadAsset("Cube"));
gpPrefabs.GetComponent<Renderer> ().material.color =
Color.yellow;
gpPrefabs.transform.position =
newPosition.transform.position;
downloadAsset.assetBundle.Unload (false);
downloadAsset.Dispose ();
当然,我们这里也可以使用异步加载的方式加载AssetBundle的资源,如下所示:
代码语言:javascript复制 AssetBundle bundle = downloadAsset.assetBundle;
AssetBundleRequest request = bundle.LoadAssetAsync
("Cube",typeof(GameObject));
yield return request;
GameObject gpPrefabs = (GameObject)Instantiate
(request.asset);
gpPrefabs.GetComponent<Renderer> ().material.color = Color.yellow;
gpPrefabs.transform.position =newPosition.transform.position;
downloadAsset.assetBundle.Unload (false);
downloadAsset.Dispose ();
五:AssetBundle原理分析
5.1 AssetBundle加载
当AssetBundle 解压加载到内存之后,我们可以通过WWW.assetbundle属性获得AssetBundle对象(上图的粉色框部分)来得到各个Assets,并对这些Assets进行加载或者实例化操作。
在加载过程中,unity会将AssetBundle中的数据流转变成unity可识别的信息类型,如:材质、纹理等。加载完成之后,我们就可以对其进行更多操作了,如:对象的实例化、材质复用、纹理替换等等。
5.2 AssetBundle及Assets的卸载
在AssetBundle的下载和加载过程中,以及Assets加载和实例化过程中,AssetBundle以及加载的Assets都会占用内存。
(1)AssetBundle的卸载采用Assetbundle.Unload(bool)接口。
(2)Assets的卸载有两种方式:
①.Assetbundle.Unload(true)。这会强制卸载掉所有从AssetBundle加载的Assets。
②.Resource.UnloadUnusedAssets()和 Resources.UnloadAsset。这会卸载掉所有没有用到的Assets。需要注意的是,该接口作用于整个系统,而不仅仅是当前的AssetBundle,而且不会卸载从当前AssetBundle文件中加载并仍在使用的Assets。
(3)对于实例化出来的对象,可以使用GameObject.Destroy或GameObject.DestroyImmediate。注意的是:官方说法是这样的,如果使用GameObject.Destroy接口,unity会将真正的删除操作延后到一个合适的时机统一进行处理,但会在渲染之前。
WWW对象和WWW.asssetbundle加载的AssetBundle对象都会对Web Stream数据持有引用。AssetBundle对象同时也会引用到从它加载的所有Assets。按照官方说法,真正的数据都是存放在Web Stream数据中(如纹理、模型),而WWW和AssetBundle对象只是一个结构指向了Web Stream数据。
对于WWW对象,可以使用www=null或www.dispose来卸载。这两者是有区别的,www=null不会立即释放内存,而是系统的自动回收机制启动时回收。www.dispose则会立即调用系统的回收机制来释放内存。当WWW对象被释放后,其对于Web Stream数据的引用计数也会相应减1。
对于Web Stream数据,它所占用的内存会在其引用计数为0时,被系统自动回收。例如:当上图中的AssetBundle对象和WWW对象被释放后,Web Stream数据所占内存也会被系统自动回收。
六:AssetBundle依赖加载
如果一个或者多个 UnityEngine.Objects 引用了其他 AssetBundle 中的 UnityEngine.Object,那么 AssetBundle 之间就产生的依赖关系。相反,如果 UnityEngine.ObjectA 所引用的UnityEngine.ObjectB 不是其他 AssetBundle 中的,那么依赖就不会产生。
假若这样(指的是前面两个例子的后者,既不产生依赖的情况),被依赖对象(UnityEngine.ObjectB)将被拷贝进你创建的 AssetBundle(指包含 UnityEngine.ObjectA 的 AssetBundle)。
更进一步,如果有多个对象(UnityEngine.ObjectA1、UnityEngine.ObjectA2、UnityEngine.ObjectA3……)引用了同一个被依赖对象(UnityEngine.ObjectB),那么被依赖对象将被拷贝多份,打包进各个对象各自的 AssetBundle。
如果一个 AssetBundle 存在依赖性,那么要注意的是,那些包含了被依赖对象的 AssetBundles,需要在你想要实例化的对象的加载之前加载。Unity 不会自动帮你加载这些依赖。
想想看下面的例子, Bundle1 中的一个材质(Material)引用了 Bundle2 中的一个纹理(Texture):
在这个例子中,在从 Bundle1 中加载材质前,你需要先将 Bundle2 加载到内存中。你按照什么顺序加载 Bundle1 和 Bundle2 并不重要,重要的是,想从 Bundle1 中加载材质前,你需要先加载 Bundle2。
(1)AssetBundle.LoadFromMemoryAsync
这个方法的参数是一个包含了 AssetBundle 数据的字节数组。如果需要的话,你还可以传入一个 CRC(循环冗余校验码) 参数。如果 AssetBundle 使用 LZMA 算法压缩,那么 AssetBundle 在加载的时候会被解压。如果 AssetBundle 使用 LZ4 算法压缩,它将直接以压缩形式被加载。
代码语言:javascript复制IEnumerator LoadFromMemoryAsync(string path)
{
AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
yield return createRequest;
AssetBundle bundle = createRequest.assetBundle;
var prefab = bundle.LoadAsset.<GameObject>("MyObject");
Instantiate(prefab);
}
AssetBundle.LoadFromFile
这个 API 在加载本地存储的未压缩 AssetBundle 时具有很高效率。如果 AssetBundle 是未压缩,或者是数据块形式(LZ4 算法压缩)的,LoadFromFile 将从磁盘中直接加载它。如果 AssetBundle 是高度压缩(LZMA 算法压缩)的,在将它加载进入内存前,会首先将它解压。
下面是一个如何使用这个方法的例子:
代码语言:javascript复制public class LoadFromFileExample extends MonoBehaviour
{
function Start() {
var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
if (myLoadedAssetBundle == null) {
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject");
Instantiate(prefab);
}
}
注意:在安卓设备上,如果 Unity 是5.3或者更老的版本,这个方法在读取资源流路径(Streaming Assets path)的时候会失败。这是因为那个路径是在一个 .jar 文件的内部。Unity5.4 以及更高的版本没有这个问题,可以正常的读取资源流。
WWW.LoadFromCacheOrDownload
这个 API 已经被废弃(建议使用 UnityWebRequest)(三思:这句话不是我加的,官方文档中就是有这句话)
这个 API 对于从远程服务器加载 AssetBundles,或者加载本地 AssetBundles 都很有用。这个 API 是 UnityWebRequest 不尽如人意的老版本。
从远程服务器加载的 AssetBundle 将会被自动缓存。如果 AssetBundle 是压缩形式的,一个工作线程将加速解压这个 AssetBundle 并写入缓存。一旦一个 AssetBundle 已经被解压且被缓存,它将完全像使用 AssetBundle.LoadFromFile 方法一样被加载。
下面是一个如何使用这个方法的例子:
代码语言:javascript复制using UnityEngine;
using System.Collections;
public class LoadFromCacheOrDownloadExample : MonoBehaviour
{
IEnumerator Start ()
{
while (!Caching.ready)
yield return null;
var www = WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle", 5);
yield return www;
if(!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
yield return;
}
var myLoadedAssetBundle = www.assetBundle;
var asset = myLoadedAssetBundle.mainAsset;
}
}
由于缓存 AssetBundle 字节数据的开销较大,建议所有开发者在使用 WWW.LoadFromCacheOrDownload 方法时,确保 AssetBundles 都比较小——最多几兆字节。同样建议所有开发者在内存比较有限的平台(比如移动设备)上使用这个方法时,确保同时只下载一个 AssetBundle,防止内存泄漏。
如果缓存文件夹没有足够的空间来缓存额外的文件,LoadFromCacheOrDownload 将会从缓存中迭代删除最近最少使用的 AssetBundles,直到有足够的空间来存储新的 AssetBundle。如果空间还是不够(比如硬盘满了,或者所有缓存的文件都正在被使用),LoadFromCacheOrDownload() 将绕开缓存,直接将文件以流的形式存进内存。
如果想要使用 LoadFromCacheOrDownload 的版本变量,方法参数(第二个参数)需要改变。如果参数与当前缓存的 AssetBundle 的版本变量一致,那么就可以从缓存中加载这个 AssetBundle。
UnityWebRequest
UnityWebRequest 有个专门的 API 来处理 AssetBundles。首先,你需要使用 UnityWebRequest.GetAssetBundle 方法来创建你的 web 请求。在请求返回后,将请求放入 DownloadHandlerAssetBundle.GetContent(UnityWebRequest) 作为参数。GetContent 方法将返回你的 AssetBundle 对象。
在下载完 AssetBundle 后,你同样可以使用 DownloadHandlerAssetBundle 类的 assetBundle 属性来加载 AssetBundle,这就和使用 AssetBundle.LoadFromFile 方法一样高效。
下面有个例子展示:如何加载一个包含两个 GameObjects 的 AssetBundle,并实例化它们。想要运行这段程序,我们只需要调用 StartCoroutine(InstantiateObject()) 方法:
代码语言:javascript复制IEnumerator InstantiateObject()
{
string uri = "file:///" Application.dataPath "/AssetBundles/" assetBundleName;
UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject cube = bundle.LoadAsset<GameObject>("Cube");
GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
Instantiate(cube);
Instantiate(sprite);
}
使用 UnityWebRequest 的优点是,它允许开发者用更灵活的方式来处理下载的数据,并且潜在地排除了不必要的内存占用。和 UnityEngine.WWW 类相比,这是更现代,也更推荐的 API。
从 AssetBundles 中加载资源
现在,你已经成功下载了你的 AssetBundle,是时候从中加载一些资源。
通常的代码片段:
T objectFromBundle = bundleObject.LoadAsset<T>(assetName);
T 是你想加载的资源类型。
当你决定如何加载资源的时候,有一对方法供使用。我们可以使用 LoadAsset、LoadAllAssets 方法,以及与它们对应的异步方法: LoadAssetAsync、LoadAllAssetsAsync。
下面是一个从一个 AssetBundle 中同步加载资源的例子:
加载一个 GameObject:
GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);
加载所有资源:
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();
现在,和上面展示的方法(要么返回你正在加载的对象,要么返回一组对象)不同的是,异步方法返回的是一个 AssetBundleRequest。
在可以使用资源前,你需要等待处理完成。如下:
代码语言:javascript复制AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;
以及:
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;
一旦你已经加载好你的资源,是时候行动了!你可以像使用 Unity 中的其他对象一样使用加载的对象。
加载 AssetBundle Manifests(资源清单)
加载 AssetBundle manifests 非常的有用。尤其是当处理 AssetBundle 依赖关系的时候。为了获取可以使用的 AssetBundleManifest,你需要加载一个额外的 AssetBundle(即那个和文件夹名称相同的文件),并且从中加载出一个 AssetBundleManifest 类型的对象。从 AssetBundle 中加载 manifest 完全和从中加载其他资源一样,如下:
代码语言:javascript复制AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
现在,你可以通过上面例子获取到的 manifest 对象来使用 AssetBundleManifest 类的 API。从现在开始,你可以使用这个 manifest 来获取关于 AssetBundle 的信息,包括:依赖数据、hash 数据,以及版本变量数据。
还记得前面章节我们讨论过的,如果一个 bundleA 对 bundleB 有依赖,那么在从 bundleA 中加载任何资源之前,我们需要先加载 bundleB 吗?Manifest 对象就使得动态查找正在加载的依赖关系成为可能。比如我们想要加载一个名叫“assetBundle”的 AssetBundle 的所有依赖:
代码语言:javascript复制AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies(“assetBundle");
foreach(string dependency in dependencies)
{
AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}
现在,你已经加载了 AssetBundle、AssetBundle 依赖,以及其他资源,是时候讨论如何管理这些加载好的 AssetBundles 了。
在 Objects 被从场景中移除的时候,Unity 不会自动将它们卸载。资源的清理是在某个特定时机被触发,当然也可以手动触发。
知道什么时候加载和卸载一个 AssetBundle 很重要。不合时宜的卸载 AssetBundle 可能导致重复对象(duplicating objects)错误,或者其他未预料到的情况,比如纹理丢失
理解如何管理 AssetBundle 最重要的事是什么时候调用 AssetBundle.Unload(bool) 方法,以及该方法的参数应该传入 true 还是 false。该方法卸载 AssetBundle 的头信息;方法参数决定了是否同时卸载从 AssetBundle 中加载并实例化的所有 Objects。
如果你传入 true 参数,那么你从 AssetBundle 中加载的所有对象将被卸载,即便这些对象正在被使用。这就是我们前面提到的,导致纹理丢失的原因。
假设 Material M 是从 AssetBundle AB 中加载的,如下:
如果 AB.Unload(true) 被调用,那么任何使用 Material M 的实例都将被卸载并消除,即便它们正在场景中被使用。
如果 AB.Unload(false) 被调用,那么将切断所有使用 Material M 的实例与 AssetBundle AB 的联系。
如果 AssetBundle AB 在被卸载后不久再次被加载,Unity 并不会将已经存在的使用 Material M 的实例与 AssetBundle AB 重新联系。因此将存在两份被加载的 Material M。
通常情况下,使用 AssetBundle.Unload(false) 不会获得理想情况。大多数项目应该使用 AssetBundle.Unload(true) 方法,以避免内存中出现重复对象(duplicating objects)。
大多数项目应该使用 AssetBundle.Unload(true) 方法,并且要采取措施确保没有重复对象。两种通常采取的措施如下:
在应用的生命周期中找到合适的时机来卸载 AssetBundle,比如关卡之间,或者加载场景的时候。
为每个对象采取引用计数管理方法,只有当 AssetBundle 的所有对象都没有被使用的时候,再卸载 AssetBundle。这样就可以避免应用出现重复对象的问题。
如果应用必须使用 AssetBundle.Unload(false) 方法,对象将只能在以下两种情况下被卸载:
消除对象的所有引用,包括场景中的和代码中的。之后,调用
Resources.UnloadUnusedAssets。
没有额外附加特性地加载一个场景。这将消除当前场景的所有对象,并自动调用 Resources.UnloadUnusedAssets。
如果你不想自己管理加载的 AssetBundle、依赖关系,以及资源,你可能需要使用 AssetBundle 管理器。(AssetBundle Manager,下一章节将介绍。)
代码语言:javascript复制public class LoadAssetBundle : MonoBehaviour {
public string manifestFilePath;
// Use this for initialization
void Start () {
manifestFilePath = Application.streamingAssetsPath "/StreamingAssets";
}
void GetData(){
AssetBundle assetBundle = AssetBundle.CreateFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies(“mater.assetbundle");
foreach(string dependency in dependencies)
{
AssetBundle.CreateFromFile(Application.streamingAssetsPath "/" dependency);
}
AssetBundle bundle= AssetBundle.CreateFromFile
(Application.streamingAssetsPath "/mater.assetbundle");
Material mat = (Material)bundle.LoadAsset ("Red");
GetComponent<MeshRenderer> ().material = mat;
}
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/149092.html原文链接:https://javaforall.cn