官方文档和一些网络上的资料还停留在旧版本,有些界面和选项不太一样,故记录一下针对1.6.0版本的流程。
0.最基本的操作就不说了,可以去B站找视频,只记录一些要点。
1.本地搭建服务器测试
打开Addressable Hosting Services , 新建一个Local Hosting . 点击Enable开启,生成的IP和Port就是
用于本地测试的虚拟服务器
打开Addressables Profiles ,根据刚刚配置的Hosting设置RemoteLoadPath,注意:RemoteBuildPath不需要修改,
使用默认值,会build在与Assets同级的目录里。这个目录就是加载时最终指向的目录。
以上是Unity提供的一个工具,建议自己搭建真实服务器(Mac自带Apache、Win自带IIS),然后用手机测试。
Editor下一些表现并不正确,比如在找不到服务器资源的情况下虽然会报错但是依然显示了项目里的资源,
而真实手机上会彻底无法加载。
2.Group设置
手机测试结果:
Local Build |
创建到Library目录,会在打包时候自动包含进StreamingAssets。 |
Remote Build |
创建到指定目录,所以这个Group的资源不会被包含在包(apk)中,而是需要联网下载。 |
Local Load |
会尝试连接服务器去检测是否有更新,有的话则下载最新资源并覆盖到本地,没有则直接读取本地资源(初始在SA目录) |
Remote Load |
需要读取服务器上的资源,有则下载到本地,没有则会报错,且无内容可加载。此时如果在服务器上添加资源,手机端可以重新调用API直接加载到资源,但是加载之后就会被缓存,如果服务器此时再更新资源,手机端必须重启游戏才会得到更新资源。 |
Can/Cannot Change Post Release:仅仅是一个标记,将此组标记为Cannot之后,当组内有资源
发生变化,可以通过check+update方式只给发生变化的资源出“更新资源包”
3.关于更新
第一次打包时候打开如下配置,这样第二次才可以更新。
这个选项打开之后Build完才会在RemoteBuildPath目录里面生成.hash和.json文件,记录着此次打包的资源信息。
开启这个选项之后意味着以后游戏每次启动,都需要去根据这两个文件检查是否需要更新资源。所以这两个文
件需要放到服务器上,否则会报错,报错不会阻断游戏进程而是继续读取本地已有的资源。
先检查资源更新和自动分组。Addressable->Tools->Check for Content Update .然后选择上一次整包的bin文件,Unity会做对比后显示对比结果,apply则自动分组。
更新的选项是 Build-> Update a previous Build 。
如果是远程服务器,直接将打包出来的更新资源放到服务器指定位置即可
4.选择性更新
Addressable默认是在每次加载资源的时候更新的,但是实际开发中希望启动时下载完所有需要更新的资源,
或者弹窗出来让用户选择性更新资源,甚至是只更新一部分
首先钩上这个选项,不勾的时候Unity会在每次加载资源时尝试检查更新并下载更新,勾上之后只会读取本地资源
需要用到的API:
CheckForCatalogUpdates
UpdateCatalogs
使用API之前需要先确保Addressable已经初始化完成(InitializeAsync),虽然手册写的会在第一次调用API时自动初始化,
但是实际测试中我发现不先调用初始化,直接执行CheckForCatalogUpdates会返回0
另外以上功能只是“更新”资源,即已经拥有资源但是检测到需要更新时生效。如果包体内不包含资源,即直接将游戏的一部分
资源放到服务器,在游戏安装后下载,需要API:
Addressables.GetDownloadSizeAsync
示例代码:
public void DownLoadAsset()
{
StartCoroutine(download());
}
IEnumerator download()
{
//初始化Addressable
var init = Addressables.InitializeAsync();
yield return init;
List<object> list = new List<object>();
list.Add("Cube");
list.Add("Sphere");
var downloadsize = Addressables.GetDownloadSizeAsync(list);
yield return downloadsize;
Debug.Log("start download:" + downloadsize.Result);
var download = Addressables.DownloadDependenciesAsync(list,Addressables.MergeMode.None);
yield return download;
Debug.Log("download finish");
}
public void UpdateAsset()
{
StartCoroutine(checkUpdate());
}
IEnumerator checkUpdate()
{
//初始化Addressable
var init = Addressables.InitializeAsync();
yield return init;
//开始连接服务器检查更新
AsyncOperationHandle<List<string>> checkHandle = Addressables.CheckForCatalogUpdates(false);
//检查结束,验证结果
yield return checkHandle;
if (checkHandle.Status == AsyncOperationStatus.Succeeded)
{
List<string> catalogs = checkHandle.Result;
if (catalogs != null && catalogs.Count > 0)
{
Debug.Log("download start");
var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
yield return updateHandle;
Debug.Log("download finish");
}
}
Addressables.Release(checkHandle);
}
5.以上工作都可以通过代码完成,由于官方文档写的比较简单也没有实例,很多东西都是从源码中挖掘出来的。贴出一部分常用功能
//Editor类:设置Group,打包,更新等
using System;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Script.ResMgr.Editor
{
public class AddressableEditor
{
[MenuItem("AddressableEditor/SetGroup => StaticContent")]
public static void SetStaticContentGroup()
{
foreach (AddressableAssetGroup groupAsset in Selection.objects)
{
for (int i = 0; i < groupAsset.Schemas.Count; i++)
{
var schema = groupAsset.Schemas[i];
if (schema is UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema)
{
(schema as UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema)
.StaticContent = true;
}
else if (schema is UnityEditor.AddressableAssets.Settings.GroupSchemas
.BundledAssetGroupSchema)
{
var bundledAssetGroupSchema =
(schema as UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema);
bundledAssetGroupSchema.BuildPath.SetVariableByName(groupAsset.Settings,
AddressableAssetSettings.kLocalBuildPath);
bundledAssetGroupSchema.LoadPath.SetVariableByName(groupAsset.Settings,
AddressableAssetSettings.kLocalLoadPath);
}
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
[MenuItem("AddressableEditor/Build All Content")]
public static void BuildContent()
{
AddressableAssetSettings.BuildPlayerContent();
}
[MenuItem("AddressableEditor/Prepare Update Content")]
public static void CheckForUpdateContent()
{
//与上次打包做资源对比
string buildPath = ContentUpdateScript.GetContentStateDataPath(false);
var m_Settings = AddressableAssetSettingsDefaultObject.Settings;
List<AddressableAssetEntry> entrys =
ContentUpdateScript.GatherModifiedEntries(
m_Settings, buildPath);
if (entrys.Count == 0) return;
StringBuilder sbuider = new StringBuilder();
sbuider.AppendLine("Need Update Assets:");
foreach (var _ in entrys)
{
sbuider.AppendLine(_.address);
}
Debug.Log(sbuider.ToString());
//将被修改过的资源单独分组
var groupName = string.Format("UpdateGroup_{0}",DateTime.Now.ToString("yyyyMMdd"));
ContentUpdateScript.CreateContentUpdateGroup(m_Settings, entrys, groupName);
}
[MenuItem("AddressableEditor/BuildUpdate")]
public static void BuildUpdate()
{
var path = ContentUpdateScript.GetContentStateDataPath(false);
var m_Settings = AddressableAssetSettingsDefaultObject.Settings;
AddressablesPlayerBuildResult result = ContentUpdateScript.BuildContentUpdate(AddressableAssetSettingsDefaultObject.Settings, path);
Debug.Log("BuildFinish path = " + m_Settings.RemoteCatalogBuildPath.GetValue(m_Settings));
}
[MenuItem("AddressableEditor/Test")]
public static void Test()
{
Debug.Log("BuildPath = " + Addressables.BuildPath);
Debug.Log("PlayerBuildDataPath = " + Addressables.PlayerBuildDataPath);
Debug.Log("RemoteCatalogBuildPath = " + AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogBuildPath.GetValue(AddressableAssetSettingsDefaultObject.Settings));
}
}
}
//更新脚本,检查与下载
using System;
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 AddressableUpdater : MonoBehaviour
{
[SerializeField] private Text statusText;
[SerializeField] private GameObject downLoad;
[SerializeField] private GameObject startUp;
private bool checkingUpdate;
private bool needUpdate;
private bool isUpdating;
private float checkUpdateTime = 0;
private const float CHECKTIMEMAX = 5;
private List<string> needUpdateCatalogs;
private AsyncOperationHandle<List<IResourceLocator>> updateHandle;
private void Start()
{
StartCheckUpdate();
}
public void StartCheckUpdate()
{
statusText.text = "正在检测资源更新...";
Reg.PlatformAPI.SetAddressableMsg("正在检测资源更新...");
StartCoroutine(checkUpdate());
}
public void StartDownLoad()
{
if (needUpdate)
{
StartCoroutine(download());
}
}
IEnumerator checkUpdate()
{
checkingUpdate = true;
//初始化Addressable
var init = Addressables.InitializeAsync();
yield return init;
var start = DateTime.Now;
//开始连接服务器检查更新
AsyncOperationHandle<List<string>> handle = Addressables.CheckForCatalogUpdates(false);
//检查结束,验证结果
checkingUpdate = false;
Debug.Log(string.Format("CheckIfNeededUpdate use {0}ms", (DateTime.Now - start).Milliseconds));
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
List<string> catalogs = handle.Result;
if (catalogs != null && catalogs.Count > 0)
{
needUpdate = true;
needUpdateCatalogs = catalogs;
}
}
if (needUpdate)
{
//检查到有资源需要更新
statusText.text = "有资源需要更新";
Reg.PlatformAPI.SetAddressableMsg("有资源需要更新");
downLoad.SetActive(true);
startUp.SetActive(false);
StartDownLoad();
}
else
{
Reg.PlatformAPI.SetAddressableMsg($"Loading...");
//没有资源需要更新,或者连接服务器失败
Skip();
}
Addressables.Release(handle);
}
IEnumerator download()
{
var start = DateTime.Now;
//开始下载资源
isUpdating = true;
updateHandle = Addressables.UpdateCatalogs(needUpdateCatalogs, false);
yield return updateHandle;
Debug.Log(string.Format("UpdateFinish use {0}ms", (DateTime.Now - start).Milliseconds));
Reg.PlatformAPI.SetAddressableMsg($"下载完成");
//下载完成
isUpdating = false;
Addressables.Release(updateHandle);
Skip();
Reg.PlatformAPI.RestartShowSplash();
}
public void Skip()
{
SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive).completed += operation =>
{
SceneManager.UnloadSceneAsync(0);
};
}
private void Update()
{
if (checkingUpdate)
{
checkUpdateTime += Time.deltaTime;
if (checkUpdateTime > CHECKTIMEMAX)
{
//自测连接超时
checkingUpdate = false;
StopAllCoroutines();
Skip();
Debug.Log(string.Format("Connect Timed Out"));
}
}
if (isUpdating)
{
int progress = (int) (updateHandle.PercentComplete * 100);
statusText.text = $"正在更新资源... {progress}%";
Reg.PlatformAPI.SetAddressableMsg($"正在更新资源... {progress}%");
Reg.PlatformAPI.SetAddressablePro(progress);
}
}
}