本项目将整合之前Unity程序基础小框架专栏在Unity 3D模型展示项目基础上进行整合,并记录了集成过程中对原脚本的调整过程。增加了Asset Bundle+ILRuntime热更新技术流程。
本篇文章介绍如何对更新进行代码检测以及使用更新资源服务器进行资源热更新。
创建登录UI预制体LoginUI.prefab
,主要功能按钮如图所示:
添加项目启动脚本ProLaunch.cs
,主要进行热更资源检测、下载更新操作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class ProLaunch : MonoBehaviour
{
///
/// 显示下载状态和进度
///
public Text UpdateText;
public Text DownText;
public Button btnCheckAndUpdate;
public Button btnUpdate;
public Button btnDown;
public Button btnLogin;
public Slider Slider;//滑动条组件
private List<object> _updateKeys = new List<object>();
// Start is called before the first frame update
void Start()
{
//retryBtn.gameObject.SetActive(false);
btnCheckAndUpdate.onClick.AddListener(() =>
{
StartCoroutine(DoUpdateAddressadble());
});
btnUpdate.onClick.AddListener(() =>
{
UpdateCatalog();
});
// 默认自动执行一次更新检测
//StartCoroutine(DoUpdateAddressadble());
btnDown.onClick.AddListener(() =>
{
DownLoad();
});
btnLogin.onClick.AddListener(() =>
{
SceneManager.LoadScene(1);
//StartCoroutine(LoadScene("Test2"));
});
}
// Update is called once per frame
void Update()
{
}
public async void UpdateCatalog()
{
//初始化Addressable
var init = Addressables.InitializeAsync();
await init.Task;
//开始连接服务器检查更新
var handle = Addressables.CheckForCatalogUpdates(false);
await handle.Task;
Debug.Log("check catalog status " + handle.Status);
if (handle.Status == AsyncOperationStatus.Succeeded)
{
List<string> catalogs = handle.Result;
if (catalogs != null && catalogs.Count > 0)
{
foreach (var catalog in catalogs)
{
Debug.Log("catalog " + catalog);
}
Debug.Log("download catalog start ");
UpdateText.text = UpdateText.text + "\n下载更新catalog";
var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
await updateHandle.Task;
foreach (var item in updateHandle.Result)
{
Debug.Log("catalog result " + item.LocatorId);
foreach (var key in item.Keys)
{
Debug.Log("catalog key " + key);
}
_updateKeys.AddRange(item.Keys);
}
Debug.Log("download catalog finish " + updateHandle.Status);
UpdateText.text = UpdateText.text + "\n更新catalog完成" + updateHandle.Status;
}
else
{
Debug.Log("dont need update catalogs");
UpdateText.text = "没有需要更新的catalogs信息";
}
}
Addressables.Release(handle);
}
public void DownLoad()
{
StartCoroutine(DownAssetImpl());
}
public IEnumerator DownAssetImpl()
{
var downloadsize = Addressables.GetDownloadSizeAsync(_updateKeys);
yield return downloadsize;
Debug.Log("start download size :" + downloadsize.Result);
UpdateText.text = UpdateText.text + "\n更新文件大小" + downloadsize.Result;
if (downloadsize.Result > 0)
{
var download = Addressables.DownloadDependenciesAsync(_updateKeys, Addressables.MergeMode.Union);
yield return download;
//await download.Task;
Debug.Log("download result type " + download.Result.GetType());
UpdateText.text = UpdateText.text + "\n下载结果类型 " + download.Result.GetType();
foreach (var item in download.Result as List<UnityEngine.ResourceManagement.ResourceProviders.IAssetBundleResource>)
{
var ab = item.GetAssetBundle();
Debug.Log("ab name " + ab.name);
UpdateText.text = UpdateText.text + "\n ab名称 " + ab.name;
foreach (var name in ab.GetAllAssetNames())
{
Debug.Log("asset name " + name);
UpdateText.text = UpdateText.text + "\n asset 名称 " + name;
}
}
Addressables.Release(download);
}
Addressables.Release(downloadsize);
}
IEnumerator LoadScene(string senceName)
{
// 异步加载场景(如果场景资源没有下载,会自动下载),
var handle = Addressables.LoadSceneAsync(senceName);
if (handle.Status == AsyncOperationStatus.Failed)
{
Debug.LogError("场景加载异常: " + handle.OperationException.ToString());
yield break;
}
while (!handle.IsDone)
{
// 进度(0~1)
float percentage = handle.PercentComplete;
Debug.Log("进度: " + percentage);
yield return null;
}
Debug.Log("场景加载完毕");
}
IEnumerator DoUpdateAddressadble()
{
AsyncOperationHandle<IResourceLocator> initHandle = Addressables.InitializeAsync();
yield return initHandle;
// 检测更新
var checkHandle = Addressables.CheckForCatalogUpdates(false);
yield return checkHandle;
if (checkHandle.Status != AsyncOperationStatus.Succeeded)
{
OnError("CheckForCatalogUpdates Error\n" + checkHandle.OperationException.ToString());
yield break;
}
if (checkHandle.Result.Count > 0)
{
var updateHandle = Addressables.UpdateCatalogs(checkHandle.Result, false);
yield return updateHandle;
if (updateHandle.Status != AsyncOperationStatus.Succeeded)
{
OnError("UpdateCatalogs Error\n" + updateHandle.OperationException.ToString());
yield break;
}
// 更新列表迭代器
List<IResourceLocator> locators = updateHandle.Result;
foreach (var locator in locators)
{
List<object> keys = new List<object>();
keys.AddRange(locator.Keys);
// 获取待下载的文件总大小
var sizeHandle = Addressables.GetDownloadSizeAsync(keys);
yield return sizeHandle;
if (sizeHandle.Status != AsyncOperationStatus.Succeeded)
{
OnError("GetDownloadSizeAsync Error\n" + sizeHandle.OperationException.ToString());
yield break;
}
long totalDownloadSize = sizeHandle.Result;
UpdateText.text = UpdateText.text + "\ndownload size : " + totalDownloadSize;
Debug.Log("download size : " + totalDownloadSize);
if (totalDownloadSize > 0)
{
// 下载
var downloadHandle = Addressables.DownloadDependenciesAsync(keys, Addressables.MergeMode.Union, false);
//yield return downloadHandle;
while (!downloadHandle.IsDone)
{
if (downloadHandle.Status == AsyncOperationStatus.Failed)
{
OnError("DownloadDependenciesAsync Error\n" + downloadHandle.OperationException.ToString());
yield break;
}
// 下载进度
float percentage = downloadHandle.PercentComplete;
Debug.Log($"已下载: {percentage}");
DownText.text = $"已下载: {Mathf.Round(percentage * 100)}%";
Slider.value = percentage;
if (percentage >= 0.9f)//如果进度条已经到达90%
{
Slider.value = 1; //那就让进度条的值编变成1
}
yield return null;
}
yield return downloadHandle;
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log("下载完毕!");
DownText.text = DownText.text + " 下载完毕";
}
}
}
}
else
{
UpdateText.text = UpdateText.text + "\n没有检测到更新";
}
// 进入游戏
EnterPro();
}
// 进入游戏
void EnterPro()
{
// TODO
UpdateText.text = UpdateText.text + "\n进入游戏场景";
Debug.Log("进入游戏");
}
private void OnError(string msg)
{
UpdateText.text = UpdateText.text + $"\n{msg}\n请重试! ";
}
}
注意使用代码更新时,需要修改热AddressableAssetSettings更新设置否则代码将无法检测拦截更新信息
。
做完以上工作,进行热更Groups
的设置与修改。创建一个远程更新组并命名为RemoteGroup
,按照图中所示进行热更资源配置。
配置热更资源服务器,web服务器为IIS,如何创建应用程序这里不做赘述,有不懂的小伙伴留言。这里注意将热更新资源.bundle
、.hash
加入到应用程序的MIME中使得Unity项目可以访问到,否则404无法访问资源。
配置好IIS服务器后访问地址 http://www.btlfxx.cn/unity_cmj/tempas,没毛病!!!
Unity项目中配置远程更新服务器
配置信息
配置完成后New Build
一下,重新生成一下.bin
文件。
构建成功信息
打开本地构建热更资源的文件夹,看到如下文件信息。这些就是需要Copy到服务器上的热更文件,Copy服务器之后在进行如下配置。
确定项目热更新的工作模式
运行Unity项目
点击检测并更新
点击登录
后跳转场景
下面做热更操作,将Switch
预制体中的模块隐藏掉。重新构建资源热更包,将热更包Copy到服务器上。
运行项目,下面做一个能下载资源的热更包。
将RemoteGroup
组中的Switch
中的主体添加一个3D物体,重新打包。可以发现3个更新文件,组remotegroup
新生成一个资源包,只需要Copy这3个文件到服务器上。
Unity AA热更新