Unity的Addressable系统提供的两种打包方式:local和remote。然而这与国内手游的更新逻辑有所不同。使用local方式能够进到安装包但是无法更新;使用了remote方式可以进行更新,但是不在安装包中,游戏第一次运行时需要进行下载。
手游更新逻辑通常是,安装包中保存完整的资源,第一次运行时无需再次进行长时间的更新,可以直接进入游戏,之后依然可以更新资源。这就需要资源在打包的时候放进StreamingAsset中,更新方式又需要设置为remote。
Unity提供了InternalIdTransformFunc
函数实现了URL重定向功能,这样资源请求时可以指定到StreamingAsset中。
实现过程分为:
- 打包过程中获取bundle文件及其列表文件
- 将bundle文件和列表文件拷贝到AA的LocalBuildPath文件夹中,AA的LocalBuildPath会在BuildPlayer时拷贝到StreamingAsset中
- 设置Addressables.InternalIdTransformFunc函数,重定向资源的URL
环境准备
- Packages:
- “com.unity.addressables”: “1.19.15”,
- “com.unity.nuget.newtonsoft-json”: “2.0.2”,用来写入json文件
- remote资源服务器直接使用了AAS自带的Host
工程设置
-
开启AAS的远程打包更新配置
- 设置Player Version Override
- 禁用Catalog自动更新(可选,会手动初始化)
- 开启BuildRemoteCatalog选项
-
添加图片到工程,并且配置到AAS中
代码部分
- 资源构建
public static class BuildEditor
{
[MenuItem("Tools/Build Resource #%&t")]
public static void BuildResource()
{
var buildRootDir = GetAASRemoteBuildDir();
//删除原有文件夹
GlobalFunc.DeleteFolder(buildRootDir);
//执行打包逻辑
AddressableAssetSettings.BuildPlayerContent(out var result);
BuildResource_CacheBundleList(result);
BuildResource_CopyBundleDirToAAS();
Debug.Log($"build resource finish");
}
/// <summary>
/// 获取RemoteBuild文件夹路径
/// </summary>
/// <returns></returns>
public static string GetAASRemoteBuildDir()
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
var profileSettings = settings.profileSettings;
var propName =
profileSettings.GetValueByName(settings.activeProfileId, AddressableAssetSettings.kRemoteBuildPath);
var remoteBuildDir = profileSettings.EvaluateString(settings.activeProfileId, propName);
return remoteBuildDir;
}
/// <summary>
/// 生成BundleCache文件列表
/// </summary>
/// <param name="result"></param>
static void BuildResource_CacheBundleList(AddressablesPlayerBuildResult result)
{
var buildRootDir = GetAASRemoteBuildDir();
var buildRootDirLen = buildRootDir.Length;
List<string> allBundles = new List<string>();
var filePathList = result.FileRegistry.GetFilePaths().Where((s => s.EndsWith(".bundle")));
foreach (var filePath in filePathList)
{
var bundlePath = filePath.Substring(buildRootDirLen + 1);
allBundles.Add(bundlePath);
}
var json = JsonConvert.SerializeObject(allBundles);
File.WriteAllText($"{buildRootDir}/{GlobalFunc.Caching}.json", json);
}
/// <summary>
/// 拷贝Bundle到AAS,并随AAS进包
/// </summary>
static void BuildResource_CopyBundleDirToAAS()
{
var remoteBuildDir = GetAASRemoteBuildDir();
var aaDestDir = $"{Addressables.BuildPath}/{GlobalFunc.Caching}";
GlobalFunc.DeleteFolder(aaDestDir);
Directory.CreateDirectory(aaDestDir);
//拷贝bundle到aa文件夹
var allSrcFile = Directory.EnumerateFiles(remoteBuildDir, "*.*", SearchOption.AllDirectories);
foreach (var srcFile in allSrcFile)
{
var fileName = Path.GetFileName(srcFile);
var destFile = $"{aaDestDir}/{fileName}";
File.Copy(srcFile, destFile);
}
Debug.Log($"Bundle拷贝完毕:{remoteBuildDir}==>{aaDestDir}");
}
}
- 运行时代码
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SocialPlatforms;
using UnityEngine.UI;
public class GameMain : MonoBehaviour
{
public RawImage Raw;
private List<string> _bundleCacheList = new List<string>();
// Start is called before the first frame update
IEnumerator Start()
{
Debug.Log(Application.persistentDataPath);
yield return Addressables.InitializeAsync();
var checkHandle = Addressables.CheckForCatalogUpdates(false);
yield return checkHandle;
if (checkHandle.Result.Count > 0)
{
yield return Addressables.UpdateCatalogs(checkHandle.Result);
}
Addressables.Release(checkHandle);
//从StreamingAsset中获取bundle列表文件
var bundleCacheFileURL = $"{Addressables.RuntimePath}/{GlobalFunc.Caching}/{GlobalFunc.Caching}.json";
#if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR
var url = bundleCacheFileURL;
#else
var url = Path.GetFullPath(bundleCacheFileURL);
#endif
var request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();
if (!string.IsNullOrEmpty(request.error))
{
Debug.LogError(request.error);
}
Debug.Log($"BundleCache:{request.downloadHandler.text}");
_bundleCacheList = JsonConvert.DeserializeObject<List<string>>(request.downloadHandler.text);
Addressables.InternalIdTransformFunc = Addressables_InternalIdTransformFunc;
//开始业务逻辑
StartLogic();
}
string Addressables_InternalIdTransformFunc(IResourceLocation location)
{
if (location.Data is AssetBundleRequestOptions)
{
if (_bundleCacheList.Contains(location.PrimaryKey))
{
var fileName = Path.GetFileName(location.PrimaryKey);
//使用LogError用来测试是否使用了StreamingAsset缓存
Debug.LogError($"StreamingAssetCache:{location.PrimaryKey}");
return $"{Addressables.RuntimePath}/{GlobalFunc.Caching}/{fileName}";
}
}
return location.InternalId;
}
void StartLogic()
{
var h = Addressables.LoadAssetAsync<Texture>("pic/1.jpg");
h.Completed += handle => { Raw.texture = h.Result; };
}
}
运行测试
-
构建安装包,并运行。可以看到使用了缓存资源
-
再次构建资源后,重新运行程序。可以看到图片资源已经更新,并且没有出现缓存的log。
-
经测试安卓端正常运行,其中需要注意的是Caching.json时,根据平台不同需要对URL的处理也不同
#if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR
var url = bundleCacheFileURL;
#else
var url = Path.GetFullPath(bundleCacheFileURL);
#endif
最后附上工程链接:https://github.com/ZiJinZiMing/Demo4InternalIdTransformFunc