10.总结
10.1 核心要点速览
YooAsset扮演的角色
- YooAsset 把「打资源包 → 跟依赖 → 多环境加载 → 热更链路 → 内存里的存活与回收」收成一条相对统一的工作流,减轻依赖乱、包体胀、热更难控、内存不可预期这类中高端项目里常见的运维成本。
- 叙述里对标的一类典型矛盾:既要分包规则清晰,又要别打出一堆冗余;既要加载接口简单,又要经得起联机/Web/小游戏等多形态;既要热更省流量,又要灰度和回滚别只能靠手工改文件名。
打包与依赖面
| 要点 | 在做什么 | 容易记的结论 |
|---|---|---|
| 双通道 | 可视化按目录/标签/规则分组,外加可编程接入;能和内置构建管线、SBP 一起走 | 小团队可以几乎不碰脚本就把规则跑起来;大项目再把定制化接到管线层 |
| 依赖分析 | 自动扒依赖关系 | 目的很明确:少打无用资源、尽量避免循环依赖把包搅乱 |
| 分包与发布策略 | 标签驱动 | 「安装包近乎空、内容全靠热更」和「安装包带全量、热更只动增量」都能落在同一套标签语义里 |
| 压缩与加密 | LZMA、LZ4 等 | 省体积是基础收益;加密是可选能力,密钥与管线细节以你项目安全规范为准 |
加载与环境切换
- 模式上覆盖编辑器模拟(不等完整打包也能试加载路径)、单机、联机、Web;同一套加载入口下业务侧尽量少写环境分支。
- 异步形态上协程、Task、委托都能接;也允许同步与异步混用——混用时心里要有数:别在不该阻塞主线程的路径上卡死 UI。
- 「边玩边下」= 本地缺包时拉远端、校验、修损坏文件、断点续传,把「缺资源就不能玩」收窄成可恢复的下载问题,而不是一次失败就全盘重来。
内存侧
| 机制 | 作用 |
|---|---|
| 引用计数 | 生命周期跟着句柄走,理论上路径比「到处 Resources.UnloadUnusedAssets」好讲清楚 |
| 对象池 | 减轻高频 Instantiate/Destroy 带来的 GC 尖峰和碎片 |
| 分析器 | 用来盯引用是不是还在被谁占着,排查悬挂引用比纯靠猜快一截 |
热更与运营侧能力
- 增量补丁只包变动资源,带宽和等待时间都更容易控。
- 多 CDN、审核/测试/正式分道、版本回退——这类能力是为灰度和线上事故预案服务的,不是摆设文案。
- 运行时侧补丁检查、下载等以 Operation 为粒度拼起来,统一走
YooAssets.StartOperation;进度和完成不在虚构的「全局 Updater 类」上瞎挂,而是挂在具体 Operation 实例上,避免回调找不到主语。
平台与环境
- 常见文档列出的 Unity 版本带:
2019.4+、2020.3+、2021.3+、2022.3+、Unity 6.0;真接入仍以当前包版本说明为准。 - 目标机覆盖常见桌面/移动/WebGL,并点名微信、抖音等小游戏形态。
- 脚本后端按 .NET 4.x 一类现代 Player 设置来期望;别拿老 .NET 配置硬蹭新 API。
何时值得认真考虑它
- 依赖复杂、包体与加载时机都要管的中大型项目。
- 更新频繁、要强灰度与回滚的长线运营。
- 多端复用同一套资源语义的多平台发行。
- 内存与 GC 尖峰敏感,尤其在移动与低配机上。
最小骨架
入门最常用的四步:初始化 → 取包 → 异步加载拿句柄 → 用完 Release。
YooAssets.Initialize();
var package = YooAssets.TryGetPackage("DefaultPackage") ?? YooAssets.CreatePackage("DefaultPackage");
AssetHandle handle = package.LoadAssetAsync<Texture2D>("资源定位地址");
// 使用资源 …
handle.Release();
安装与工程接入
- UPM:
Edit → Project Settings → Package Manager增加 Scoped Registry:名为package.openupm.com,URL 为https://package.openupm.com,Scope 列表包含com.tuyoogame.yooasset;再Window → Package Manager安装 YooAsset。 - manifest 直改:编辑
Packages/manifest.json,在dependencies里声明com.tuyoogame.yooasset的版本号,并配置指向同一源的scopedRegistries。 - GitHub 源码包:Release 里下载 Source Code,把
Assets/YooAsset拷进工程Assets。源码方式要自行处理依赖,一般仍优先 Package Manager。
| 路径 | 记下来 |
|---|---|
| OpenUPM / manifest | 版本与依赖交给 UPM 解析,团队对齐成本低 |
拷贝 Assets/YooAsset |
适合要改包内源码或离线环境,后续维护自己负责 |
- 包安装后的目录概念:
Packages/YooAsset下 Editor 管扩展窗口与构建,Runtime 管运行时加载;这和玩家设备上读到的StreamingAssets产物不是同一层,别混记。
示例工程踩坑
- 导入示例若提示 AssetBundleCollectorSetting 重复:删掉多出来的最外层配置,只保留工程真正需要的一份。
编辑器菜单在管什么
- AssetBundle Collector:资源谁进包、地址规则、分包与标签——打包「配方」的主战场。
- AssetBundle Builder:选 Package、Build Pipeline、输出目录、Build Version、是否清构建缓存、加密压缩、Copy Buildin 首包拷贝;构建结束得到补丁目录与清单文件。
- AssetBundle Reporter:Import 构建生成的
.report,看概览、资源对象、资源包视图(工具要求 **Unity 2019.4+**)。 - AssetBundle Debugger:运行时看引用计数、包卸载条件、异步任务链;老版本 Android 可能要 ADB 端口转发。
- AssetArt Scanner / Reporter:扫美术规范、出报告、支持 Fix;与 AB 并行的一条资源的质检线。
Collector 里会改掉语义的关键项
- Fix / Import / Export:目录搬迁了点 Fix;规则用 XML 导入导出备份协作。
- Show Package / Show Editor Alias:多包列表与中英界面类开关。
- Unique Bundle Name:bundle 名加
PackageName前缀,多 Package 时减轻命名撞车。 - Enable Addressable / Location To Lower / Include Asset GUID / Auto Collect Shaders / File Ignore Rule:决定地址形态、GUID 是否进清单、Shader 是否集中打独立包、全局排除哪些文件。
- Active Rule:分组是否生效,可内置或实现 **
IActiveRule**。 - Collector Type:Main 进清单可走地址加载;Static 只服务包体形态、不写清单;Depend 自动收集主资源依赖并裁掉无人引用的依赖。
- AddressRule / PackRule / FilterRule:加载时写什么字符串、资源如何合成 bundle、搜集器收哪些类型——三者一起锁定热更粒度与冲突面。
Builder 管线与首包策略
| Build Pipeline | 一句话 |
|---|---|
| EditorSimulateBuildPipeline | 跑流程、有清单语义,不生成真实 AB,改规则后快速自查 |
| BuiltinBuildPipeline | Unity 内置 AB 构建,常规发布常用 |
| ScriptableBuildPipeline | SBP,可脚本化介入加密、拆包等;多见于 Unity 2021.3+ |
| RawFileBuildPipeline | 非 Unity 资源按原生文件输出 |
- Copy Buildin File Option:控制构建时向
StreamingAssets/yoo/拷哪些首包资源;从 None 到 Clear/Only × All/ByTags 的组合,是在安装包体积、首包离线能力、迭代是否容易堆冗余之间的取舍。 - Clear Build Cache:不勾选吃增量缓存;勾选强制重算,耗时会上去。
- SBP 与 Shader 包名:内置着色器 bundle 名要和自动收集 Shader 的 PackRule 一致,示例通过
DefaultPackRule.CreateShadersPackRuleResult()与UniqueBundleName拼最终名,避免打得出加载不对。
构建产物与报告
- 输出目录里,以资源版本号组织的补丁文件夹中常见 **
DefaultPackage.version**、清单的 hash / json / bytes,以及 **DefaultPackage_xxx.report**;Reporter 导入用的就是这份报告。
运行时 PlayMode 选型
用「资源从哪来、要不要真打 AB、要不要走 HTTP、能不能拆多个文件系统」可把运行时划成五种 PlayMode;初始化资源系统时按模式选用对应文件系统与更新语义。
| PlayMode | 典型场景 | 构建与首包要点 |
|---|---|---|
| EditorSimulateMode | 编辑器里快速跑玩法 | 不构建真实 AB 也能 Play;仅编辑器 |
| OfflinePlayMode | 无热更、资源随包或随 Streaming | 必须打 AB;常见示例用 ClearAndCopyAll 把首包拷到 StreamingAssets/yoo/DefaultPackage |
| HostPlayMode | 热更、边玩边下 | 远端 HTTP(S) 提供补丁;本地 StreamingAssets 里放基线的 version / 清单 bytes 做版本对比 |
| WebPlayMode | WebGL、微信/抖音小游戏 | 必须构建;小游戏另有一套文档约束 |
| CustomPlayMode | 多文件系统拼接 | 列表最后一项是主文件系统 |
单机侧增量构建在记什么
- 打包输出旁会有 OutputCache:存「未套版本目录名的原始 AB」,当增量素材库。
- 勾 Use Asset Depend DB 时,后续构建才能更可靠地判断哪些资源内容变了:未变的从 OutputCache 直接抄,省压缩/加密;变了的重打并更新缓存。不勾则这条增量短路不可靠。
联机调试在记什么
- 示例用
.bat启python -m http.server当本机 CDN;浏览器能打开http://localhost/CDN/PC/v1.0/说明目录布局对。 GetHostServerURL把 平台(PC/Android/iOS/WebGL)和 版本段拼进路径,磁盘上的..\CDN\<平台>\<版本>必须和这段 URL 同构,否则「能编包、拉不到清单」。- 把「当前包内认的版本」写回 **
StreamingAssets**,才能和服务器上的新 version 做 diff,驱动下载。
示例工程:Boot 与补丁状态机
太空战机 demo 把「进游戏前要干嘛」收成一条可照搬的主线:引导 → 初始化框架 → 出补丁 UI → 跑完 PatchOperation → 设默认包 → 进主场景。
- Boot:Inspector 上选
EPlayMode;Awake里打日志、锁 60 帧、runInBackground、DontDestroyOnLoad。Start协程顺序大致是:把自身交给 GameManager 当协程宿主、UniEvent.Initialize()、YooAssets.Initialize()、Resources.Load("PatchWindow")弹出更新界面、new PatchOperation("DefaultPackage", PlayMode)+YooAssets.StartOperation并yield return等 Operation 完整结束,再 **GetPackage+SetDefaultPackage**,最后发 **SceneEventDefine.ChangeToHomeScene**。 - GameManager:用 EventGroup 监听
ChangeToHomeScene/ChangeToBattleScene,回调里直接YooAssets.LoadSceneAsync加载scene_home或scene_battle,把「谁来开协程」和「切场景」从 Boot 里拆出去。
PatchOperation 继承示例侧的 **GameAsyncOperation**:构造函数里注册 User* 系列消息、搭好状态机节点、往黑板写 PackageName 与 PlayMode;OnStart 进入 FsmInitializePackage;OnUpdate 在 Update 步里 _machine.Update();SetFinish 清监听、把 Status 置成功、标记 Done。
| 用户事件(示例) | 推到的状态 |
|---|---|
| UserTryInitialize | FsmInitializePackage |
| UserTryRequestPackageVersion | FsmRequestPackageVersion |
| UserTryUpdatePackageManifest | FsmUpdatePackageManifest |
| UserTryDownloadWebFiles | FsmCreateDownloader |
| UserBeginDownloadWebFiles | FsmDownloadPackageFiles |
状态流转梗概:初始化包(按 PlayMode 配 InitializeAsync)→ 请求版本(RequestPackageVersionAsync,版本进黑板)→ 更新清单(UpdatePackageManifestAsync)→ 创建下载器(CreateResourceDownloader,并发数与失败重试示例里写死为 10 和 3)→ 若 TotalDownloadCount == 0 直接 开始游戏;否则 FoundUpdateFiles 弹窗,用户点下载后发 UserBeginDownloadWebFiles → 下载(BeginDownload,进度/单文件失败走事件)→ 下载结束 → 清理缓存(ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles),回调里进 FsmStartGame)→ StartGame 里 SetFinish() 结束整条 Operation。
FsmInitializePackage 里与 PlayMode 一一对应的初始化原文要点:EditorSimulate 用 EditorSimulateModeHelper.SimulateBuild 拿 PackageRootDirectory 再配编辑器文件系统;Offline 用 OfflinePlayModeParameters 接默认内置文件系统;Host 用 **HostPlayModeParameters**,内置 + 缓存文件系统,缓存侧 IRemoteServices 由 RemoteServices 拼主备 GetRemoteMainURL / GetRemoteFallbackURL;WebGL 分支里微信小游戏用 WechatFileSystemCreater 与可写根路径,其它 Web 走 **CreateDefaultWebServerFileSystemParameters**。
手写热更协程与加载方式
实践篇把战机 demo 的状态机收成「一条协程从头到尾 callable」的写法,便于自己接 UI。主链路与示例工程语义对齐:
YooAssets.Initialize()→TryGetPackage/CreatePackage→ **HostPlayModeParameters**(内置文件系统 + 带IRemoteServices的缓存文件系统)→ **InitializeAsync**。RequestPackageVersionAsync拿到PackageVersion字符串 → **UpdatePackageManifestAsync(packageVersion)**。- **
CreateResourceDownloader(并发数, 失败重试)**:为 0 则直接收工;否则挂DownloadErrorCallback/DownloadUpdateCallback,BeginDownload()再 **yield return _downloader**。 - **
ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles)**,在Completed里进你自己的「进游戏 / 加载资源」逻辑。
手写热更链路上任一步失败仍继续跑后面,容易在清单或缓存未就绪时加载报错,应用 yield break 掐断后续。
加载侧原文覆盖:LoadAssetSync / LoadAssetAsync + Completed / yield return handle / await handle.Task;LoadSceneAsync 与 **SceneHandle**;预制体 Instantiate 与 handle.InstantiateSync();LoadSubAssetsAsync + GetSubAssetObject;以及 UnloadUnusedAssetsAsync / UnloadAllAssetsAsync / TryUnloadUnusedAsset 等卸载入口。全路径加载能用的前提与 Collector 里 Enable Addressable、AddressRule 一致,别混记。
构建期加密与运行期解密
- 加密:在 Editor 下实现 **
IEncryptionServices**,在Encrypt里按 **EncryptFileInfo**(含BundleName、FileLoadPath等)决定是否改包体;示例用 bundle 名含_image过滤(实际包名还与收集器目录命名、大写转下划线等规则相关)。 - 两种示例策略:整文件 XOR(与
BundleStream里KEY成对);头部插桩偏移(前面垫offset字节,运行时LoadFromFile带fileOffset跳过)。 - 解密:实现
IDecryptionServices:FileStreamDecryption用自定义FileStream包一层再AssetBundle.LoadFromStream(Async);FileOffsetDecryption无托管流,直接LoadFromFile(Async)(..., fileOffset),偏移量必须与打包时垫的长度一致。 - 挂上运行时:在
FileSystemParameters.CreateDefaultCacheFileSystemParameters的重载里传入IDecryptionServices(示例new FileOffsetDecryption()与RemoteServices同参构造)。打包器枚举到IEncryptionServices实现后,Builder 里多出加密选项。 - 密钥/偏移不一致:如加密写 32、解密按 64 读,效果同示例里故意改错偏移——加载失败,本质是读写协议没对齐。
HybridCLR 与资源热更联运
- 环境:按 HybridCLR 要求装 IL2CPP 模块;通过 Git URL 拉
com.code-philosophy.hybridclr,Installer 初始化;HybridCLR → Settings 里把 HotUpdate 程序集加入 Hot Update Assemblies。Player:Scripting Backend = IL2CPP,API 兼容 .NET 4.x / .NET Framework;包版本 低于 v4.0.0 时需关 Incremental GC。 - 热更 DLL 产出:Generate → All 必跑;改码后用 CompileDll → ActiveBuildTarget 重编。把
HybridCLRData/HotUpdateDlls/<当前平台>下的HotUpdate.dll复制为工程内HotUpdate.dll.bytes(必须.bytes后缀,便于当 TextAsset 打进包)。 - 运行时加载:非编辑器用
YooAssets.GetPackage→LoadAssetSync<TextAsset>(dll 路径)→Assembly.Load(text.bytes);编辑器里热更程序集已由域加载,不要再 Load 一遍 bytes,用GetAssemblies().First(Name == "HotUpdate")找程序集,否则易重复加载出问题。 - 与 YooAsset 发版流程:把 dll.bytes 当普通资源收进 Collector → 打 AB → 上 CDN;改 Hello.Run 后重新 CompileDll、替换 bytes、重打资源,已安装的 Player 再走一遍资源更新即可换逻辑,无需整包重装(前提你的 AOT/元数据与 HybridCLR 管线本身已配对)。
UniTask 扩展接入要点
- 源码导入:从 UniTask Release 取 **
Plugins/UniTask**;在_InternalVisibleTo.cs增加 **[assembly: InternalsVisibleTo("UniTask.YooAsset")]**,让 YooAsset 侧扩展程序集能调用 UniTask 内部成员。 - 扩展脚本:从
YooAssets/Samples/UniTask Sample/UniTask拷贝 Runtime/External 下与 YooAsset 相关的扩展到本机 UniTask 的 **Runtime/External/YooAsset**(路径以你工程为准)。 - 宏:在 Player → Scripting Define Symbols 增加
UNITASK_YOOASSET_SUPPORT;若你用的是带 UniTask.YooAsset 的 UPM 组合,有时会自动定义,以工程里是否已定义为准。 - 用法:**
await sceneHandle.ToUniTask()、await assetHandle.ToUniTask()**。战斗房间示例里在Update驱动的async void UpdateRoom中,先把_steps切到中间态(如LoadingPlayer)再await,避免下一帧再次进入同一段 Ready 逻辑造成重复发起加载——这是「每帧调异步」场景里常见的状态机护栏。
10.2 面试题精选
基础题
1. YooAsset 主要帮 Unity 项目解决哪些问题?
题目
从资源工程角度,YooAsset 宣称覆盖哪些痛点?不必背诵官网口号,用「依赖/包体/加载/热更/内存」里的两三句话组织答案即可。
深入解析
概述把动机写得很集中:中大型项目里资源冗余、依赖难理清、内存占用高、热更新链路复杂;框架用分包策略、依赖分析、统一加载入口和引用计数等机制把这些事「收口」到可操作的工作流。中小项目则强调「也能快速开发」,不是只服务巨型项目。
答题示例
它主要是在 Unity 里把资源从打包、加载、更新到内存回收串成一套管线,重点缓解依赖乱、包体胀、热更难做、内存不好控这几块。
技术和团队规模上,既照顾要深度定制管线的大项目,也保留可视化工具这种上手路径。
参考文章
- 1.概述
2. Unity Package Manager 安装 YooAsset 时 OpenUPM 要配哪几项?
题目
Project Settings 里加 Scoped Registry 时,Name、URL、Scope 分别填什么?它和直接改 manifest.json 里的 scopedRegistries 是什么关系?
深入解析
正文写法:Registry 名为 package.openupm.com,URL 为 https://package.openupm.com,Scope 列表包含 com.tuyoogame.yooasset。UPM 窗口能拉到包,本质是 manifest.json 里同时存在 dependencies 对 com.tuyoogame.yooasset 的声明,以及指向 OpenUPM 的 scopedRegistries,让包管理器知道去哪个源解析该 scope。
答题示例
UI 里就是在 Package Manager 设置里加一条 OpenUPM 仓库,Scope 写成
com.tuyoogame.yooasset,让 UPM 能解析这个命名空间下的包。和手改
manifest.json是一回事:都是给dependencies和scopedRegistries写同构信息,只是入口不同。
参考文章
- 2.YooAsset下载安装及导入示例项目
3. EditorSimulateMode 和 OfflinePlayMode 在「要不要构建资源包」上差在哪?
题目
哪种模式可以不先打出 AssetBundle 就在 Unity 里跑起来?哪种必须构建,并且正文示例建议怎么配首包拷贝?
深入解析
EditorSimulateMode:编辑器内模拟,不需要构建资源包即可运行,且只在编辑器生效。OfflinePlayMode:面向不发热的本地资源流,必须构建;示例配合 ClearAndCopyAll,再把输出同步到 **StreamingAssets/yoo/DefaultPackage**(是否自动拷贝依版本而定)。
答题示例
模拟模式是编辑器专用、吃清单语义但不走「真包进包」那一套;单机离线模式必须先打 AB,还要把首包资源落到 StreamingAssets 约定目录里才能跑。
两者别混用场景:一个图开发迭代,一个图无热更的发布形态。
参考文章
- 4.YooAsset资源系统的运行模式
4. Boot 里为什么要 yield return 整个 PatchOperation 之后才 SetDefaultPackage、再发切场景事件?
题目
示例 Start 协程里,若把 SetDefaultPackage 挪到 StartOperation 之前会发生什么语义问题?PatchOperation 成功结束大致代表补丁链路的哪一段已经闭环?
深入解析
yield return operation 会卡到 PatchOperation 把内部状态机跑到 FsmStartGame 并 SetFinish() 为止,即:初始化、拉版本、更清单、按需下载、清缓存这一整条 Operation 已成功。只有此时 DefaultPackage 才处于示例期望的「可加载主场景」状态;提前 SetDefaultPackage 可能在清单/缓存尚未就绪时就让 LoadSceneAsync 走寻址,属于时序错误。补丁 UI 仍由 Resources 加载,与 YooAsset 默认包无关,但主资源流应以 Operation 完成为界。
答题示例
yield return等于等补丁状态机跑完并成功收尾,这时候再设默认包、发进主界面事件,才能保证后续LoadSceneAsync打在已更新的包语义上。操作没结束就设默认包,等同于拿着半拉子运行时状态去加载,竞态和报错都不好查。
参考文章
- 5.YooAsset太空战机示例工程源码解析
5. 手写热更协程里,某一步 Operation 失败后仍继续往下跑会有什么问题?
题目
正文为什么建议初始化、拉版本、更清单、下载等步骤在失败时用 yield break 主动中止?若强行执行后面的 LoadAsset,常见踩坑是什么?
深入解析
后续步骤依赖前序产物:PackageVersion 无效则 UpdatePackageManifestAsync 参数不靠谱;清单没更完则 CreateResourceDownloader 计数与条目不可信;下载失败则缓存里文件不完整。此时 LoadAsset / LoadSceneAsync 往往在运行时以「找不到资源、CRC 失败、句柄错误」等形式爆掉,排障还难,因为日志里看起来像加载 API 坏了,其实是状态机没走完。
答题示例
热更是一条有前驱依赖的链,前面失败了后面就没有合法前提,
yield break等于把错误卡在可控位置。工程上还要配合 UI 重试或回退,而不是 silent continue。
参考文章
- 6.YooAsset资源热更新实践
6. 真机跑 HybridCLR 时,为何用 YooAsset 加载 HotUpdate.dll.bytes 再 Assembly.Load,编辑器里却不要用同一套?
题目
#if !UNITY_EDITOR 分支里 LoadAssetSync<TextAsset> + Assembly.Load 在解决什么问题?**#else** 里为什么改成 GetAssemblies 查找 HotUpdate,正文说「重复加载反而会出问题」指什么?
深入解析
真机 IL2CPP + HybridCLR 路径下,热更 DLL 一般以 bytes 形式随 AB/Streaming 分发,需要从 ResourcePackage 读出 TextAsset.bytes 再 Assembly.Load。编辑器里 HotUpdate 通常已被 Unity/IDE 编译加载进当前域,再用同一字节流 Assembly.Load 容易形成重复加载或域状态不一致。因此示例在编辑器分支用 AppDomain.CurrentDomain.GetAssemblies() 按程序集名取引用,保证调试路径与真机路径职责分离。
答题示例
真机侧 dll 在包里,要当资源拉出来喂给 CLR;编辑器侧程序集已经活着了,再 Load 一份是给调试找不痛快。
两套分支就是在区分「资源管道」和「域里已有程序集」。
参考文章
- 8.YooAsset结合HybridCLR代码热更新实践
进阶题
1. MainAssetCollector、StaticAssetCollector、DependAssetCollector 各适合什么资源?
题目
三种 Collector Type 在「是否进清单」「谁能被代码加载」「依赖怎么处理」上的分界是什么?各举一个资源例子。
深入解析
Main:收集并写入清单,可走 Load/LoadAssetAsync 这类地址化加载;UI、模型、音效等业务资源落在这是常态。Static:参与定制化打包但不写入清单,偏「随安装包走、不需要动态地址加载」的固定内容。Depend:自动收主资源依赖,未被子资源引用的依赖会裁掉,用来避免手工漏依赖或把无关依赖打进来。
答题示例
Main 就是业务要按地址拉的那批,进 Manifest;Static 更像随包死的素材,配置需要打包形态但不必占一条可寻址记录。
Depend 跟 Main 走依赖树,自动加减依赖,省手动维护链条;Static 不会替代 Depend 去做依赖裁剪逻辑。
参考文章
- 3.YooAsset工具窗口
2. OutputCache 和 Use Asset Depend DB 在增量构建里各自起什么作用?
题目
为什么构建目录里会有 OutputCache?Use Asset Depend DB 不勾选时,依赖 DB 驱动的「从缓存拷 AB、跳过重压缩」还能不能指望?
深入解析
OutputCache 存放首次构建得到的、未按版本发布目录封装的一套原始 AB,充当后续构建的复用仓库。Use Asset Depend DB 打开后,构建能依据依赖与内容变更判断资源是否改动:未改的从 OutputCache 直接复制,改了的重打并更新缓存条目,从而省时间。这条增量链路与 Depend DB 绑在一起——不勾 Use Asset Depend DB 就少了「判断变没变」的关键输入,增量捷径不可靠。
答题示例
OutputCache 像本地 AB 素材库;Depend DB 用来算这次到底谁变了。
两个都不到位时,就别期待每次改一两个资源还能秒级出包——那是机制给出来的,不是玄学。
参考文章
- 4.YooAsset资源系统的运行模式
3. TotalDownloadCount > 0 时为什么先 FoundUpdateFiles 弹窗,再等 UserBeginDownloadWebFiles 才进下载状态?
题目
FsmCreateDownloader 已经算出了待下数量和体积,为何不直接在节点里 BeginDownload?用户事件在这一步介入解决了什么产品或工程问题?
深入解析
示例在 TotalDownloadCount != 0 时只发 **FoundUpdateFiles**,由 UI ShowMessageBox 展示体量,用户点确认后才会 UserBeginDownloadWebFiles.SendEventMessage,PatchOperation 再把状态机切到 FsmDownloadPackageFiles。这把「要不要在弱网/流量敏感环境继续」的决策交给玩家或运营策略,也把下载启动时机和自动状态机推进解耦,避免静默偷跑流量。技术上 CreateDownloader 只负责建 ResourceDownloaderOperation 并存黑板,真正 BeginDownload 放在下一状态。
答题示例
创建下载器只是把任务算清楚并备好
Downloader,弹窗是给人拍板的;确认后才切到真正BeginDownload的状态,避免一检测到更新就后台狂拉。正式项目里这一步往往还要接流量提示、存储空间校验,事件链正好插桩。
参考文章
- 5.YooAsset太空战机示例工程源码解析
4. 偏移加密与 XOR 流加密在运行时要分别接什么解密路径?GetFileOffset 和打包时 offset 不一致会怎样?
题目
FileOffsetEncryption 与 FileStreamEncryption 各改动了包文件的什么形态?**FileOffsetDecryption** 与 FileStreamDecryption 各用 AssetBundle 的哪类加载 API?示例把解密偏移从 32 改成 64 后为何加载失败?
深入解析
偏移方案:文件前 offset 字节是垫片,真 AB 从偏移后开始;运行时 LoadFromFile / LoadFromFileAsync 带上与垫片长度一致的 **fileOffset**,等于让 Unity 从正确位置解析。XOR 方案:整文件字节被变换,通常用自定义 FileStream 在 Read 时按相同规则还原,再走 LoadFromStream / LoadFromStreamAsync。打包与运行必须同一套参数:偏移、XOR 的 key、筛选哪些 BundleName 加密,任一不符都会表现为 AB 头损坏或 CRC 不匹配。
答题示例
偏移是「前面垃圾后面才是真包」,解密端用带偏移的
LoadFromFile;XOR 是「读进来再变回去」,解密端包一层流再走LoadFromStream。加密写 32 解密读 64,等于把 AB 头指错地方,必挂。
参考文章
- 7.YooAsset资源加密解密
5. InternalsVisibleTo("UniTask.YooAsset") 和宏 UNITASK_YOOASSET_SUPPORT 各自挡的是哪类错误?
题目
没有 InternalsVisibleTo 时,YooAsset 的 ToUniTask 扩展容易卡在什么编译问题?不开 UNITASK_YOOASSET_SUPPORT 又可能导致扩展代码走哪条分支、表现是什么?
深入解析
InternalsVisibleTo 让 UniTask.YooAsset 程序集能合法访问 UniTask 内核里 internal 的成员,否则扩展方法编译期就报可见性错误。**UNITASK_YOOASSET_SUPPORT** 用于 条件编译:只有定义了宏,YooAsset 才会把与 UniTask 集成的 extension/API 编进来;若你的 UniTask 分发方式已经自动定义了它,手动再加会重复但一般无害;若宏和扩展包版本完全没对齐,表现多为 ToUniTask 找不到或相关文件未参与编译——以你当前 Package/源码 组合为准排查。
答题示例
InternalsVisibleTo 解决「扩展程序集看不见 UniTask 内部 API」;宏解决「这段 YooAsset×UniTask 胶水代码要不要参与编译」。
接入时两样要对齐:看得见内部实现,且编译开关打开。
参考文章
- 9.YooAsset结合UniTask异步加载实践
深度题
1. AssetHandle 与 Release 在引用计数体系里承担什么责任?
题目
引用计数框架下,为什么加载返回的是句柄而不是裸 UnityEngine.Object?Release 调用错误会导致什么问题?配套还有什么排障手段?
深入解析
LoadAssetAsync 得到 AssetHandle,用完 Release() 做引用计数递减,归零后由框架回收。句柄把「谁在用这份资源」钉在调用栈的所有者上,比到处缓存 UnityEngine.Object 更利于追溯。少 Release 会拖高常驻内存、掩盖泄漏;多发 Release 若在具体实现里不严谨可能带来双 free 类风险,实际以框架实现为准。资源分析器可观察引用状态,与纯凭 Profiler 猜相比多了一条专用工具链。
答题示例
句柄绑定一次加载的持有关系,
Release告诉框架「这一段逻辑不再使用该资源」,引用归零才走回收。忘了
Release容易把资源长期钉在内存里;分析器用来查引用悬挂,比只盯着 Profiler 里上升的曲线更有方向。是否多线程加载、
Release是否必须主线程,官方文档与版本为准。
参考文章
- 1.概述
2. HostPlayMode 下调试热更时,StreamingAssets、本机 HTTP 目录与 GetHostServerURL 之间要满足什么关系?CustomPlayMode 谁说了算?
题目
为什么要把 version / bytes 放回 **StreamingAssets**?示例里 http://localhost/CDN/PC/v1.0/ 和磁盘路径 ..\CDN\PC\v1.0 是谁在对齐谁?自定义模式多个文件系统时,以哪一个为准?
深入解析
StreamingAssets 一侧提供客户端当前认的版本基线和清单数据,用来和服务器上的新 version 做对比,决定是否下载补丁。示例 GetHostServerURL 按平台与版本段拼 URL,本机用 Python 起一个目录根的 HTTP 服务,磁盘上 CDN 子目录结构必须与 URL 路径一致,否则拉清单会 404。CustomPlayMode 明文规定:列表最后一个元素是主文件系统,排序不是随便排的。
答题示例
本地流式目录负责「我现在是谁」,远端目录负责「我能不能升到谁」,两边通过 version 对比咬合。
URL 怎么拼,磁盘就怎么摆文件夹;拼错了不是框架 bug,是约定没对齐。
自定义多文件系统时,最后一个才是主系统,别的都是挂上去的组合。
参考文章
- 4.YooAsset资源系统的运行模式
3. 示例里 HostPlayMode 初始化为什么同时配 BuildinFileSystem 和带 IRemoteServices 的 CacheFileSystem?
题目
和 OfflinePlayMode 只接默认内置文件系统相比,HostPlayMode 在 HostPlayModeParameters 里多出来的缓存文件系统在运行时解决什么问题?RemoteServices 拼出来的 URL 和清单里的相对文件名是什么关系?
深入解析
Host 分支给 BuildinFileSystemParameters 配默认内置盘,给 CacheFileSystemParameters 挂 IRemoteServices:内置侧承担首包/本地基线资源,缓存侧通过 GetRemoteMainURL / GetRemoteFallbackURL(fileName) 把远端文件拉进可写缓存。示例 RemoteServices 用同一基地址作主备,最终 URL 为 {host}/{fileName},这里的 fileName 由框架按清单条目传入,故 GetHostServerURL 给出的目录层级必须与实际 CDN 布局一致。Offline 没有这条「远端 + 可写缓存」组合,热更链路停在随包资源上。
答题示例
内置文件系统吃随包那份,缓存文件系统负责从网上补差;
IRemoteServices就是把清单里的文件映射成完整 HTTP 地址,主备两套字符串给你做容灾。只 Offline 的话就没有这一层 HTTP 解析,资源更新只能跟着整包走。
参考文章
- 4.YooAsset资源系统的运行模式
- 5.YooAsset太空战机示例工程源码解析
4. async void UpdateRoom 里为什么在 await assetHandle.ToUniTask() 前先把 _steps 切成 LoadingPlayer?
题目
若保留在 ESteps.Ready 不动、直接在 Ready 分支里 **await**,当 UpdateRoom 每帧被调用时会发生什么逻辑洞?中间态与 SpawnEnemy 的衔接在示例里扮演什么角色?
深入解析
UpdateRoom 由 Update 每帧触发,若仍在 Ready 态就开始异步加载,下一帧仍会进入 Ready 分支(因为 await 只让出当前异步方法,不自动改状态机),从而重复创建 LoadAssetAsync 或多条并发加载。示例先 _steps = LoadingPlayer,让后续帧不再误入 Ready 的「开火条件」,等 await 结束后再落到 **SpawnEnemy**。这是把 Unity 帧循环与 async/await 焊在一起的常见写法:用显式状态挡住重入。
答题示例
帧更新的入口会一遍遍进来,不先切状态,
await没跑完前每一帧都可能再开一次加载。中间态就是告诉后续帧「已经在路上了,别重复点单」。
参考文章
- 9.YooAsset结合UniTask异步加载实践
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com