Skip to main content
Version: 2022.2

例子

提供了在 Unity 中使用 xasset 打包资产、上传资产、发布版本更新信息、打包安装包、初始化、更新资产版本、加载 Unity 资产、加载 Unity 场景、加载原始资产、清理下载数据的操作步骤和示例代码说明。

打包资产

注意

开源版的 xasset 没有提供配置驱动的分布式打包工具,可以使用 Unity 的 AssetBundleBrowser 窗口来为资产分配 AssetBundle 到打包配置。

第1步: 创建 Group 配置。 可以在 Unity 编辑器中选择使用 Assets>Create>xasset>Group 来实施。 创建好后,可以批量拖拽资产文件或文件夹到 Group 配置的 Entries 属性来为 Group 添加要打包的资产节点。 注意: 这个步骤可以重复多次,直到把所有需要在代码中主动加载的资产全部用 Group 配置覆盖,只有被 Group 配置覆盖到的资产才能被 xasset 打包和加载使用。并且,Group 配置需要添加到 Build 配置才能生效。

build-bundles-create-group

第2步: 创建 Build 配置。可以 Unity 编辑器中选择使用 Assets>Create>xasset>Build 来实施。 Build 配置创建后,可以把【第1步】中创建的没有相互依赖的 Group 配置添加到同一个 Build 配置,可以在 Unity 编辑中批量拖拽到创建后的 Group 配置到 Build 配置的 Parameters > Groups 属性来实施。 注意: 这个步骤可以重复多次,直到所有的 Group 配置都添加到了 Build 配置,并且,同一个 Group 配置不能添加到多个 Build 配置。

build-bundles-create-build

第3步: 打包资产。可以在 Unity 编辑器中选择使用 xasset > Build > Bundles 来实施。打包资产开始时,xasset 会对 Build 配置进行引用关系进行检查,如果没有异常,等打包完成后,会在控制台输出如下打包日志。 注意: 使用 xasset > Build > Bundles 打包资产时,默认,会针对所有的 Build 配置进行打包,而选中一个或多个 Build 配置时,只会针对选中的部分进行打包。

build-bundles-console-log

上传资产

打包资产完成后,会在 Unity 工程跟目录下创建 Bundles 和 BundlesCache 两个目录,最终打包输出的资产会按平台名字输出到这两个目录下。 BundlesCache 是用于编辑器增量打包的缓存数据,运行时用不到。 Bundles 则是运行时使用的数据目录,可以直接把这个目录的资产上传到 CDN。 注意:和一些老方案不同,xasset 打包后输出的文件的文件名会附带文件内容的 hash,所以,资产上传到 CDN 是不需要按版本号存的,这样可以大幅减少 CDN 存储空间的占用并通过增量上传,加快部署流程。

build-bundles-outputpath

发布更新信息

打包资产完成后,会在 Bundles 目录下输出 updateinfo.json。这个文件定义了客户端资产版本的更新信息。 在生产环境中,可以把这个文件提供给后台来控制客户端的内容分发,客户端在启动后,可以从后台获取这个文件,判断资产是否有更新。 updateinfo.json 的内容大致如下:

{
// 版本文件的文件名
"file": "versions_v1.1.json",
// 版本文件文件内容的 hash
"hash": "b8df4af41fe3624a717edf38ee284767",
//版本文件的大小
"size": 298,
// 版本文件的时间戳,客户端通过这个时间戳判断本地的资产是否需要更新
"timestamp": 133026096481856054,
// 客户端资产更新的下载地址。
"downloadURL": "http://127.0.0.1/Bundles/WebGL/"
}

打包安装包

在 Unity 编辑器中,选择使用 xasset > Build > Player 可以一键构建安装包。 安装包中的场景来自 Unity 编辑器的 File > BuildSettings... > SceneInBuild 中的内容。打包后的安装包,默认会按平台名字输出到 Build 目录下。

build-player

如上图,这种方式打包安装包的命名格式是:

项目名字-版本号-时间戳

另外,还可以选择使用 Unity 编辑器中默认的 File > Build Settings...File > Build And Run 打包安装包。但不论以何种方式打包安装包,xasset 都会根据 Settings 配置中的 Player Assets Split Mode 选项自动添加包含在安装包中的资产到构建的安装包中。 但在高版本的 Unity 中的 Android 平台下,打包后在真机运行,如果资产 IO 时间慢的离谱,可能需要修改 gradle 不压缩 StreamingAssets 的中包含的资产,这样可以优化加载资产的 IO 时间,不过安装包可能会大一些。

android {
...
aaptOptions {
noCompress 'bundle' // or whatever extension you use
}
}
提示

xasset 针对安装包打包的过程做了特殊优化,打包前会自动把需要包含到安装包的资产复制到 Unity 项目的 Assets > StreamingAssets 目录,这些内容在打包后会自动删除。这样可以减少可能出现的漫长的资产导入等待时间。

初始化 xasset

使用 Assets.InitializeAsync 可以让 xasset 进行初始化。只有初始化好后,xasset 才能正常执行获取更新大小、资产加载、场景加载等流程。xasset 提供了通用的 Startup 组件封装了常规的初始化流程。可以直接使用,也可以按自己项目的实际需要参考使用。

public class Startup : MonoBehaviour
{
[SerializeField] private bool autoUpdate = true;
[SerializeField] private bool fastVerifyMode = true;
[SerializeField] private bool simulationMode = true;
[SerializeField] private UnityEvent completed;

[Conditional("UNITY_EDITOR")]
private void Awake()
{
Assets.SimulationMode = simulationMode;
}

private IEnumerator Start()
{
Assets.FastVerifyMode = fastVerifyMode;
DontDestroyOnLoad(gameObject);
var initializeAsync = Assets.InitializeAsync();
yield return initializeAsync;
Debug.Log($"[{nameof(Assets)}] Initialize with: {initializeAsync.result}.");
Debug.Log($"API Version:{Assets.APIVersion}");
Debug.Log($"Simulation Mode: {Assets.SimulationMode}");
Debug.Log($"Versions: {Assets.Versions}");
Debug.Log($"Platform Name: {Assets.PlatformName}");
// 安装包完全不带资产的时候,可以从服务器下载版本文件和资产。
if (autoUpdate)
{
// 获取服务器的更新信息。
var getUpdateInfoAsync = Assets.GetUpdateInfoAsync();
yield return getUpdateInfoAsync;
if (getUpdateInfoAsync.result == Request.Result.Success)
{
var getVersionsAsync = Assets.GetVersionsAsync(getUpdateInfoAsync.info);
yield return getVersionsAsync;
if (getVersionsAsync.versions != null)
{
getVersionsAsync.versions.Save(Assets.GetDownloadDataPath(Versions.Filename));
Assets.Versions = getVersionsAsync.versions;
}
}
}
completed?.Invoke();
}
}

更新资产版本

使用 Assets.GetUpdateInfoAsync 可以获取客户端资产版本是否需要更新。 Assets.GetUpdateInfoAsync 主要是向后台获取更新信息这个更新信息就是打包资产的时候生成的 updateinfo.json。 在调用 Assets.GetUpdateInfoAsync 前需要按项目实际情况实现一个 GetUpdateInfoRequestHandler 来获取更新信息。

public readonly struct GetUpdateInfoRequestHandlerX : GetUpdateInfoRequestHandler
{
public static GetUpdateInfoRequestHandler CreateInstance()
{
return new GetUpdateInfoRequestHandlerSimulation();
}

public void Start(GetUpdateInfoRequest request)
{
// TODO: Start 方法会在请求启动的时候调用。这时候可以和后台通信,把客户端的平台渠道信息等数据发送给后台,
// 让后台返回 updateinfo.json 的数据,获取到后台返回的 updateinfo.json 之后可以直接更新到 Assets.UpdateInfo。
// 这个操作就是把 Assets.UpdateInfo 的值 copy 到 request.info。
request.OnLoadUpdateInfo();
// 通过比对时间戳判断是否需要更新。这里暂不考虑后台没有正常返回数据的情况。
if (request.info.timestamp > Assets.Versions.timestamp)
request.SetResult(Request.Result.Success);
else
request.SetResult(Request.Result.Failed, "Nothing to update.");
}

public void Update(GetUpdateInfoRequest request)
{

}

public void Complete(GetUpdateInfoRequest request)
{
}
}

在调用 Assets.GetUpdateInfoAsync 之前,可以使用 GetUpdateInfoRequest.CreateHandler 修改默认返回的更新信息的实现。

GetUpdateInfoRequest.CreateHandler = GetUpdateInfoRequestHandlerX.CreateInstance;

使用 Assets.GetUpdateInfoAsync 获取到返回的更新信息后,就可以按需更新资产的版本信息。

// 获取服务器的更新信息。 
var getUpdateInfoAsync = Assets.GetUpdateInfoAsync();
yield return getUpdateInfoAsync;
if (getUpdateInfoAsync.result == Request.Result.Success) // 需要更新的时候,这个 result 要返回 Success
{
// 从服务器下载版本文件和资产。
var getVersionsAsync = Assets.GetVersionsAsync(getUpdateInfoAsync.info);
yield return getVersionsAsync;
if (getVersionsAsync.versions != null)
{
// 查询 assetContainer.GetAssets() 返回的资产是否需要下载更新。
var getDownloadSizeAsync = Assets.GetDownloadSizeAsync(versions, assetContainer.GetAssets());
yield return getDownloadSizeAsync;
if (getDownloadSizeAsync.downloadSize > 0)
{
var downloadAsync = request.DownloadAsync();
while (downloadAsync.result != DownloadResult.Success)
{
var downloadedBytes = Utility.FormatBytes(downloadAsync.downloadedBytes);
var downloadSize = Utility.FormatBytes(downloadAsync.downloadSize);
var bandwidth = Utility.FormatBytes(downloadAsync.bandwidth);
var msg = $"{Constants.Text.Loading}{downloadedBytes}/{downloadSize}, {bandwidth}/s";
LoadingScreen.Instance.SetProgress(msg, downloadAsync.progress);
yield return null;
if (!downloadAsync.isDone || string.IsNullOrEmpty(downloadAsync.error)) continue;
var ar = MessageBox.Show(Constants.Text.Tips, Constants.Text.TipsDownloadFailed);
yield return ar;
if (ar.result == Request.Result.Success) downloadAsync.Retry();
else break;
}
if (downloadAsync.result != DownloadResult.Success) yield break;
// 清理历史文件
var bundles = new HashSet<string>();
foreach (var item in versions.data)
{
bundles.Add(item.file);
foreach (var bundle in item.manifest.bundles)
bundles.Add(bundle.nameWithAppendHash);
}
var files = new List<string>();
foreach (var item in Assets.Versions.data)
{
if (!bundles.Contains(item.file))
files.Add(item.file);
foreach (var bundle in item.manifest.bundles)
if (!bundles.Contains(bundle.nameWithAppendHash))
files.Add(item.file);
}

var removeAsync = new RemoveRequest();
foreach (var file in files)
{
var path = Assets.GetDownloadDataPath(file);
removeAsync.files.Add(path);
}

removeAsync.SendRequest();
while (!removeAsync.isDone)
{
LoadingScreen.Instance.SetProgress($"清理历史文件 {removeAsync.current}/{removeAsync.max}", removeAsync.progress);
yield return null;
}
// 下载完成后再覆盖版本文件,下次启动就能直接加载新版本的资产。
Assets.Versions = versions;
versions.Save(Assets.GetDownloadDataPath(Versions.Filename));
UpdateVersion();
}
}
}

加载 Unity 中的资产

使用 Asset.Load(Async) 可以加载 Unity 中的资产。

var request = Asset.Load(path, type);
var asset = request.asset; // 获取 Unity 中的资产。
// 资产不需要使用的时候可以调用 Release 释放。
request.Release();

xasset 在 Windows、OSX、Android、iOS、WebGL 等平台都是使用目标资产在 Unity 编辑器工程下相对 Assets 目录的路径加载。 并且会自动管理依赖关系,已经加载的内容不会重复加载,同时在加载到本地没有的内容的时候,底层会自动去服务器下载再加载,已经加载的内容,在更新后会自动热重载。 另外,使用 Asset.InstantiateAsync 可以异步实例化一个 GameObject 到当前场景。

var request = Asset.InstantiateAsync(path, parent, worldPositionStays);
yield return request;
var gameObject = request.gameObject;

异步实例化的 gameObject 在 gameObject 被销毁时,会自动释放相关资产。所以不需要自行调用 Release。

提示

Unity 中的资产主要包括 Texture、Sprite、TextAsset、GameObject、Shader、AnimationClip、AudioClip、VideoClip、ScriptableObject 等类型的资产。

加载 Unity 中的场景

使用 Scene.Load(Async) 可以加载 Unity 中的场景。

// 加载一个 Unity 中的 Single 场景
var single = Scene.LoadAsync(path);
// 让场景加载后不激活
single.allowSceneActivation = false;
yield return single;
// 等可以激活的时候再激活
single.allowSceneActivation = true;
// 在加载新的 Single 场景时,之前加载的所有场景都会自动回收,所以 Single 场景不需要主动 Release;

Unity 中的场景有Single 和 Additive 两种类型,Single 场景比较特殊,每次加载新的 Single 场景后,之前加载的所有类型的场景全部会自动释放。 所以,Single 场景无需主动调用 Release。和 Single 场景不同,Additive 场景允许我们在切入新的 Single 场景前按需 Release。

// 加载 Additive 场景
var additive = Scene.LoadAsync(path, true);
// 主动释放 Additive 场景
additive.Release();

注意: 不管场景是否已经加载好,只要调用了 Release 就能正常释放。这样在开发业务的时候,相对更轻松。

加载原始资产

使用 RawAsset.Load(Async) 可以加载非 Unity 中的资产。一些第三方插件的资产,例如,Wwise、FMOD、AVPro、EasyAR等 ,没有使用 Unity 中的资产来获取目标内容的数据的时候,可以用 RawAsset 来加载。

var request = RawAsset.LoadAsync(path);
// 让异步加载立即同步完成
request.WaitForCompletion();
// 读取资产数据。需要注意的是,在 Android 平台,使用 AssetPack 的时候,需要按 request.offset 读取数据
var bytes = File.ReadAllBytes(request.savePath);
// 不用的时候也可以 Release
request.Release();
建议

能用 Unity 中的资产就用 Unity 中的资产,这样在跨平台的时候,可以具备更好的兼容性。

自动回收资产

xasset 提供了 AutoreleaseCache 来简化 Unity 对象相关资产的生命周期管理。

var asset = Asset.LoadAsync(path, type); 
// 为 go 创建自动回收的资产池。
var cache = AutoreleaseCache.Get(go);
// 将 asset 添加到 go 的自动回收资产池 assets 中,不再需要手动调用 asset.Release 来回收资产
cache.Add(asset);
// 在 go 被销毁时,assets 中的资产会自动回收。或者主动调用 assets.Clear 清理池中的资产。
cache.Clear();

另外,还可以使用 QueueCache 来简化符合先进先出规律的 Unity 对象相关生命周期的管理。

// 为 go 创建 先进先出 的自动回收的资产池
var cache = QueueCache.Get(go);
// 让资产池最大保留 5 个对象。
cache.maxQueueSize = 5;
// 从池中加载对象,超过 5 次时,第一个对象会被自动释放,同时,go 在销毁时,整个池的资产也会自动释放。
var asset = Asset.LoadAsync(path, type);
cache.Add(asset);

和 AutoreleaseCache 一样,QueueCache 中缓存的资产在其关联的 Unity 对象销毁时,都会自动回收。