Unity3D4.x之AssetBundle学习笔记

关于AssetBundle

AssetBundle可用来将多个资源打包为一个文件,实现动态下载和更新。需要注意的是Unity3D5.x以后对打包方式进行了升级,不用再在依赖关系上伤透脑筋,但是和4.x的版本不再兼容,不过我的这篇笔记是基于4.x的。

打包资源

Unity对AssetBundle仅提供了代码方面的支持,并没有一个菜单或窗口可以直接进行操作,不过这样却给了我们最大的开放度来进行资源打包。

我们看看打包的代码:

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

mainAsset:主资源,可以通过assetBundle.mainAsset直接获取,如果没有填null;

assets:打包的所有资源,可以通过assetBundle.Load获取,如果没有填null;

pathName:打包后的资源文件路径;

options:打包选项

  • BuildAssetBundleOptions.CollectDependencies:搜集所有依赖项,将依赖项打包到资源中;
  • BuildAssetBundleOptions.CompleteAssets:完全打包资源,如果传递网格它还将包括游戏物体和任意动画剪辑;
  • BuildAssetBundleOptions.DeterministicAssetBundle:编译资源包使用一个哈希表储存对象ID在资源包中;
  • BuildAssetBundleOptions.DisableWriteTypeTree:在资源包不包含类型信息;
  • BuildAssetBundleOptions.UncompressedAssetBundle:不压缩AssetBundle,默认会进行压缩;

targetPlatform:打包资源的目标平台,不同平台的资源是不通用的,需要额外打包。

其它的打包方法

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

可以为每个资源指定特殊的名称。

static function BuildPlayer (levels : string[], locationPathName : string, target : BuildTarget, options : BuildOptions) : string

生成一个流unity3d文件。这包含一个场景,可以按需下载和加载,一旦它的资源包已被加载。

static function BuildStreamedSceneAssetBundle (levels : string[], locationPath : String, target : BuildTarget) : String

编译一个或多个场景和所有它依赖的压缩资源包。

打包脚本

这里提供一个可以进行简单打包的脚本:

 1 using UnityEditor;
 2 using UnityEngine;
 3
 4 /// <summary>
 5 ///
 6 /// 简单的 AssetBundle 创建类.
 7 ///
 8 /// 解决问题:
 9 /// 实现简单的资源打包.
10 ///
11 /// 使用方法及步骤:
12 /// 1.修改要打包到的目标平台枚举;
13 /// 2.选中要打包的文件在菜单栏点击对应的功能菜单.
14 ///
15 /// </summary>
16 public class SimpleCreateAssetBundle
17 {
18     /// <summary>
19     /// 打包的目标平台.
20     /// </summary>
21     private const BuildTarget BUILD_TARGET = BuildTarget.StandaloneWindows64;
22
23     /// <summary>
24     /// 将选定的一个对象进行打包, 同时包含依赖项, 可通过 AssetBundle 的 main 属性获取.
25     /// </summary>
26     [MenuItem("Hammerc/AssetBundle/CreateSingleAssetBundle")]
27     private static void CreateSingleAssetBundle()
28     {
29         if(Selection.activeObject != null)
30         {
31             //显示保存窗口
32             string path = EditorUtility.SaveFilePanel("Create Single AssetBundle:", "", "New AssetBundle", "unity3d");
33
34             if(path.Length > 0)
35             {
36                 //打包
37                 BuildPipeline.BuildAssetBundle(Selection.activeObject, null, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BUILD_TARGET);
38             }
39         }
40     }
41
42     /// <summary>
43     /// 将选定的多个对象进行打包, 同时包含依赖项, 不指定 AssetBundle 的 main 属性获取.
44     /// </summary>
45     [MenuItem("Hammerc/AssetBundle/CreateMultipleAssetBundle")]
46     private static void CreateMultipleAssetBundle()
47     {
48         if(Selection.objects.Length > 0)
49         {
50             //显示保存窗口
51             string path = EditorUtility.SaveFilePanel("Create Multiple AssetBundle:", "", "New AssetBundle", "unity3d");
52
53             if(path.Length > 0)
54             {
55                 //打包
56                 BuildPipeline.BuildAssetBundle(null, Selection.objects, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BUILD_TARGET);
57             }
58         }
59     }
60 }

加载和测试AssetBundle

新建一个工程,我添加了一个Cube的预制件(MyBox),Cube添加了一个材质对象(MyBoxMaterial)和一个简单的用来移动的脚本对象(MyBoxScript)。

下面选中MyBox后使用上面脚本的CreateSingleAssetBundle打包为StreamingAssets文件夹下的SingleAssetBundle.unity3d文件。

我们添加一个脚本SceneScript到场景的摄像机上。

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class SceneScript : MonoBehaviour
 5 {
 6     void Start()
 7     {
 8         this.StartCoroutine(LoadAssetBundle());
 9     }
10
11     IEnumerator LoadAssetBundle()
12     {
13         //格式化路径
14         string url = string.Format("file:///{0}/{1}", Application.streamingAssetsPath, "SingleAssetBundle.unity3d");
15
16         //开始加载
17         WWW www = new WWW(url);
18         yield return www;
19
20         //加载失败
21         if(www.error != null)
22         {
23             Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
24             yield break;
25         }
26
27         AssetBundle bundle = www.assetBundle;
28
29         //这里打上断点可以查看 bundle 中的所有对象
30         Object[] objs = bundle.LoadAll();
31
32         //获取资源, 注意该资源不能直接使用
33         GameObject go = bundle.mainAsset as GameObject;
34         //创建实例并添加到场景
35         Instantiate(go);
36
37         //销毁资源但保留已经实例化的资源
38         bundle.Unload(false);
39     }
40 }

运行可以发现小盒子已经被添加到场景中了,我们打个断点看看AssetBundle中都有啥:

我们发现和MyBox关联的对象都一道被打包进去了。

下面我添加了一个球型的预制件(MySphere),同时将已有的材质(MyBoxMaterial)和脚本(MyBoxScript)都添加到该预制件中。

选中MyBox和MySphere后使用CreateMultipleAssetBundle打包为StreamingAssets文件夹下的MultipleAssetBundle.unity3d文件。

我们修改一下SceneScript脚本,分别加载一下这两个对象。

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class SceneScript : MonoBehaviour
 5 {
 6     void Start()
 7     {
 8         this.StartCoroutine(LoadAssetBundle());
 9     }
10
11     IEnumerator LoadAssetBundle()
12     {
13         //格式化路径
14         string url = string.Format("file:///{0}/{1}", Application.streamingAssetsPath, "MultipleAssetBundle.unity3d");
15
16         //开始加载
17         WWW www = new WWW(url);
18         yield return www;
19
20         //加载失败
21         if(www.error != null)
22         {
23             Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
24             yield break;
25         }
26
27         AssetBundle bundle = www.assetBundle;
28
29         //这里打上断点可以查看 bundle 中的所有对象
30         Object[] objs = bundle.LoadAll();
31
32         //获取资源, 注意该资源不能直接使用
33         GameObject go1 = bundle.Load("MyBox", typeof(GameObject)) as GameObject;
34         //创建实例并添加到场景
35         Instantiate(go1, new Vector3(3, 0, 0), Quaternion.identity);
36
37         //获取资源, 注意该资源不能直接使用
38         GameObject go2 = bundle.Load("MySphere", typeof(GameObject)) as GameObject;
39         //创建实例并添加到场景
40         Instantiate(go2, new Vector3(-3, 0, 0), Quaternion.identity);
41
42         //销毁资源但保留已经实例化的资源
43         bundle.Unload(false);
44     }
45 }

我们接着打个断点看看究竟打包了啥到AssetBundle中:

总结一下:

  1. 打包到AssetBundle的资源仅保留名称,路径和后缀都会被去掉;
  2. 每个AssetBundle中可能会出现多个同名对象(但类型会不同),所以使用Load加载时务必加上类型以减少不必要的麻烦;
  3. 空字符串一般都是一个引用类型(ReferenceData),之所以搞成空字符串是为了不让我们可以取到?如下图所示:

通过名称可以发现是脚本(MyBoxScript)的一个引用类型,难道脚本不一起打包而只是用引用引用一下?我们做一个测试一看便知。

关于AssetBundle中的脚本

我们保留之前打包出的AssetBundle文件,将Asset文件夹中的MyBoxScript文件删除,再次运行,会发现脚本提供的功能没有了,同时会报错,如下:

说明脚本文件不会打包到AssetBundle文件中,仅仅是保留一个引用而已。

关于这个问题,我在网上找到了一种比较合理的解释:Unity不允许将脚本打包到AssetBundle中,可以考虑直接将脚本放到工程的Resources下面,编译到主程序里面,在AssetBundle中引用即可。

依赖关系

一般在实际开发中,我们会将资源进行分类打包,下面引用下文章Unity AssetBundle爬坑手记的内容。

在打包的时候,我们需要对包的大小和数量进行一个平衡,所有资源打成一个包,一个资源打一个包,都是比较极端的做法,他们的问题也很明显,更多情况下我们需要灵活地将他们组合起来

打成一个包的缺点是加载了这个包,我们不需要的东西也会被加载进来,占用额外内存,而且不利于热更新

打成多个包的缺点是,容易造成冗余,首先影响包的读取速度,然后包之间的内容可能会有重复,且太多的包不利于资源管理

哪些模块打成一个包,哪些模块打成多个包,需要根据实际情况来,例如游戏中每个怪物都需要打成一个包,因为每个怪物之间是独立的,例如游戏的基础UI,可以打成一个包,因为他们在各个界面都会出现

PS.想打包进AssetBundle中的二进制文件,文件名的后缀必须为“.bytes”

那么打成多个包就会出现数据冗余的问题,比如按我们上面的示例来看,如果我要将MyBox和MySphere分别打成两个包,那么相同的数据MyBoxMaterial和MyBoxScript在两个包中都会存在,出现了多余不必要的数据。

解决这个问题的办法就是将公共部分打包成一个资源,然后让MyBox和MySphere分别都依赖这个公共包,这样就不会出现数据冗余的问题

我们使用BuildPipeline.PushAssetDependencies()和BuildPipeline.PopAssetDependencies()来开启Bundle之间的依赖关系,当我们调用PushAssetDependencies之后,会开启依赖模式,当我们依次打包 A B C时,如果A包含了B的资源,B就不会再包含这个资源,而是直接依赖A的,如果A和B包含了C的资源,那么C的这个资源旧不会被打包进去,而是依赖A和B。这时候只要有同样的资源,就会向前依赖,当我们希望,B和C依赖A,但B和C之间不互相依赖,就需要嵌套Push Pop了,当我们调用PopAssetDependencies就会结束依赖

打包的代码如下(有详尽的注释):

 1 using UnityEditor;
 2 using UnityEngine;
 3
 4 /// <summary>
 5 /// 数据打包脚本.
 6 /// </summary>
 7 public class CreateAssetBundle
 8 {
 9     /// <summary>
10     /// 打包的目标平台.
11     /// </summary>
12     private const BuildTarget BUILD_TARGET = BuildTarget.StandaloneWindows64;
13
14     /// <summary>
15     /// 使用依赖分别打包出公共数据资源, 小盒资源和小球资源.
16     /// </summary>
17     [MenuItem("Hammerc/AssetBundle/CreateAssetBundleFile")]
18     private static void CreateAssetBundleFile()
19     {
20         //压入依赖项
21         BuildPipeline.PushAssetDependencies();
22
23         //先打包共同数据, 第一个打包的数据会全部进行打包
24         Object[] assets = new[]
25         {
26             AssetDatabase.LoadMainAssetAtPath("Assets/Res/Material/MyBoxMaterial.mat"),
27             AssetDatabase.LoadMainAssetAtPath("Assets/Res/Script/MyBoxScript.cs"),
28         };
29         BuildPipeline.BuildAssetBundle(null, assets, Application.streamingAssetsPath + "/Common.unity3d", BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle, BUILD_TARGET);
30
31         //再次压入依赖项, 仍然依赖上一层的依赖项
32         BuildPipeline.PushAssetDependencies();
33
34         //打包小盒资源, 该资源会依赖于 Common.unity3d
35         assets = new[]
36         {
37             AssetDatabase.LoadMainAssetAtPath("Assets/Res/MyBox.prefab"),
38         };
39         BuildPipeline.BuildAssetBundle(null, assets, Application.streamingAssetsPath + "/MyBox.unity3d", BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle, BUILD_TARGET);
40
41         //弹出依赖项, 表明下面的打包不会依赖 MyBox.unity3d 但会继续依赖 Common.unity3d
42         BuildPipeline.PopAssetDependencies();
43
44         //打包小球资源, 该资源会依赖于 Common.unity3d
45         assets = new[]
46         {
47             AssetDatabase.LoadMainAssetAtPath("Assets/Res/MySphere.prefab"),
48         };
49         BuildPipeline.BuildAssetBundle(null, assets, Application.streamingAssetsPath + "/MySphere.unity3d", BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle, BUILD_TARGET);
50
51         //弹出依赖项, 所有依赖都弹出, 如果接下来继续打包资源则不会依赖任何已经打包的资源
52         BuildPipeline.PopAssetDependencies();
53     }
54 }

我们只要点击菜单栏对应的菜单项就能生成3个打包后的资源文件,分别是Common.unity3d、MyBox.unity3d和MySphere.unity3d。

加载资源

当我们的Bundle之间有了依赖之后,就不能像前面那样简单地直接Load对应的Bundle了,我们需要把Bundle所依赖的Bundle先加载进来,这个加载只是WWW或者LoadFromCacheOrDownload,并不需要对这个Bundle进行Load,如果BundleB依赖BundleA,当我们要加载BundleB的资源时,假设BundleA没有被加载进来,或者已经被Unload了,那么BundleB依赖BundleA的部分就会丢失,例如每个正方体上都挂着一个脚本,当我们不嵌套Push Pop时,单个正方体的Bundle没有被加载或者已经被卸载,我们加载的那组正方体上的脚本就会丢失,脚本也是一种资源,当一个脚本已经被打包了,依赖这个包的资源,就不会被再打进去

下面我们创建一个SceneScript2的脚本挂到场景的摄像机上,脚本如下:

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class SceneScript2 : MonoBehaviour
 5 {
 6     void Start()
 7     {
 8         this.StartCoroutine(LoadAssetBundle());
 9     }
10
11     IEnumerator LoadAssetBundle()
12     {
13         // ----- 公共数据 -----
14
15         //格式化路径
16         string url = string.Format("file:///{0}/{1}", Application.streamingAssetsPath, "Common.unity3d");
17
18         //开始加载
19         WWW www = new WWW(url);
20         yield return www;
21
22         //加载失败
23         if (www.error != null)
24         {
25             Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
26             yield break;
27         }
28
29         AssetBundle bundle = www.assetBundle;
30         AssetBundle commonBundle = bundle;
31
32         //这里打上断点可以查看 Common 中的所有对象
33         Object[] objs = bundle.LoadAll();
34
35         // ----- MyBox -----
36
37         //格式化路径
38         url = string.Format("file:///{0}/{1}", Application.streamingAssetsPath, "MyBox.unity3d");
39
40         //开始加载
41         www = new WWW(url);
42         yield return www;
43
44         //加载失败
45         if (www.error != null)
46         {
47             Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
48             yield break;
49         }
50
51         bundle = www.assetBundle;
52
53         //这里打上断点可以查看 MyBox 中的所有对象
54         objs = bundle.LoadAll();
55
56         //获取资源, 注意该资源不能直接使用
57         GameObject go1 = bundle.Load("MyBox", typeof(GameObject)) as GameObject;
58         //创建实例并添加到场景
59         Instantiate(go1, new Vector3(3, 0, 0), Quaternion.identity);
60
61         //销毁资源但保留已经实例化的资源
62         bundle.Unload(false);
63
64         // ----- MySphere -----
65
66         //格式化路径
67         url = string.Format("file:///{0}/{1}", Application.streamingAssetsPath, "MySphere.unity3d");
68
69         //开始加载
70         www = new WWW(url);
71         yield return www;
72
73         //加载失败
74         if (www.error != null)
75         {
76             Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
77             yield break;
78         }
79
80         bundle = www.assetBundle;
81
82         //这里打上断点可以查看 MyBox 中的所有对象
83         objs = bundle.LoadAll();
84
85         //获取资源, 注意该资源不能直接使用
86         GameObject go2 = bundle.Load("MySphere", typeof(GameObject)) as GameObject;
87         //创建实例并添加到场景
88         Instantiate(go2, new Vector3(-3, 0, 0), Quaternion.identity);
89
90         //销毁资源但保留已经实例化的资源
91         bundle.Unload(false);
92
93         //销毁公共资源但保留已经实例化的资源
94         commonBundle.Unload(false);
95     }
96 }

运行一下会看见两个对象都出现并正常运行了,我们接下来看看每个资源的数据情况:

公共资源:

MyBox:

MySphere:

看起来好像公共数据也被打包到MyBox和MySphere资源中了,其实没有,如果我们把公共数据的加载去掉,在运行时会出现丢失数据的报错。

更新依赖

在打包的时候我们需要指定BuildAssetBundleOptions.DeterministicAssetBundle选项,这个选项会为每个资源生成一个唯一的ID,当这个资源被重新打包的时候,确定这个ID不会改变,包的依赖是根据这个ID来的,使用这个选项的好处是,当资源需要更新时,依赖于该资源的其他资源,不需要重新打包

A -> B -> C

当A依赖B依赖C时,B更新,需要重新打包C,B,而A不需要动,打包C的原因是,因为B依赖于C,如果不打包C,直接打包B,那么C的资源就会被重复打包,而且B和C的依赖关系也会断掉

内存及其它

写不出新花样了,大家直接点这里吧:Unity AssetBundle爬坑手记

最后提供一下工程文件:http://pan.baidu.com/s/1jGDzshS

时间: 2024-10-25 19:07:04

Unity3D4.x之AssetBundle学习笔记的相关文章

Unity3D之UGUI学习笔记(一):UGUI介绍以及Canvas

UGUI是Unity3D4.6官方提供的UI系统,支持2D和3D UI的开发. Unity3D UI史 OnGUI 在Unity4.6之前,官方提供的是OnGUI函数来开发UI界面,当然问题也比较多,首先不支持可视化开发,其次UI始终位于所有3D对象的上方,无法实现在UI上添加3D模型的效果. 现在一般这套系统多用来在Unity编辑器中开发界面或者快速搭建一些调试界面时使用. NGUI 大名鼎鼎的NGUI是可以看做是开发Unity游戏必备的插件,支持可视化开发,同时也支持2D和3D UI的开发,

CSLight学习笔记结合NGUI

 这两天一直在研究CSLight,目前Unity热更新的方式有两种,一种是ulua这个网上的例子已经很多了.还有一种就是CSLight.其实我更希望CSLight可以趋向成熟,因为它的语法就是C#,但是有些C#的标准语法用不了.这两天我学习的做了一个例子,也把我遇到的坑记录一下.结合上章NGUI研究之李剑英的CSLight入门指南结合NGUI热更需要的朋友可以看下. 1.在github上下载CSLight,当我把DLL拖进项目的时候会报错.原因是CSLight的dll和NGUI的冲突了,所以

vector 学习笔记

vector 使用练习: /**************************************** * File Name: vector.cpp * Author: sky0917 * Created Time: 2014年04月27日 11:07:33 ****************************************/ #include <iostream> #include <vector> using namespace std; int main

Caliburn.Micro学习笔记(一)----引导类和命名匹配规则

Caliburn.Micro学习笔记(一)----引导类和命名匹配规则 用了几天时间看了一下开源框架Caliburn.Micro 这是他源码的地址http://caliburnmicro.codeplex.com/ 文档也写的很详细,自己在看它的文档和代码时写了一些demo和笔记,还有它实现的原理记录一下 学习Caliburn.Micro要有MEF和MVVM的基础 先说一下他的命名规则和引导类 以后我会把Caliburn.Micro的 Actions IResult,IHandle ICondu

jQuery学习笔记(一):入门

jQuery学习笔记(一):入门 一.JQuery是什么 JQuery是什么?始终是萦绕在我心中的一个问题: 借鉴网上同学们的总结,可以从以下几个方面观察. 不使用JQuery时获取DOM文本的操作如下: 1 document.getElementById('info').value = 'Hello World!'; 使用JQuery时获取DOM文本操作如下: 1 $('#info').val('Hello World!'); 嗯,可以看出,使用JQuery的优势之一是可以使代码更加简练,使开

[原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

Activiti 学习笔记记录(三)

上一篇:Activiti 学习笔记记录(二) 导读:上一篇学习了bpmn 画图的常用图形标记.那如何用它们组成一个可用文件呢? 我们知道 bpmn 其实是一个xml 文件

HTML&CSS基础学习笔记8-预格式文本

<pre>标签的主要作用是预格式化文本.被包围在 pre 标签中的文本通常会保留空格和换行符.而文本也会呈现为等宽字体. <pre>标签的一个常见应用就是用来表示计算机的源代码.当然你也可以在你需要在网页中预显示格式时使用它. 会使你的文本换行的标签(例如<h>.<p>)绝不能包含在 <pre> 所定义的块里.尽管有些浏览器会把段落结束标签解释为简单地换行,但是这种行为在所有浏览器上并不都是一样的. 更多学习内容,就在码芽网http://www.

java/android 设计模式学习笔记(14)---外观模式

这篇博客来介绍外观模式(Facade Pattern),外观模式也称为门面模式,它在开发过程中运用频率非常高,尤其是第三方 SDK 基本很大概率都会使用外观模式.通过一个外观类使得整个子系统只有一个统一的高层的接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节.当然,在我们的开发过程中,外观模式也是我们封装 API 的常用手段,例如网络模块.ImageLoader 模块等.其实我们在开发过程中可能已经使用过很多次外观模式,只是没有从理论层面去了解它. 转载请注明出处:http://bl