前一篇文章记录了Addressable可寻址地址系统的一些基本概念和基本用法,本章将会记录一套基于Addressable的资源加载卸载。结合前面学习的享元模式的理念,也会设计缓存池的相关逻辑。
如上图所示,整套流程主要分为两大部分:资源加载部分和缓存池设计部分。
资源加载
AddressableManager通过单例模式,将各种类型的加载、下载以及卸载入口都放入统一的入口处。
public void LoadAsset<T>(string address, System.Action<T> onComplete, System.Action onFailed = null, bool autoUnload = false) where T : UnityEngine.Object
{
if (caches.ContainsKey(address))
{
var handle = this.caches[address];
if (handle.IsDone)
{
if (onComplete != null)
{
onComplete(caches[address].Result as T);
}
}
else
{
handle.Completed += (result) => {
if (result.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
var obj = result.Result as T;
if (onComplete != null)
{
onComplete(obj);
}
if (autoUnload)
UnLoadAsset(address);
}
else
{
if (onFailed != null)
{
onFailed();
}
Helper.LogError("Load " + address + " failed!");
}
};
}
}
else
{
var handle = Addressables.LoadAssetAsync<T>(address);
handle.Completed += (result) =>
{
if (result.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
var obj = result.Result as T;
if (onComplete != null)
{
onComplete(obj);
}
if (autoUnload)
UnLoadAsset(address);
}
else
{
if (onFailed != null)
{
onFailed();
}
Helper.LogError("Load " + address + " failed!");
}
};
addCaches(address, handle);
}
}
如上述代码所示,考虑加载是异步的,所以设计时需要缓存下载句柄AsyncOperationHandle,当资源已经启动加载时添加完成回调,避免重复加载的情况。
资源检查更新
虽然Addressable实现了再加载时自动检查更新最新的资源文件进行加载使用,但是对于游戏而言这种做法并不是很友好,因此在设计时需要将AddressableAssetSettings 的设置项中Disable Catalog Update on StartUp 勾选,禁止自动更新,在游戏开始前手动做好资源的检查更新,手动下载资源。
检查更新
Addressable官方提供了增量资源检查,通过一些特定的API可以实现增量的资源更新,打包后只需要将打包的资源放在特定的服务器资源路径下即可。
资源检查更新一般可按照Addressable初始化->获取Catalog更新->获取需要下载资源大小->资源下载。
注意 :后面两步骤在设计时可以更新所有资源,也可以更新指定的资源,本项目在设计时将一部分资源放网络端动态下载,另外一部分放本地随包发布以此来减少安装包的大小。注意:经过实践,如果是指定资源的话,keys 只能是AddressableAsset资源的标签Labels,否则下载会失败。因此Addressable规划时要考虑到分组策略,以便后期的打包使用
IEnumerator checkUpdate()
{
keys = new List<object>();
for (int i = 0; i < needLoadKeys.Count; i++)
{
keys.Add(needLoadKeys[i]);
}
totalDownLoadSize = 0;
desc.color = Color.black;
desc.text = "正在检查更新...";
Helper.Log("初始化 Addressables");
yield return Addressables.InitializeAsync();
Helper.Log("检查 catalogs 更新");
var checkHandle = Addressables.CheckForCatalogUpdates(false);
yield return checkHandle;
if (checkHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
List<string> catalogs = checkHandle.Result;
if (catalogs != null && catalogs.Count > 0)
{
Helper.Log("下载 catalogs 更新");
var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
yield return updateHandle;
if (updateHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
var sizeHandle = Addressables.GetDownloadSizeAsync(keys);
yield return sizeHandle;
desc.text = string.Empty;
if (sizeHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
totalDownLoadSize = sizeHandle.Result;
Debug.Log("DownLoad Size:" + (totalDownLoadSize / 1024.0f / 1024.0f).ToString("0.00"));
checkStatus = CheckStatus.FinishCheck;
checkForUpdate();
}
else
{
checkStatus = CheckStatus.FailedCheck;
checkForUpdate();
}
Addressables.Release(sizeHandle);
}
else
{
desc.text = "检查更新失败,检查联网状态";
desc.color = Color.red;
checkStatus = CheckStatus.FailedCheck;
checkForUpdate();
}
Addressables.Release(updateHandle);
}
else
{
Helper.Log("本地catalogs无需更新,检查资源是否有更新");
var sizeHandle = Addressables.GetDownloadSizeAsync(keys);
yield return sizeHandle;
if (sizeHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
desc.text = string.Empty;
totalDownLoadSize = sizeHandle.Result;
Debug.Log("DownLoad Size:" + (totalDownLoadSize / 1024.0f / 1024.0f).ToString("0.00"));
checkStatus = CheckStatus.FinishCheck;
checkForUpdate();
}
else
{
desc.text = "检查更新失败,检查联网状态";
desc.color = Color.red;
checkStatus = CheckStatus.FailedCheck;
checkForUpdate();
}
Addressables.Release(sizeHandle);
}
}
else
{
checkStatus = CheckStatus.FailedCheck;
checkForUpdate();
}
Addressables.Release(checkHandle);
}
/// <summary>
/// 获取下载的资源大小
/// </summary>
public void GetDownLoadSize(object key, System.Action<long> onComplete, System.Action onFailed = null)
{
var sizeHandle = Addressables.GetDownloadSizeAsync(key.ToString());
sizeHandle.Completed += (result) => {
if (result.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
var totalDownLoadSize = sizeHandle.Result;
if (onComplete != null)
{
onComplete(totalDownLoadSize);
}
}
else
{
if (onFailed != null)
{
onFailed();
}
}
Addressables.Release(sizeHandle);
};
}
/// <remarks>下载指定资源</remarks>
public AsyncOperationHandle DownLoad(object key, System.Action onComplete, System.Action onFailed = null)
{
var downLoadHandle = Addressables.DownloadDependenciesAsync(key.ToString(), true);
downLoadHandle.Completed += (result) =>
{
if (result.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
if (onComplete != null)
{
onComplete();
}
}
else
{
if (onFailed != null)
{
onFailed();
}
}
};
return downLoadHandle;
}
版本检查
游戏中难免会存在单靠资源更新不能解决的问题,如新功能开放等。为保障用户体验,我们需要设计APP的版本检查。方案其实很简单,在资源服务器指定路径下存放一文本,记录当前APP的最新版本,游戏启动后客户端自行对比本地版本号和服务器版本号是否一致来判断是否需要像用户提示更新信息:
IEnumerator checkVersion()
{
yield return new WaitForSeconds(5);
UnityWebRequest request = UnityWebRequest.Get(versionUrl);
request.SendWebRequest();
while (!request.isDone)
{
yield return 1;
}
if (request.isDone)
{
try
{
string new_version =string.Empty;
for (int i = 0; i < request.downloadHandler.text.Length; i++)
{
var cr = request.downloadHandler.text[i];
Debug.LogError(i + " = " + cr);
if ((cr >= '0' && cr <= '9') || cr == '.')
new_version += cr;
}
int[] new_versions=Array.ConvertAll(new_version.Split('.'), s=>Convert.ToInt32(s.ToString())) ;
string now_version = PlayerPrefs.GetString("AppVersion", Application.version);
int[] now_versions = Array.ConvertAll(now_version.Split('.'), s => Convert.ToInt32(s.ToString()));
if (new_versions[0] > now_versions[0])
{
UIManager.Instance.showConsureOnly_ok("<color=#FF0000FF>" + "检查到有版本更新\n请立即更新后使用\n" + "</color>",
(isOK) =>
{
Application.OpenURL(downLoadUrl);
});
}
else if (new_versions[1] > now_versions[1])
{
UIManager.Instance.showMessageConsure("<color=#FF0000FF>" + "检查到有版本更新\n请立即更新后使用\n" + "</color>",
(isOK) =>
{
if (isOK)
{
Application.OpenURL(downLoadUrl);
}
else
{
UpdateAssets();
}
});
}
}
catch (Exception e)
{
Helper.LogError(e.Message);
UpdateAssets();
}
}
else
{
UpdateAssets();
}
}
之所以挨个遍历是还有一个问题尚未解决:同样是“1.0.1“这样的版本信息,从服务器down下来之后使用Split分割后使用 int.Prase() 转整数会抛出字符串不正确的异常 我确认服务器上的文本也只有“1.0.1“版本信息,本地直接写在代码里的string字符串则不会。挨个遍历重新存储一次后又能正常转整型。