添加链接 注册    登录
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
一身肌肉的罐头  ·  搜索 | 香港中文大学(深圳)·  3 月前    · 
冷冷的板凳  ·  js中数组for循环调用后端接口,返回数据填 ...·  11 月前    · 
淡定的登山鞋  ·  中通支付亮相,5家快递巨头拿下20余张金融牌 ...·  11 月前    · 
路过的大象  ·  lotus nyo什么车-汽车之家·  1 年前    · 
茫然的木瓜  ·  研究机构-西安航空学院·  1 年前    · 
link管理  ›  Unity AB包学习笔记 | 星光与路人
unity string assetbundle
http://www.starloong.top/2023/05/17/Unity-AB%E5%8C%85%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
风流倜傥的烈酒
2 年前
avatar
文章
49
标签
14
分类
0

首页
归档
标签查看
星光与路人
首页
归档
标签查看

Unity AB包学习笔记

发表于 2023-05-17 | 更新于 2023-06-18
| 字数总计: 2.3k | 阅读时长: 9分钟 | 阅读量:

Unity AB包学习笔记

前言

这个笔记是我看b站教程所记录下来的笔记。大家可以去b站看一下这个教程: b站教程 。

了解什么是AB包

AB包就是特定于平台的资产压缩包,有点类似压缩文件。这些资产包括∶模型、贴图、预设体、音效、材质球等等。但是它不支持将代码一并打包。

AB包的作用

AB包相对于Resources加载,它可以更好的管理资源。Resources在项目出包的时候就被定死了,它是只读且无法修改。在Resources文件下的文件,无论是否被工程使用到它都会被打包。而AB包存储位置可自定义,压缩方式自定义且后期可以动态更新。而且AB包可以减少包体大小。这是因为AB包本身就有压缩资源,如果我们将AB包放置到网络上然后让玩家在包体安装好后再去下载AB包。那么初始包的包体大小就会减少了。(之所以减少包体大小是因为不同的包体大小每次宣发量是不一样的。我之前工作的时候听运营聊过100mb的包和100mb以下的包,宣发的钱差多了。)除此之外,AB包也支持热更新。

生成AB包资源文件

PS:我使用的Unity版本为2019.4.40f1,并且我在项目中已经安装好了Asset Bundle Browser1.7.0。

生成AB包资源文件有两种方法(视频里面只说了两种):我们使用Unity编译器开发自定义打包工具(公司比较大或者是公司比较老就可能有这样的方法)和我们使用Unity官方提供的打包工具Asset Bundle Browser来进行打包。

在开始前,我们首先要让资源和AB包关联。首先我们选中任意一个预制体,然后在Inspector窗口下选中AssetBundle选项并且new一个名字。如下图:

这是用来表明这个预制体会打包到model这个包中。

此时大家可以点击Windows→AssetBundle Browser来查看当前项目中AB包的信息。大家可以在它的Configure页签下看到一个model的选项,再次点击这个选项就可以看到我们之前设置的模型了。

另一个页签Build就是我们用来打包的。Build Target是用来指定我们要打包到哪里的平台。Output Path是我们打包成功后输出的路径。Clear Folders表示是否要请空Output Path路径下的文件夹。Copy to StreamingAssets表示我们是否要将其复制到StreamingAssets文件夹中。Advanced Setting下Comperssion是压缩类型,LZMA压缩的大小最小但是每次加载要将全部的资源都解压出来,LZ4压缩的大小较大但是我们可以要一个资源解压一个。Exclude Type Information在资源包中不包含资源的类型信息。Force Rebuild重新打包时需要重新构建包,但是它和ClearFolders不同,它不会删除不再存在的包。lgnore Type Tree Changes增量构建检查时,忽略类型数的更改。Append Hash 将文件哈希值附加到资源包名上。Strict Mode严格模式,如果打包时报错了,则打包直接失败无法成功。Dry Run Build运行时构建。我们设置完后点击Build按钮就可以进行打包。

打包出来后,大家可以看到一个主包和你设定的AB包的配置信息和资源(配置信息是文件后缀名为.manifest,资源是那些没有后缀名的二进制文件)。主包名和你设定文件名称相同。manifest包含了AB包的文件信息。当我们加载的时候它提供了关键信息:资源信息、依赖关系和版本信息等。

最后一个页签Inspector是用来看包的信息。

使用代码加载AB包文件

同步和异步加载AB包

同步加载AB包代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace MyGame
{
public class ABTest : MonoBehaviour
{
void Start()
{
// 加载AB包
AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "model"));
GameObject obj = ab.LoadAsset<GameObject>("Cube");
GameObject.Instantiate(obj);
}
}
}

因为我们之前设定文件在项目打包后是不能打进包中的,所以我们才用streamingAssets文件存储。实际在项目中,AB包资源会存放在服务器中然后我们从服务器中下载。

我们不能重复加载AB包比如下面的这段代码就会报错:The AssetBundle ‘StreamingAssets\model’ can’t be loaded because another AssetBundle with the same files is already loaded.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace MyGame
{
public class ABTest : MonoBehaviour
{
void Start()
{
// 加载AB包
AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "model"));
GameObject obj = ab.LoadAsset<GameObject>("Cube");
GameObject.Instantiate(obj);
AssetBundle ab2 = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "model"));
}
}
}

异步加载代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace MyGame
{
public class ABTest : MonoBehaviour
{
void Start()
{
StartCoroutine(LoadABAsync("model", "Cube"));
}

// 异步加载
private IEnumerator LoadABAsync(string abName, string resName)
{
// 加载ab包
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, abName));
yield return abcr; // 等待ab包加载完
// 异步加载资源
AssetBundleRequest abr = abcr.assetBundle.LoadAssetAsync<GameObject>(resName);
yield return abr;
GameObject.Instantiate(abr.asset as GameObject);
yield return null;
}
}
}

我们可以调用AssetBundle.UnloadAllAssetBundles来卸载全部加载过的AB包。但是如果我们只想卸载个别的AB包,那么可以按照下面的代码执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
```csharp
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace MyGame
{
public class ABTest : MonoBehaviour
{
void Start()
{
// 加载AB包
AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "model"));
ab.Unload(false);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
更多关于AssetBundle的API大家可以查看官网的介绍:[AssetBundle - Unity 脚本 API](https://docs.unity3d.com/cn/2019.4/ScriptReference/AssetBundle.html)。

### 代码加载AB包中的依赖

        我创建了一个新的材质命名为CubeMat,然后我让它被打包在mat包中。那么在我们加载model包之前,我们就要先让mat包被加载或者同时加载。而包之间的依赖,我们可以通过主包来获得。具体代码如下:

```csharp
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace MyGame
{
public class ABTest : MonoBehaviour
{
void Start()
{
LoadAB();
}
private void LoadAB()
{
// 加载主包
AssetBundle mainAB = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "StandaloneWindows"));
// 在主包中默认就有这个存在
AssetBundleManifest manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
// 获取所有的依赖
string[] strs = manifest.GetAllDependencies("model");
foreach(string str in strs)
{
AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, str));
}
AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "model"));
GameObject obj = ab.LoadAsset<GameObject>("Cube");
GameObject.Instantiate(obj);
}
}
}

AB包管理器

视频中使用了单例模式,但是我感觉更本没有这个必要的直接静态类应该也可以的。这里我就贴一个简单的单例实现就是了。我看视频中在调用的时候出现了一个DontDestroyOnLoad,我想这个单例大概是继承了Test : MonoBehaviour。所以单例模式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Common
{
public class MonoSingle<T> : MonoBehaviour where T : MonoSingle<T>
{
private static T instance;
private static Object lockflag = new Object();
public static T Instance
{
get
{
if (instance == null)
{
lock (lockflag)
{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
new GameObject(typeof(T).Name).AddComponent<T>();
}
instance.Init();
}
}
}
return instance;
}
}
void Awake()
{
if (instance == null)
{
instance = this.transform.GetComponent<T>();
instance.Init();
}
else Destroy(this.gameObject);
}

public virtual void Init()
{

}
}
}

具体的AB包管理器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using Common;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace MyGame
{
public class ABManager : MonoSingle<ABManager>
{
// 存储已经加载过的AB包
private Dictionary<string, AssetBundle> loadedAB = new Dictionary<string, AssetBundle>();

private string pathUrl = Application.streamingAssetsPath;

private string MainPackName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "android";
#else
return "StandaloneWindows";
#endif
}
}

private AssetBundle mainPackage = null;
private AssetBundleManifest manifest = null;

// 加载依赖包
private void LoadDependencies(string abName)
{
if (loadedAB.ContainsKey(abName))
{
return;
}
if (mainPackage == null)
{
// 加载主包
mainPackage = AssetBundle.LoadFromFile(Path.Combine(pathUrl, MainPackName));
// 在主包中默认就有这个存在
manifest = mainPackage.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}

// 获取所有的依赖
string[] strs = manifest.GetAllDependencies(abName);
foreach (string str in strs)
{
if (!loadedAB.ContainsKey(str))
{
loadedAB.Add(str, AssetBundle.LoadFromFile(Path.Combine(pathUrl, str)));
}
}
}

//同步加载
public Object loadRes(string abName, string resName)
{
if (loadedAB.ContainsKey(abName))
{
return loadedAB[abName].LoadAsset(resName);
}
LoadDependencies(abName);
loadedAB.Add(abName, AssetBundle.LoadFromFile(Path.Combine(pathUrl, abName)));
return loadedAB[abName].LoadAsset(resName);
}

public Object loadRes(string abName, string resName, System.Type type)
{
if (loadedAB.ContainsKey(abName))
{
return loadedAB[abName].LoadAsset(resName, type);
}
LoadDependencies(abName);
loadedAB.Add(abName, AssetBundle.LoadFromFile(Path.Combine(pathUrl, abName)));
return loadedAB[abName].LoadAsset(resName, type);
}

public T loadRes<T>(string abName, string resName, System.Type type) where T : UnityEngine.Object
{
if (loadedAB.ContainsKey(abName))
{
return loadedAB[abName].LoadAsset<T>(resName);
}
LoadDependencies(abName);
loadedAB.Add(abName, AssetBundle.LoadFromFile(Path.Combine(pathUrl, abName)));
return loadedAB[abName].LoadAsset<T>(resName);
}

// 卸载包
public void UnLoadAB(string abName, bool unLoadAllLoadedObj = false)
{
if (loadedAB.ContainsKey(abName))
{
loadedAB[abName].Unload(unLoadAllLoadedObj);
loadedAB.Remove(abName);
}
}
public void UnLoadAllAB(bool unLoadAllLoadedObj = false)
{
loadedAB.Clear();
AssetBundle.UnloadAllAssetBundles(unLoadAllLoadedObj);
mainPackage = null;
manifest = null;
}
}
}
 
推荐文章
一身肌肉的罐头  ·  搜索 | 香港中文大学(深圳)
3 月前
冷冷的板凳  ·  js中数组for循环调用后端接口,返回数据填充入数组_js循环请求接口,将返回数据放到数组里-CSDN博客
11 月前
淡定的登山鞋  ·  中通支付亮相,5家快递巨头拿下20余张金融牌照,什么信号
11 月前
路过的大象  ·  lotus nyo什么车-汽车之家
1 年前
茫然的木瓜  ·  研究机构-西安航空学院
1 年前
Link管理   ·   Sov5搜索   ·   小百科
link管理 - 链接快照平台