自2022年9月6日起,本文档站不再更新内容,相关文档已迁移至全新“抖音开放平台”前往

小包化 API

Namespace

using StarkMini;

1. 用 address 异步加载单个资源 Asset

异步加载单个 asset

调用:ResLoader.LoadAssetAsync<T>(key)

泛型:T:需要指定资源类型,例如 GameObject,Sprite,Texture。

参数:key: asset 配置的 address

返回: handle 结构体

// 加载一个address配置为 "Avatars/hero_1" 的资源
var handle = ResLoader.LoadAssetAsync < GameObject > "Avatars/hero_1";

注:泛型 T 也可以是父类 UnityEngine.Object,但对部分特定类型资源(最常见的就是 Sprite 和 Texture2D),会有可能无法保证得到你真正要使用的类型。因为对于这种资源,可能 bundle 中打入了 2 个或以上不同类型的资源对象,但是同一个 address。

例如:用<UnityEngine.Object>这个父类类型加载一个图片,可能在 Editor 下返回的实际是 Sprite 对象、而在发布的版本运行时返回的是 Texture 对象。

因此:建议尽量指定你确定要使用的类型:例如对于 Sprite, 或 Texture2D (Texture),分别使用 ResLoader.LoadAssetAsync("hero_head.png"), ResLoader.LoadAssetAsync("hero_skin.psd") 来区分两种类型的加载。

2.判断、处理异步加载结果

方法 1:可以直接设置 onLoad 回调,处理加载结果。

注:这些回调都是已经成功加载的。

// example 1.1 Prefab GameObject
ResLoader.LoadAssetAsync <
  GameObject >
  ("Avatars/hero_1",
  (res) => {
    // 使用加载到的 (GameObject) res 对象
    var go = Instantiate(res);
  });
// example 1.2 AudioClip
ResLoader.LoadAssetAsync <
  AudioClip >
  ("sandloop.wav",
  (res) => {
    // 使用加载到的 (AudioClip) res 对象
    sandLoopSource.clip = res;
  });

方法 2:异步 async await 的写法(需要自己判断是否成功)

注:Unity 中 async await Task 类似协程,会保证在主线程运行。(这一点区别于原生的 C#)

注:原 Unity lifecycle 的 Start 等方法,也可以直接改造为 async。

public async void Foo() {
    var handle = ResLoader.LoadAssetAsync<GameObject>(key);
    // 等待
    await handle.Task;
    // 判断是否成功
    if (handle.Status == AsyncOperationStatus.Succeeded) {
        var res = handle.Result;
        // 处理成功结果 ...
    } else {
        // 处理失败
    }
}

// 原 Unity lifecycle 的 Start 等方法,也可以直接改造为 async.
public async void Start() {
    var handle = ResLoader.LoadAssetAsync<GameObject>(key);
    await handle.Task;
    // 处理结果 ...

    // 继续原同步游戏逻辑 sync logic ...
}

方法 3:异步 Couroutine 协程的写法

注:与 async 类似。

public IEnumerator Foo() {
    var handle = ResLoader.LoadAssetAsync<Texture2D>("mytexture");
    yield return handle;
    // 处理结果 ...
}

3.加载成功后,用 ResCache 同步读取

一个资源加载成功后,可以从资源缓存中同步读取(ResLoader 会缓存已加载对象)。

调用:ResCache.Get<T>(key)

泛型:T:需要指定资源类型

参数:key: asset 配置的 address

返回: T 类型的资源对象

public Avatar GetPrefab()
{
    return ResCache.Get<Avatar>("Avatars/" + prefabName);
}

说明:对比原 Resources.Load,是相同接口形式:

return Resources.Load < Avatar > "Avatars/" + prefabName;

如果需要判断一个资源是否已经被 cache 缓存,可调用

bool Has<T>(string key)

说明:ResCache.Get 的读取,是依赖于: ResLoader 的大部分 Load 方法,默认用了参数 bool useResCache = true,会在加载成功时同时把该资源引用保存在 ResCache 缓存。

4.用 label 异步加载一批资源 Assets

异步加载一批 assets

调用:ResLoader.LoadAssetsAsync<T>(key)

参数:key: 配置的一系列资源的 label (打的标签)

返回:handle 结构体

示例代码:

public async void Load() {
    var handle = ResLoader.LoadAssetsAsync<UnityEngine.Object>("preload");
    // 等待
    await handle.Task;
    // 判断是否成功、加载结果处理方法,与 LoadAssetAsync 相同 ...
    // 区别:返回的 handle.Result 是 IList<T>
    var results = handle.Result;
}
void Update() {
    // 检查IsValid(),如果handle已释放,会返回false
    if (!this.handle.IsValid())
        return;

    if (this.handle.isDone) {
        // 已完成。
    } else {
        // 获取加载进度的两种方式:
        var progress = this.handle.PercentComplete;
        var downloadStatus = this.handle.GetDownloadStatus();
    }
}

注:自 StarkMini 版本 v1.2 起,LoadAssetsAsync 也统一处理 ResCache 资源缓存。即,加载成功资源后,同样可以从 ResCache 读取。

如果希望 LoadAssetsAsync 不进行资源缓存,传入 bool useResCache = false 即可。

5.通过 handle 获取加载进度

需要注意区分两种方式:

handle.PercentComplete

float PercentComplete // 加载任务的进度百分比,范围 0 ~ 1 的 float, 仅按涉及的bundle数量计算完成度,不精确计算每个bundle的大小。

举例:涉及 9 个文件每个 1 MB、1 个 10 MB 文件时,这个大文件计算 PercentComplete 时占 10%。

handle.GetDownloadStatus()

DownloadStatus GetDownloadStatus() // 下载的进度,会综合计算依赖的每个bundle、及其文件大小,可以进一步取得 Percent 百分比、DownloadedBytes、TotalBytes 字节数。
// 包括属性:
float Percent; // 下载进度百分比,范围 0 ~ 1 的 float
long DownloadedBytes; // 已下载字节数
long TotalBytes; // 总字节数
bool IsDone; // 是否下载完成

举例:涉及 9 个文件每个 1 MB、1 个 10 MB 文件时,这个大文件计算 GetDownloadStatus().Percent 时占 52.6%。

注:按照 Addressable 和 Unity AssetBundle 实现机制,一个加载任务的 PercentComplete 任务进度值,和 bundle 下载状态 GetDownloadStatus() 的 percent 下载进度值, 在任务失败时为 1.0f 是设计如此。

AA、AB 进度百分比值(过程中、完成、失败后)的机制说明:

  • 加载的过程中: 任务进度:按 已加载完成(包括失败)的任务数 / 总任务数 计算, 下载进度百分比:按 已下载数据字节数 / 总需要字节数 计算。 如果总分母是 0,视为无需下载,那么百分比为 1.0f。 已下载字节数:按 实际已下载的字节数
  • 加载完成包括失败时: 任务进度:总是返回 1.0f; 下载进度百分比:总是返回 1.0f; 已下载字节数:总是返回 总需要字节数

6.多个加载任务的组合

方法 1:在 async 函数、或 coroutine 函数中,调用并等待多个加载任务

注意:需要自己判断每个 handle 是否成功 success

示例代码:

async Task<bool> LoadManyAssets()
{
    var isSuccess = true;
    // 同时发起加载(底层关联bundle会并发下载)
    var handle1 = ResLoader.LoadAssetAsync<Object>(key);
    var handle2 = ResLoader.LoadAssetsAsync<Object>(label);
    // 等待
    await handle1.Task;
    await handle2.Task;
    // 判断是否成功
    isSuccess = isSuccess && handle1.Status == AsyncOperationStatus.Succeeded;
    isSuccess = isSuccess && handle2.Status == AsyncOperationStatus.Succeeded;
    return isSuccess;
}

方法 2:使用 LoadTaskList组合多个加载任务:

创建对象:var loadList = LoadTaskList.Create()

方法:Add<T>(handle)// 添加管理一个 asset 加载任务的 handle

方法:LoadAll()// 加载所有任务,异步返回 Task isSuccess 结果

属性:IsDone// 是否所有加载完成(包括成功或失败)

属性:IsSucceeded// 是否所有加载任务成功

属性:PercentComplete// 加载任务进度, 范围为 0 ~ 1 的 float

属性:DownloadStatus GetDownloadStatus() // 下载的进

示例代码:

async Task<bool> LoadStageAssets() {
    var loadList = LoadTaskList.Create();
    // 添加 handle
    foreach (var data in stageDataList)
    {
        loadList.Add(data.LoadPrefabHandle());
    }
    // 添加更多 handle
    loadList.Add(ResLoader.LoadAssetAsync<GameObject>("asset_1"));
    loadList.Add(ResLoader.LoadAssetAsync<GameObject>("asset_2"));
    // 等待全部,返回是否全部成功
    var isSuccess = await loadList.LoadAll();
    // 也可以通过属性,判断是否全部成功
    isSuccess = loadList.IsSucceeded;
    return isSuccess;
}
void Update() {
    var isDone = loadList.IsDone;
    var progress = loadList.PercentComplete;
    var downloadStatus = loadList.GetDownloadStatus();
    var downloadPercent = downloadStatus.Percent;
}

7.预加载后续资源、队列

预加载后续即将用到的资源,如果多个同时发起,会同时并发下载:

  • 调用: ResLoader.PreDownload// 只做下载,不处理具体资源,不触发 ResCache 资源缓存
  • 参数:key:asset group中配置的 label
  • 返回:void

也可以使用常规的加载一批资源的方法,但不做等待:

  • 调用: ResLoader.LoadAssets// 常规加载,会处理具体资源,会触发 ResCache 资源缓存

也可以加入后台队列下载资源,依次排队下载,而不会多个并发:

  • 调用:BackgroundLoader.AddDownloadOnly 接口 // 只做下载

BackgroundLoader.AddAssets<T>接口 // 会处理具体资源、和 ResCache

  • 参数:key: asset group 中配置的 label
  • 返回:void

示例代码:

// 预加载。多个同时发起,会同时并发下载
ResLoader.PreDownload("p1_fonts");
ResLoader.PreDownload("p1_levels");
ResLoader.PreDownload("p1_scenes");

// 调用常规加载,但不做等待
ResLoader.LoadAssets < AudioClip > "g1_sounds";

// 后台加载队列
BackgroundLoader.Start();
BackgroundLoader.AddDownloadOnly("p2_icons");
BackgroundLoader.AddDownloadOnly("p2_levels");
BackgroundLoader.AddAssets < AudioClip > "p2_sounds";

预加载,如果需要等待其完成、执行相应逻辑:

调用: ResLoader.PreDownloadAsync // 只做下载,不处理具体资源,不触发 ResCache 资源缓存

参数:key: asset group中配置的 label

返回:Task<bool>,携带 (bool) isSuccess 的 Result

调用: LoadAssetsAsync<T>// 常规加载,会处理具体资源,会触发 ResCache 资源缓存

参数:key: 配置的一系列资源的 label (打的标签)

返回:handle 结构体

示例代码:

var isSuccess = await ResLoader.PreDownloadAsync(key);
var handle = ResLoader.LoadAssetsAsync < TObject > key;
await handle.Task;

8.后台下载所有资源 DownloadAll

注:自版本 1.4.0 起支持

DownloadAll 方法:DownloadAllInTheBackground

// 后台下载全部addressable内容,这个接口是启动空闲下载,所有的key是chain的方式一个接一个下载,且只在空闲时下载
Addressables.DownloadAllInTheBackground();

// 参数 `maxActiveWebRequest` 最大允许的并发连接数,只有小于这个连接数才继续下载</param>
const int maxActiveWebRequest = 3;
Addressables.DownloadAllInTheBackground(maxActiveWebRequest);

(注:此功能实验中、改进中)

9.异步加载场景 Scene,和失败重试

调用 ResLoader.LoadSceneAsync 接口

参数:key: address

参数:mode: 场景模式,枚举:single, additive

var handle = ResLoader.LoadSceneAsync("battle_scene", LoadSceneMode.Single);
var handle = ResLoader.LoadSceneAsync("result_dialog", LoadSceneMode.Additive);

示例代码 - async 方式:

async void LoadSceneAddressable(string key, LoadSceneMode mode) {
    bool isSuccess = false;
    bool loop = true;
    while (!isSuccess && loop) {
        var handle = ResLoader.LoadSceneAsync(scene.ToString(), mode);
        await handle.Task;
        isSuccess = handle.Status == AsyncOperationStatus.Succeeded;
        if (!isSuccess) {
            // load error
            Debug.LogWarning($"LoadScene - fail! \"{scene.ToString()}\"");
            // 错误处理、弱网兜底重试:
            // 可选a. 可以做循环重试的间隔 delay in millisecond between retries
            await Task.Delay(retryDelayMs);
            // 可选b. 可以做循环自定义错误弹框
            m_isErrorConfirmed = false;
            InvokeNetErrorPopup(OnNetErrorConfirm);
            while (!m_isErrorConfirmed) {
                await Task.Yield();
            }
            // 可选c. 可以做等待弹窗按钮确认:“加载失败,请检查网络” “重试”
            await LoadErrorPopupView.PopupAsync(LoadErrorPopupView.ErrorMsg, LoadErrorPopupView.RetryText);
        }
        loop = !isSuccess && Application.isPlaying;
    }
    // load success
}
private bool m_isErrorConfirmed = false;
void OnNetErrorConfirm() {
    m_isErrorConfirmed = true;
}

注:这个 LoadErrorPopupView 界面所用的资源在Packages/com.bytedance.starkminiunity/Runtime/Examples/UI/StarkMiniUICanvas.prefab, 需要将这个 prefab 拖动到自己工程的 Scene 中,它会保持单例 DontDestroyOnLoad

示例代码(Coroutine 方式):

IEnumerator LoadSceneAddressable(GameScenes scene, LoadSceneMode mode) {
    bool isSuccess = false;
    bool loop = true;
    while (!isSuccess && loop) {
        var handle = ResLoader.LoadSceneAsync(scene.ToString(), mode);
        // 注意: yield return handle 必需被执行在 MonoBehaviour 的 StartCoroutine() 中,才能保证正确等待。
        if (!handle.IsDone) {
            yield return handle;
        }
        // 注: 也可以用 while 循环判断 !IsDone
        while (!handle.IsDone) {
            yield return null;
        }
        isSuccess = handle.Status == AsyncOperationStatus.Succeeded;
        if (!isSuccess) {
            Debug.LogWarning($"LoadScene - fail! \"{scene.ToString()}\"");
            {
                // 同上 async方式,错误处理、弱网兜底重试
                // ...
            }
        }
        loop = !isSuccess && Application.isPlaying;
    }
    // load success
}

检查工程的 Scenes in build 设置,应当这些转为 addressable 的 scene 都已经自动去除勾选。

说明:如何卸载 AA 场景

被动卸载、自动卸载的情况:如果加载的场景是 single 模式,那么新的场景加载时,原场景就会自动被卸载。

主动卸载、手动卸载,两种接口都可以:

用 UnityEngine 传统的 SceneManager 来卸载场景,会自动兼容处理 AA 场景

SceneManager.UnloadSceneAsync(...)

用 Addressables.UnloadSceneAsync 接口来卸载场景

var unloadHandle = Addressables.UnloadSceneAsync(currentScene);
// 返回类型为:AsyncOperationHandle<SceneInstance> 卸载场景操作的Handle
// 视情况不等待,或异步等待 unloadHandle
 // a. async 方式
 await handle.Task;
 // b. coroutine 方式
 yield return handle;

10.方法:AssetReference 与弱引用

AssetReference是一种 Addressable 中提供的类,用于方便引用一个 Addressable 的 Asset。

特点:

  • 是一个弱引用,不会产生强引用的依赖关系。
  • 用于 Editor 下设置引用关系,Build 时弱引用不会依赖打包到一起,Runtime 运行时做动态加载。
  • 非常适合解决一些资源引用依赖关系会导致 bundle

基本用法:

声明成员变量代码

public AssetReference assetRef;

编辑器 Inspector,指向引用的 asset 资源

示例代码:运行时加载

// 方法1 用ResLoader的static方法
 var handle = ResLoader.LoadAssetAsync<ScriptableObject>(assetRef);
 // 方法2 用AssetReference的成员方法
 var handle = assetRef.LoadAssetAsync<ScriptableObject>();

常见强引用问题和弱引用解决方法:

参见:

11.动态加载特定资源类型

示例:动态加载界面 UI Sprite 图片

示例代码:

public UnityEngine.UI.Image skinImage;
public string id;

void Show()
{
    ResLoader.LoadUISpriteAsync(skinImage, "Skins/" + id);
}

示例:动态加载和播放声音

注:既然做动态加载,一般相应地把 Scene、Prefab 中本来直接引用的 AudioClip 去除置空。

示例代码:

void PlaySe(string se)
{
    var sound = ResCache.Get<AudioClip>("Sound/" + se);
    if (sound != null) {
        // 已加载过,有缓存,设置clip,直接播放
        audioSource.clip = res;
        audioSource.Play();
    } else {
        // 没有缓存,只做加载
        ResLoader.LoadAssetAsync<AudioClip>("sound_effect_key");
    }
}

void InitMusic()
{
    ResLoader.LoadAssetAsync<AudioClip>("music_key", (res) => {
        // 加载成功,设置clip
        musicSource.clip = res;
        // 自定义逻辑判断是否要播放
        if (!musicSource.isPlaying && Setting.MusicOn) {
            musicSource.Play();
        }
    });
}

示例:动态加载字体

注:既然做动态加载,一般相应地把 Scene、Prefab 中本来直接引用的 Font 去除置空。

示例代码:

private Font m_font;
public Font FontRes => m_font;

async void LoadFont(string path) {
    var isSuccess = false;
    // 此例做了循环重试
    var loop = true;

    while (loop) {
        var handle = ResLoader.LoadAssetAsync<Font>(path);
        await handle.Task;
        isSuccess = handle.IsValid() && handle.Status == AsyncOperationStatus.Succeeded;
        if (isSuccess) {
            Debug.Log($"LoadFont success, path: {path}");
            m_font = handle.Result;
        } else {
            Debug.Log($"LoadFont wait to retry...");
            await Task.Delay(3000);
        }
        loop = !isSuccess && Application.isPlaying;
    }
}

12.特殊格式 Asset 的 AA 化

有些特殊类型资源,不是 Unity native 处理的类型,通常需要专门的第三方库去加载使用。

举例:常见的这种特殊格式 Asset:

  • *.db - SQLite 数据库文件
  • bnk, .wem - WWise 声音资源文件

对这种资源 AA 化、设置为 Addressable、加入到 Group 时,通常需要做一些特殊处理:

参见:

13.资源释放、卸载

资源释放、卸载,一般通过这几个方法:

AA 场景卸载:

1)自动卸载,在其他场景的加载导致 AA 场景被替换时,底层自动卸载相关 Unity 资源、AB 资源。

2)主动卸载,有 Addressables 的 UnloadSceneAsync 方法。(一般只用于 Additive 的 scene)

AA 资源释放:有 Addressables 的 Release 方法 (参数为 asset 的 Object, 或 Handle)。内部有引用计数机制,计数归零时底层会执行 AssetBundle.Unload(true)会释放其 AB 和资源。

(适用于绝大部分 ResLoader 或 Addressables 加载得到的资源、Handle)

ResLoader 预加载、预下载:

1)ResLoader.PreLoadAssets 预加载的,有 ReleaseAllPreLoadDownloadHandles 方法。

2)ResLoader.DownloadAsync 下载的,有 ReleaseAllSavedDownloadHandles 方法。(仅对于参数为 bool autoReleaseHandle = false 常驻保存的)

LoadTaskList (组合多个加载任务)加载的,有 ReleaseHandles() 方法。

ResCache 类(配合 ResLoader 的一些加载方法,做资源缓存的),有 ClearAll() 方法,清空所有缓存。

注:ResCache 类只保存加载后资源对象的引用变量,不做实际的 Unity 资源、AB 资源 的管理,不参与加载或卸载、释放。

强制 Unity 资源释放: 有 Resources.UnloadUnusedAssets 方法。

(一般只适用于场景切换、Loading 等画面中)

强制 AB 资源释放: 有 UnloadAllAssetBundles(bool unloadAllObjects) 方法。

(一般只适用于场景切换、Loading 等画面中)

点击纠错
该文档是否对你的开发有所帮助?
有帮助
没帮助
该文档是否对你的开发有所帮助?
有帮助
没帮助