教程-【唐老狮】Unity热更新之AssetBundle

AB包理论基础

什么是AB包

特定于平台的资产压缩包,类似压缩文件

资产包括:模型、贴图、预设体、音效、材质球等

AB包的作用

相对于Resources下的资源AB包能更好管理资源,resources打包后是只读的,AB包存储位置可自定义,后期可以动态更新

  1. 减小包体大小:压缩资源、减少初始包大小
  2. 热更新:资源热更新、脚本热更新

屏幕截图 2023-04-24 173112

AB包资源打包

1.unity编辑器开发,自定义打包工具

如果想自己写打包工具,学这个

2.官方提供好的打包工具

AssetBundleBrowser

//现在(2023-4)建议使用addressable

解压方式:

  1. LZMA:压缩后文件小,使用其中任何资源都要解压整个包

  2. LZ4:压缩后文件稍大,使用其中某个资源只需打包该资源

AB包资源加载

  • 同步加载
1
2
3
4
5
6
7
//加载AB包
var ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath+"某个路径");
//加载AB包中的资源
//建议用泛型加载,或者用type指定类型,否则可能会出现重名资源问题
var obj1 = ab.LoadAsset<资源类型>("资源名");
var obj2 = ab.LoadAsset("资源名",typepf(资源类型))as 资源类型
//AB包不能重复加载否则会报错
  • 异步加载
1
2
3
4
5
6
7
8
IEnumerator LoadABRes(string ABName, string resName)
{
//加载AB包
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetPath +"某个路径");
//加载资源
var abq = abcr.assetBudle.LoadassetAsync("资源名"typeof(类型));
yield return abq;
}
1
StartCoroutine(LoadABRes("包名""资源名"));
  • 卸载AB包
1
AssetBundle.UnloadAllAssetBundles(false);// 参数为ture会把所有通过AB包加载的资源也卸载,为false会仅卸载AB包

AB包依赖

在包中的A资源如果使用了另外一个B资源(A依赖B),会自动的将它打包到同一个包中。

如果A资源和B资源打包到了不同的包中,只加载A资源所在的包,会出现B资源丢失。此时B资源所在的包叫依赖包,需要加载依赖包使A资源正常使用

1
2
3
4
5
6
7
8
9
10
11
12
var ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath +"某个路径A")
//依赖报的关键知识点--利用主包 获取依赖信息
AssetBundle abMain = AssetBundle. LoadFromFile(Application.streamingAssetsPath+"主包路径");
//加载主包中的固定文件
AssetBundleMainfest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//从固定文件中 得到依赖信息
string[] strs = abMainifest.GetAllDependencies("路径A的包名");

for(int i =0;i<strs.Length;i++)
{
AssetBundle.LoadFromFile(Application.streamingAssetsPath +"/"+strs[i]);
}

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public class ABMgr : SingletonMono<ABMgr>
{
//AB包管理器的目的是让外部更方便的进行资源加载
//AB包不能够重复加载,否则会报错
//用字典存储加载过的AB包
private Dictionary<string,AssetBundle> abDic = new();

//AB包存放路径,使用属性方便修改
private string PathUrl => Application.streamingAssetsPath + "/";
//根据平台判断主包名
private string MainABName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "ANDROID";
#else
return "PC";
}
}
//主包
private AssetBundle mainAB = null;
//依赖包获取用的配置文件
private AssetBundleManifest manifest = null;

//同步加载
public object LoadRes(string abName,string resName)
{
LoadAB(abName);
return abDic[abName].LoadAsset(resName);
}
//重载同步加载,根据type指定类型
public object LoadRes(string abName,string resName, System.Type type)
{
LoadAB(abName);
return abDic[abName].LoadAsset(resName,type);
}
//重载同步加载,根据泛型指定类型
public object LoadRes<T>(string abName,string resName) where T:object
{
LoadAB(abName);
return abDic[abName].LoadAsset<T>(resName);
}

public void LoadAB(string name)
{
//加载AB包
if(maniAB = null)
{
mainAB = AssetBundle.LoadFromFile(PathUrl+MainABName);
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
//获取依赖报相关信息
AssetBundle ab = null;
string[] strs = manifest.GetAllDependencies(abName);
for(int i = 0; i<strs.Length;i++)
{
//判断包是否加载过
if(!abDic.ContainsKey(strs[i]))
{
ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
abDic.Add(str[i],ab);
}
}
//加载资源来源包
//如果没有加载过,再加载
if(!abdic.ContainsKey(abName))
{
ab = AssetBunle.LoadFromFile(PathUrl + abName);
abDic.Add(Abname,ab);
}
}

//异步加载
//这里的异步加载,AB包并没有使用异步加载,只是从AB包加载资源时使用异步
public void LoadResAsync(string abName,string resName,UnityAction<Object> callBack)
{
StartCoroutine(ReallyLoadResAsync(abName,resName,callBack));
}

private IEnumerator ReallyLoadResAsync(string abName,string resName,UnityAction<Object> callBack)
{
LoadAB(abName);
var abr = abDic[abName].LoadAssetAsync(resName);
yield return abr;
callBack(abr.asset);
}

public void LoadResAsync(string abName,string resName,System.Type type,UnityAction<Object> callBack)
{
StartCoroutine(ReallyLoadResAsync(abName,resName,type,callBack));
}

private IEnumerator ReallyLoadResAsync(string abName,string resName,System.type,UnityAction<Object> callBack)
{
LoadAB(abName);
var abr = abDic[abName].LoadAssetAsync(resName,type);
yield return abr;
callBack(abr.asset);
}

public void LoadResAsync<T>(string abName,string resName,UnityAction<T> callBack) where T:object
{
StartCoroutine(ReallyLoadResAsync<T>(abName,resName,callBack));
}

private IEnumerator ReallyLoadResAsync<T>(string abName,string resName,UnityAction<T> callBack)
{
LoadAB(abName);
var abr = abDic[abName].LoadAssetAsync<T>(resName);
yield return abr;
callBack(abr.asset as T);

}

//单个包卸载
public void UnLoad(string abName)
{
if(abDic.ContainsKey(abName))
{
abDic[abName].Unload(false);
abDic.Remove(abName);
}
}

//所有包卸载
public void ClearAb()
{
AssetBundle.UnloadAllAssetBundles(false);
abDic.Clear();
maniAB = null;
manifest = null;
}

}

宏定义:在不同的环境下执行不同的代码,比如在安卓平台和在苹果平台下执行不同的代码,这样你就可以专门为你的目标平台编译代码

使用AB包资源管理器

1
2
3
4
5
6
7
void Start()
{
var obj = ABMgr.Instance.LoadRec("model","Cube");
Instantiate(obj);
obj = ABMgr.Instance.LoadRec("model","Cube",typeof(GameObject)) as GameObject;
obj = ABMgr.Instance.LoadRec<GameObject>("model","Cube");
}