@[TOC](【Unity开发】避免重复加载场景资产AB(AssetBundle)包的优化)
Unity2022,项目需要远程加载场景AB包,并加载场景。
1、 初始方案
1.1 异步加载场景资产(获取AB包数据)
// 异步加载远程资产(获取远程AB包数据)
public IEnumerator LoadAssetFromAB(string url, Action<AssetBundle> assetAB)
{
yield return new WaitForSeconds(0.16f);
using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
yield return webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success){
var bundle = DownloadHandlerAssetBundle.GetContent(webRequest);
assetAB?.Invoke(bundle);
}
}
}
1.2 异步加载场景(进入场景)
// 异步加载场景
public void LoadSceneFromAB(string sceneName){
// 启动异步线程加载场景
StartCoroutine(AssetToScene(sceneName));
}
// 异步场景加载迭代器
private IEnumerator AssetToScene(string sceneName){
// 异步加载场景资产
AsyncOperation loadOperation = SceneManager.LoadSceneAsync(sceneName);
yield return loadOperation;
Debug.Log("应该到不了这里就直接转场了,那么AB资产包还在内存,下次就会报[重复加载]的异常");
// 卸载内存中的assetAB资产(除了LoadScene创建出来的场景)
assetAB.Unload(false);
}
1.3 点击跳转场景
public void GotoScene(ClickEvent evt){
string sceneName = "myScene";
char dsp = Path.AltDirectorySeparatorChar;
string url = Application.dataPath + dsp + "scene" + dsp + sceneName;
// 销毁所有未使用资产
Resources.UnloadUnusedAssets();
// 临时解决方案
// 尝试直接加载AB场景 (避免AB资产已在内存时AB包重复报错)
// The AssetBundle 'http://192.168.2.107:8080/level/Level1' can't be loaded because another AssetBundle with the same files is already loaded.
SceneManager.LoadScene(sceneName);
// 这里如果成功加载场景,不会走到下一步,
// 前提是内存里有场景包 1、上次已经加载到内存没卸载 2、程序Build编译时Scenes In Build
// Scene 'Level1' couldn't be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.
//To add a scene to the build settings use the menu File->Build Settings...
// 远程异步加载AB场景资产
StartCoroutine(_dataRestapi.LoadAssetFromAB(url, (assetAB) => {
if (assetAB != null) {
LoadSceneFromAB(sceneName);
}else{
Debug.LogWarning("场景资产数据加载失败");
}
}));
}
2、 优化方案
2.1 异步加载场景资产(获取AB包数据)
2.1.1 实用精简版
public IEnumerator LoadAssetFromAB(string url, Action<AssetBundle> assetAB)
{
yield return new WaitForSeconds(0.16f);
using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
// 等待通讯结束
yield return webRequest.SendWebRequest();
// 下载成功
if (webRequest.result == UnityWebRequest.Result.Success){
Debug.Log(string.Format("下载进度: {0}%", 100));
var bundle = DownloadHandlerAssetBundle.GetContent(webRequest);
assetAB?.Invoke(bundle);
}
}
}
2.1.2 加载进度版本
// 异步加载远程资产(获取远程AB包数据)
public IEnumerator LoadAssetFromAB(string url, Action<AssetBundle> assetAB)
{
yield return new WaitForSeconds(0.16f);
using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
// 服务器通讯
webRequest.SendWebRequest();
// 下载进度信息
while (!webRequest.isDone)
{
float progress = webRequest.downloadProgress;
Debug.Log(string.Format("下载进度: {0}%", (progress/0.9*100).ToString("F2")));
yield return null;
}
// 下载成功
if (webRequest.result == UnityWebRequest.Result.Success){
Debug.Log(string.Format("下载进度: {0}%", 100));
// 从下载缓存提取AB资产包格式数据
var bundle = DownloadHandlerAssetBundle.GetContent(webRequest);
assetAB?.Invoke(bundle);
}
}
}
2.1.3 资产本地化版本
// 异步远程资产本地化并加载(保存远程AB包数据到本地)
public IEnumerator SaveAssetFromAB(string url, Action<AssetBundle> assetAB)
{
yield return new WaitForSeconds(0.16f);
using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
// 将AB字节数据写入本地文件
string locfile = url.Replace(remoteBundleURL, localBundlePath);
var handler = new DownloadHandlerFile(locfile);
webRequest.downloadHandler = handler;
// 服务器通讯
webRequest.SendWebRequest();
// 下载进度信息
while (!webRequest.isDone)
{
float progress = webRequest.downloadProgress;
Debug.Log(string.Format("下载进度: {0}%", (progress*100).ToString("F2")));
yield return null;
}
// 下载成功
if (webRequest.result == UnityWebRequest.Result.Success){
Debug.Log(string.Format("下载进度: {0}%", 100));
// 从本地文件提取AB资产数据
var bundle = AssetBundle.LoadFromFile(locfile);
assetAB?.Invoke(bundle);
}
}
}
2.2 异步加载场景(进入场景)
1 注册后处理事件 SceneManager.sceneLoaded += OnSceneLoaded;
2 删除永远不会被运行的 assetAB.Unload(false);
// 异步加载场景
public void LoadSceneFromAB(string sceneName){
// 启动异步线程加载场景
StartCoroutine(AssetToScene(sceneName));
// 当新场景开始加载时注册事件 解决加载完成后处理事件
SceneManager.sceneLoaded += OnSceneLoaded;
}
// 异步场景加载迭代器
private IEnumerator AssetToScene(string sceneName){
// 异步加载场景资产
AsyncOperation loadOperation = SceneManager.LoadSceneAsync(sceneName);
yield return loadOperation;
}
2.3 点击跳转场景
删除尝试加载场景 SceneManager.LoadScene(sceneName);
因为不需要了,每次运行都是从远程获取最新的场景AB包
// 声明AB资产缓存用于后处理事件
private AssetBundle _assetAB;
// 场景加载触发事件
public void GotoScene(string sceneName){
char dsp = Path.AltDirectorySeparatorChar;
string url = Application.dataPath + dsp + "scene" + dsp + sceneName;
// 销毁所有未使用资产
Resources.UnloadUnusedAssets();
// 远程异步加载AB场景资产
StartCoroutine(LoadAssetFromAB(url, (assetAB) => {
if (assetAB != null) {
// 缓存资产用于后处理事件
_assetAB = assetAB;
// 异步加载场景(进入场景)
LoadSceneFromAB(sceneName);
}else{
Debug.LogWarning("场景资产数据加载失败");
}
}));
}
2.4 增加场景加载后处理事件
保证场景加载完成后自动删除场景AB资产包
// 场景加载后处理事件
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 当新场景加载完成后,卸载内存中AssetBundle包(除了Load创建出来的对象/场景)
_assetAB.Unload(false);
// 注销事件,防止重复调用
SceneManager.sceneLoaded -= OnSceneLoaded;
}