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

常见问题

Hosting 工具 (Hosting Services)

A:Addressable 插件内嵌了一个 Hosting 工具,可以运行一个 http Web 服务器,让构建后的资源可以通过网络访问到,相当于可以本地模拟运行一个 CDN。

使用:打开 Hosting Services (Addressables 配置窗口 > Tools 按钮 > Hosting Services)

第一次打开时还没有创建服务器实例,需要点击 Create > Local Hosting

创建后默认服务是关闭的。可以点击勾选 Enable。

Hosting 工具提供的 profile 变量:

  • 窗口中列出的:PrivateIpAddressHostingServicePort 等 Variable Name,是 profile 管理中可以用中括号[PrivateIpAddress][HostingServicePort]引用的环境变量。
  • 以此方便将一个 profile 设置为使用本地 hosting 工具的 http 服务后,本地运行、或局域网内运行的真机版本,在运行时 Load 加载本地、局域网内电脑上工程目录中已构建输出的 AA 资源(bundle 文件)。

!注意!:如果 ip、 端口 port number 修改, 必需重新 build 构建 AA 资源、重新 build 版本,使版本中的 AA 信息使用正确的 URL.

!注意!:如果 profile 选择切换,可能需要关闭、重新打开一次 Hosting 工具、并 Enable 开关一次,注意检查 log 显示其监听绑定的路径,是否正确。

FAQ:localhost 测试连接失败、加载失败 如何排查解决

  • 排查版本和构建资源:应当在设置好和每次修改了 profile 、RemoteLoadPath 后,再构建 AA 资源,最后再打包 APK
  • 排查网络连接:桌面电脑和手机的局域网是否联通,那个加载不到的 url 在本机和不同的浏览器能否访问,桌面电脑的网络是否有防火墙设置阻挡或没有对 Unity 开启允许通过。

Use Existing Build 模式下的 shader 问题

A:Play Mode Script > Use Existing Build,此模式下,如果工程 build target 为: Android ,那么 build 出的资源的很多 shader,在 PC/Mac 版 Editor 中会显示错误,显示成:粉色(紫色/品红/magenta)的。只能用来调试逻辑

若要显示正确,以下方法选择其一:

1)切换到前两个 mode: Use Asset Database 或 Simulate Groups 进行调试。

2)给 UnityEditor 添加启动参数,强制使用 GLES:-force-gles

3)在 Android 真机设备上运行

4)build target 切到 PC/Mac,然后 build AA 资源,然后运行。

Bundle 资源 Cache/Caching 缓存机制问题

A:Addressable 底层使用了 AssetBundle,他们都完整支持了 Caching 缓存机制。

即,一个 remote 远端资源(bundle)被从服务器 url 请求到、并下载成功后,会不仅有内存中缓存,而且有本地文件缓存(包括真机 Android 设备上)。

这意味着:有缓存可读取时,资源异步加载能直接从缓存被加载成功,绕过服务器 request 下载。包括即使没有网络、服务器完全无法链接,也如此

Editor 缓存相关小工具:Editor 菜单: ByteGame -> StarkMini -> Cache & Prefs

Editor 查看缓存文件:Editor 菜单: ByteGame -> StarkMini -> Cache & Prefs -> Open AB Caching folder

Editor 清除缓存:Editor 菜单: ByteGame -> StarkMini -> Cache & Prefs -> Clear AB Caching

如何手动清除 Editor 的缓存文件:

asset bundle cache unity editor in mac

# Windows path:
C:\Users\<user>\AppData\LocalLow\<company_name>\<project_name>\
# Mac Path:
~/Library/Caches/Unity/

如何在代码中执行(例如可以 Editor 本地调试中,强制每次启动时执行):

#if UNITY_EDITOR
        Caching.ClearCache();
#endif

如何在手机设备(安卓)中清除缓存

安卓:打开“设置” -> 进入“应用管理” -> 找到你的应用 app ->

-> (部分系统) 点击【清除数据】按钮 -> 【清除全部数据】。(注意不是清除缓存)

-> 或(部分系统)点击【存储】 -> 点击【删除数据】。(注意不是清空缓存)

local 分组的 bundle,加载耗时非常长

出现:local 分组的 bundle,在游戏启动过程的 loading 时间(加载耗时)非常长。

⚠️ !注意!:local 的分组的压缩设置,应避免使用 LZMA 。

建议:总是选择: Compression: LZ4。

local 的(打包在 apk 内的)bundle 的加载如果使用 LZMA 的压缩,会导致:

  • 加载耗时严重变长。
  • 内存出现双倍 bundle 大小的毛刺,甚至可能常驻占用。

原因:Unity 底层 AssetBundle 对 LZMA 的压缩,不能直接从 apk 内读取,必须解压和存储写入到设备 persistentData 目录。 而 LZ4 方式,能够直接读取。

⚠️ !注意!:还要避免自定义的 APK 打包方式时,把 apk 整体压缩,这会导致 local 的 AA 的 bundle 文件被二次压缩,这样也会同样导致最终 AssetBundle 读取时,需要额外产生一次从 Apk 解压、存储写入设备的过程。

解决方法:选择其一:

  1. 自定义的 apk 打包时,将 apk 内 assets/aa 目录,用仅存储不压缩的方式添加到 zip 中。
  2. 自定义的 apk 打包时,将 *.bundle 文件,用仅存储不压缩方式添加到 zip 中。
  3. 使用默认的 Unity 引擎的 Build 打包方式。

Set custom log trace type

A:

Example

StarkSDKSpace.StarkSDK.Init();
Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);
Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.Full);

注:目前 StarkSDK.Init 中会有默认的设置各个 log type 的 trace type,所以自定义的 trace type 要在其 Init()之后设置。

断网的加载失败的处理

代码实现可以参考 API 中多个加载任务组合和异步加载 Scene 这 2 个部分的函数

有关于判断 isSuccess、和在 失败 !isSuccess 时可以做重试。

从用户体验角度,一般建议:加载开始时显示转圈或走进度条、加载中最多等待一定的超时时间(设置 group 的 timeout 例如 10s)、失败时弹框等待用户按钮确认、确认后重试一次加载。

如何拆分过大的资源 group 分组

方法:

  1. 可以拆成多个 group。拆分策略举例:有些大量关卡资源在同一个 group,每 5 关一个 group,每个章节一个 group。
  2. 可以修改 group 的 Inspector 面板中的"Bundle Mode"属性,使用"Pack Separately",让他按 group 中每个 asset 条目都分开单独打 ab。
  3. 使用"Pack Separately",可以与设置文件夹目录为 Addressable 结合,达到以每个目录为单位,分别打 bundle 的效果(即 group 结构中的第一层的 asset 条目是文件夹,以此为单位分别打 bundle)
  4. 拆分逻辑,也可以编写 Editor 脚本做成小工具,批量自动设置 group 分组、设置 address。

依赖关系导致会同时加载太多其他 bundle

A:这是一种较常见问题情况:依赖关系导致会同时加载太多其他 bundle。

如果你的 asset 其中一个,直接或间接引用了所有其他的大量的 asset 资源(例如,一个作为游戏关卡配置的 ScriptableObject,用 public 的属性在 inspector 挂了很多其他所有关卡的 Prefab、ScriptableObject 引用),那么容易仍然导致加载其中一个 asset 时,同时会依赖下载其他间接引用到的所有 asset 所在 group 的 AB bundle,这可能是你不想要的结果。

如果是这样,那么可以考虑把这种强引用关系拆开,改成弱引用。

改为弱引用常用方法:

方法 a:改为 public string 字符串、或其列表,填入所要弱引用的 asset 资源的 addressable key、label。运行时再 LoadAsset (请自行选择合适的加载接口)。

方法 b:改为 public AssetReference 类型变量、或其列表,在 inspector 中挂上所要引用的 asset 资源,此时为弱引用。运行时再对 assetRef 对象执行 LoadAsset。

显式指定、隐式依赖,对 AA 的 bundle 打包的影响?

关于显式指定的依赖、隐式依赖:

对于一个 bundle(例如称 Group A 或 Bundle A) 和一个 bundle 中的多个资源 Asset (例如称 Asset a,b,c) 来说,他们依赖引用其他资源 (例如称 Asset d,e,f,g)

  • 如果其中依赖到的 (Asset d,e) 有被 addressable,放入了相同、或其他的 AA 分组 group,那么这些依赖是被显式指定的依赖资源。
  • 如果其中依赖到的 (Asset f,g) 没有被设置为 addressable、没有放入任何 AA 分组 group,这样依赖到的资源 (Asset f,g) ,就称为隐式依赖

对打包方式的影响:

  • 显式指定的资源 (Asset d,e) ,只会打包到所指定的 group 的对应 bundle,不会有重复副本。 且其他不同 bundle (Bundle B, C, ...) 中的资源 Asset,只对它保持引用关系。
  • 隐式依赖的资源 (Asset f,g) ,会跟随其引用者,打包到相同的 bundle。 且如果有被多个不同 bundle 的资源 Asset 引用 (Bundle B, C, ...) ,那么在每个引用者所在 bundle 都重复打一份,即打包多个副本 (Bundle B 中 Asset f[1], g[1], Bundle C 中 Asset f[2], g[2], ...)

依赖关系导致很多隐式依赖的资源,被重复打包到多个 bundle

查看Addressable Analyze tool (重复依赖打包分析) (Check Duplicate Bundle Dependencies)

第三方库的特殊类型资源 Asset,AA 化配置和打包时出错

问题情况:

一些工程中有些资源是特殊类型,不是 Unity native 处理的类型,他们通常要由专门的第三方库去加载使用,但是我们需要对这种资源也 AA 化、设置为 Addressable、加入到 Group 时,容易碰到文件类型、资源类型错误,编辑器 Build 时报错、无法正确打出对应 bundle,并导致无法正确用 AA 方式加载。

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

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

解决:特殊格式 Asset 的 AA 化,常用处理方法:

  1. Asset 在 Unity 工程中,改名为*.bytes
  2. 将它配置到 AA group,打进 bundle ,
  3. 此资源类型现在为 UnityEngine.TextAsset 类型。(且是UnityEngine.Object的子类)
  4. 运行时以<UnityEngine.TextAsset>类型,LoadAsset 加载
  5. 加载成功后,得到 handle.Result 为 TextAsset,通过 Result 的bytes属性可以访问得到byte[]类型的 bytes 数据。
  6. 使用 bytes 数据,传给第三方库去加载这些 bytes 数据,或者先写入保存文件到 device 设备上再加载。

示例代码:写入保存到文件:

string ExampleSaveBytesFile(string filename, byte[] bytes) {
 if (bytes == null) {
 return null;
 }
 bool success = false;
 string path = Application.persistentDataPath + "/" + filename;
 try {
 // create or overwrite
 using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)) {
 fileStream.Write(bytes, 0, bytes.Length);
 }
 success = true;
 Debug.Log("Save Success.");
 } catch (Exception e) {
 Debug.Log("Save Failed with exception: " + e.ToString());
 }
 // ...

示例代码:加载已保存到文件后的 bytes 数据文件

string ExampleLoadDBGameData(string dbPath, string data_id) {
 SQLiteConnection connection = new SQLiteConnection(dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);
 ExampleGameData data = connection.Table<ExampleGameData>().FirstOrDefault(x => x.id == data_id);
 // ...

Atlas 和 Sprite 的加载:load sprites of atlas

实例代码:直接加载 Atlas 的指定子图 Sprite:

// SpriteAtlas中指定单个子图的加载:(可选参数:onLoad回调)
string address = spriteAtlasAddress + '[' + atlasedSpriteName + ']';
var handle = ResLoader.LoadAssetAsync<Sprite>(address, onLoad)
// 1. 结果处理方式a: 异步等待await:
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded) {
 var sprite = handle.Result; // 加载成功,Result类型为Sprite
 // ...
}
// 2. 结果处理方式b: 使用回调参数onLoad中处理:
void onLoad(Sprite res) {
 var sprite = res; // 成功回调,参数类型为Sprite
}

实例代码:加载图集 SpriteAtlas,并取子图 Sprite:

// SpriteAtlas整个图集的加载:(可选参数:onLoad回调)
string atlasAddress = "xxx";
var handle = ResLoader.LoadAssetAsync<SpriteAtlas>(atlasAddress, onLoad)
// 1. 结果处理方式a: 异步等待await:
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded) {
 var atlas = handle.Result; // 加载成功,Result类型为SpriteAtlas
 // ...
}
// 2. 结果处理方式b: 使用回调参数onLoad中处理:
void onLoad(SpriteAtlas res) {
 var atlas = res; // 成功回调,参数类型为SpriteAtlas
}
// 3. 取子图,所有sprites, 指定subSprite:
var sprites = atlas.GetSprites()
var subSprite = atlas.GetSprite(subAssetName)

编写 Editor 脚本执行:批量配置 AA

编写 Editor 脚本,批量配置 AA:设置 asset group 分组、设置 address 等等

using StarkMini.Editor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;

获取 settings,查找 group,创建 group

// 获取工程中的 Addressable settings (AA配置信息)对象
var settings = AddressableAssetSettingsDefaultObject.GetSettings(false);
// 查找一个已有的指定 asset group
var group = settings.FindGroup("xxxGroupName");

创建 group,自定义 group 的 schema 属性

// 创建一个指定的 asset group (StarkMini下个版本提供接口)
var group = AddressableAssetsTools.CreateOrSetGroup(settings, "xxxGroupName", isRemote: true);
// 创建一个自定义的 asset group
var group = settings.CreateGroup(name, false, false, false, null);
group.Name = "XxxGroupName";
// 配置 asset group 的 Bundle schema
{
 var schema = group.GetSchema<BundledAssetGroupSchema>();
 if (!schema) {
 schema = group.AddSchema<BundledAssetGroupSchema>();
 }
 // 设置group的schema属性: BundleMode: PackSeparately或PackTogether
 schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackSeparately;
 schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether;
 // 设置group的schema属性: BuildPath, LoadPath
 if (isRemote) {
 schema.BuildPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteBuildPath);
 schema.LoadPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteLoadPath);
 } else {
 schema.BuildPath.SetVariableByName(settings, AddressableAssetSettings.kLocalBuildPath);
 schema.LoadPath.SetVariableByName(settings, AddressableAssetSettings.kLocalLoadPath);
 }
 // 设置group的schema属性: Timeout, RetryCount
 schema.Timeout = timeout;
 schema.RetryCount = retryCount;
}
// 配置 asset group 的 ContentUpdate schema
{
 var schema = group.GetSchema<ContentUpdateGroupSchema>();
 if (!schema) {
 schema = group.AddSchema<ContentUpdateGroupSchema>();
 }
 schema.StaticContent = isStatic;
}

查找或配置 asset 为 AA 的 Entry,设置到 group,设置 address,访问其 group

var guid = AssetDatabase.AssetPathToGUID(assetPath);
// 查找指定资源 asset 的 AA Entry
var entry = settings.FindAssetEntry(guid);
// 设置指定资源 asset 到指定 AA group
var entry = settings.CreateOrMoveEntry(guid, group, false, false);
// 访问entry的 address
var address = entry.address;
// 自定义设置entry的 address
entry.address =
  "yourXxxxData/" +
  Path.GetFileNameWithoutExtension(assetPath) +
  "whatEverYouWant";
// 访问entry的 group
var group = entry.parentGroup;

其中注意:上面用到 API:CreateGroup 第 4 个参数 bool postEvent、和 CreateOrMoveEntry 第 4 个参数 bool postEvent 默认 = true

  • 批量跑很多的话,为了效率应该用 postEvent = false
  • 然后在最后批量配置完成后,调用一句标记 dirty、抛事件:
// 批量配置完了,调用一句标记dirty、抛事件
settings.SetDirty(
  AddressableAssetSettings.ModificationEvent.BatchModification,
  null,
  true,
  true
);

编写 Editor 脚本执行:构建 AA 资源(Build pipeline)

构建 AA 资源也可以由 Editor 脚本运行,方便加到自定义的自动构建 Build pipeline,举例

using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
// 执行:构建 AA 资源
public static void BuildAddressableByName(string profileName)
{
 ActivateAddressableProfile(profileName);
 AddressableAssetSettings.BuildPlayerContent();
}
// 可选:激活指定的 AA profile
public static bool ActivateAddressableProfile(string profileName)
{
 var aaSettings = AddressableAssetSettingsDefaultObject.Settings;
 var profileSettings = aaSettings.profileSettings;
 // ... 此处可以添加自定义检查验证 profileSettings 有效性
 string targetProfileId = profileSettings.GetProfileId(profileName);
 if (string.IsNullOrEmpty(targetProfileId))
 {
 Debug.LogError(LOGTAG + $"target Addressable profile not exist: \"{profileName}\"!");
 return false;
 }
 aaSettings.activeProfileId = targetProfileId;
 return true;
}

参考:[AddressableAssetSettings.BuildPlayerContent | Addressables](https://docs.unity3d.com/Packages/com.unity.addressables@1.17/manual/BuildPlayerContent.html)

版本运行时报错 E/Unity: Could not produce class with ID 91

报错 E/Unity: Could not produce class with ID 91

class with ID 91 是 AnimatorController

解决方法(选其一):

    1. "Strip Engine Code" 关闭,不 strip。
    1. 在你的 第一个初始启动 Scene(Build Settings - Scenes In Build 中开启的第 0 个)中,添加一个空的 GameObject,给它添加 component:Animator 和 Animation。

运行时报错 Decompressing this format (57) is not supported ;和 Unable to read header from。

可能是因为文件的路径超长了。

用打 pack separately 的打包方式话,建议 address key 取短一些,因为构建 bundle 资源的输出路径,会对应这个 address 的名称、以其中的 / 斜杠 处分割的子目录中。

注意:"pack separately"的可能文件路径长度过长问题

场景替换后 DontDestroyOnLoad 的资源被释放、脚本 script missing

A:机制解释:

  • AA 化的 Scene 资源,在 LoadSceneAsync 时加载为场景,并且会有内部相应的引用计数机制、进行计数增加。 它所依赖的资源、AssetBundle,会相应增加引用计数。
  • 这样的场景,被替换、卸载时,默认会进行引用计数减少、做相关的资源释放。 这包括释放它所有引用的资源,包括有标记为DontDestroyOnLoad 的 GameObject 上所使用的资源。
  • 如果某个原本有被依赖的 AssetBundle,相关引用计数到 0,那么 AssetBundle 会 Unload()。
  • 所以 AA 场景被替换卸载后, DontDestroyOnLoad 的 GameObject 上的相关资源,很可能也会被释放、出现脚本 script missing。

解决方法,取其一:

  1. 把这种需要 DontDestroyOnLoad的 GameObject,做成 Prefab,并设置为 Addressable,并且运行时动态加载。
  • 缺点:需要改较多代码和工程资源,且如果工程中这种 DontDestroyOnLoad 的使用很多、且分散在较多场景中,那么要完成完全替换比较麻烦。
  1. 对于有包含了 <span style="background-color: rgba(245, 246, 247, 1)">DontDestroyOnLoad</span> 的 GameObject 的场景,在做加载时,利用下述相关接口,使其提前增加引用计数,以避免它的相关资源都被释放。
  • 接口: ResLoader.DownloadAsync(key) ResLoader.PreDownload(key) Addressables.ResourceManager.Acquire(handle)
  • 缺点:相关场景的 AssetBundle 会保持引用,会造成增加额外的内存使用、有内存驻留。

但是注意区分:因为场景本身会卸载,所以其他跟随 Scene 卸载的 GameObject 仍然是正常 destroy 的。主要是所依赖的 AssetBundle 会保持打开、会内存驻留。

参考 Addressable 原文档:

Loading Addressable Scenes

When loading an Addressable Scene, all the dependencies for your GameObjects in the scene are accessed through AssetBundles loaded during the Scene load operation. Assuming no other objects reference the associated AssetBundles, when the Scene is unloaded, all the AssetBundles, both for the Scene and any that were needed for dependencies, are unloaded.

Note: If you mark a GameObject in an Addressable loaded scene as DontDestroyOnLoad or move it to another loaded Scene and then unload your original Scene, all dependencies for your GameObject are still unloaded.

If you find yourself in that scenario there are a couple options at your disposal:

  • Make the GameObject you want to be DontDestroyOnLoad a single Addressable prefab. Instantiate the prefab when you need it and then mark it as DontDestroyOnLoad.
  • Before unloading the Scene that contained the GameObject you mark as DontDestroyOnLoad, call Addressables.ResourceManager.Acquire(AsyncOperationHandle) and pass in the Scene load handle. This increases the reference count on the Scene, and keeps it and its dependencies loaded until Release is called on the acquired handle.

Ref: [Addressables.LoadSceneAsync | Addressables](https://docs.unity3d.com/Packages/com.unity.addressables@1.17/manual/LoadSceneAsync.html)

关于:动态资源更新、检查新版本资源列表

此处“动态资源更新”指:已经发布了一版游戏包、资源包后,开发者新发布更新资源包,并且用户不需要重新下载游戏包(Apk)的情况下,动态检查并下载更新资源包。

  • StarkMini 暂未对此专门的文档覆盖,但为了便于开发者使用,动态资源更新相关工具正在开发中。
  • 目前默认打包方式下,使用本地 catalog: 本地资源列表文件 <span style="background-color: rgba(245, 246, 247, 1)">catalog.json</span> 会打包进 apk。 新版本有新增资源时,需要发布新版本新 apk。
  • SC 小游戏用户下载包体时,(1) 包体本来就较小,(2) 平台会帮用户自动复用已下载过的 Unity 的 lib so,以缩短用户更新时间。

目前,如果需要动态资源更新,可以使用 Addressables 原版功能

  • 需要用到 remote catalog、content update 功能。
  • Addressables 默认会在它的 InitializeAsync 过程中,对 Catalog 做更新检查和加载。
  • 请参考Addressables原版的文档:<a href="https://docs.unity3d.com/Packages/com.unity.addressables@1.17/manual/ContentUpdateWorkflow.html" target="_blank">Content update workflow | Addressables | 1.17</a>
  • 参考几个相关 API 文档:

[Addressables.InitializeAsync](https://docs.unity3d.com/Packages/com.unity.addressables@1.17/manual/InitializeAsync.html)

[Addressables.UpdateCatalogs](https://docs.unity3d.com/Packages/com.unity.addressables@1.17/manual/UpdateCatalogs.html)

Addressables.CheckForCatalogUpdates

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