# 音效系统重构方案 ## 目的 使用 Unity 原生 `AudioSource + AudioMixer` 重写框架音效系统,移除业务层对 SoundManagerPro、`AudioSource`、`AudioClip`、`SongCallBack` 和插件配置概念的依赖,并建立适合微信小游戏的加载、播放、缓存与释放流程。 本文是共享框架的可实施设计。第一版只覆盖 2D 音频;CrystalBattle 中的旧 `HFSoundManager` 仅作为迁移参考,不作为新接口规范。 ## 当前实施状态(2026-07-21) 第一版已经在 `FFCard_WeChat` 落地,并于 2026-07-22 同步到 `CrystalBattle` 参考工程,两边均通过 Unity Editor 编译。已实现统一播放句柄、Music 双源交叉淡化、SFX/UI 共享池、同资源并发限制、并发加载合并、引用计数与 LRU、PlayerPrefs 设置存储、UI 按钮桥接及 `HFFunCatalogue.Audio` 入口;两个工程均已移除 SoundManagerPro、旧 `HFSoundManager` 封装和对应 asmdef 依赖,`CrystalBattle` 原有业务调用也已迁移到新入口。后续根据业务使用反馈继续补充预加载、优先级抢占和更细的诊断接口。 ## 主要路径 | 类型 | 路径 | 说明 | |---|---|---| | 旧实现参考 | `Assets/Game/GameHFScripte/GameFunction/SoundManagerPro/New/` | CrystalBattle 中基于 SoundManagerPro 的旧封装 | | 热更框架源码 | `Assets/StrayFog/Editor/CopyToX/CopyToHF/Audio/` | 音效管理器、播放句柄、缓存和资源适配器的真实源码 | | 热更生成结果 | `Assets/Game/GameHFScripte/CopyToHF/Audio/` | 由 CopyToX 生成,禁止直接修改 | | 非热更桥接源码 | `Assets/StrayFog/Editor/CopyToX/CopyToGeneral/Audio/` | UI 音效接口和桥接器的真实源码 | | 非热更生成结果 | `Assets/Game/GameGeneralScripte/CopyToGeneral/Audio/` | 由 CopyToX 生成,禁止直接修改 | | 项目播放入口 | `Assets/Game/GameHFScripte/GameFunction/GameRoot/HFFunCatalogue_Audio.cs` | 向业务统一暴露 `HFFunCatalogue.Audio` | | UI 接入 | `Assets/Game/GameMono/Scripts/UIMono/Abs/AbsSFUI_Button.cs` | 默认、覆盖、禁用三种按钮点击音模式 | | 混音资源 | `Assets/Game/GameMono/Resources/Audio/HFDefaultAudioMixer.mixer` | 首包内的 Master、Music、SFX、UI 四个混音总线 | | 启动接入 | `Assets/Game/GameHFScripte/GameFunction/GameRoot/HFGameStart.cs` | Res 表加载完成后显式初始化音效系统 | ## 已确认的设计决定 | 项目 | 决定 | |---|---| | 播放后端 | Unity 原生 `AudioSource + AudioMixer` | | 框架范围 | 共享框架,具体项目只提供资源和启动参数 | | 资源标识 | 公开接口直接接收 Res 表中的 `int` ID | | 配置来源 | 不再读取 `Sounds.xlsx`;Res 表只负责 ID 到资源路径的解析 | | 旧接口兼容 | 不保留兼容层,业务调用直接迁移到新接口 | | 播放控制 | 每次播放立即返回统一的 `HFAudioHandle` | | 初始化 | Res 表可用后,由 `HFGameStart` 显式初始化 | | 混音 | 强制使用标准 `AudioMixer`,不提供代码音量回退 | | BGM 切换 | 两个 Music AudioSource 交替执行交叉淡化 | | SFX 并发 | SFX/UI 总数 24,同一 Res ID 默认最多 4 个实例 | | 缓存 | 引用计数、并发加载合并、LRU 淘汰;默认 32 个、空闲 60 秒 | | UI 点击音 | `AbsSFUI_Button` 自动播放,支持默认、覆盖和禁用 | | 设置保存 | 通过 `IHFAudioSettingsStore` 注入,提供 PlayerPrefs 默认实现 | | 插件退出 | 新系统验收后立即删除 SoundManagerPro 及 asmdef 引用 | ## 源码与生成目录 `CopyToHF` 和 `CopyToGeneral` 是生成目录,不是框架源码目录。新音效系统必须遵循现有 CopyToX 规则: ```text Assets/StrayFog/Editor/CopyToX/CopyToHF/Audio/ -> 编写 ESF_ / IESF_ 热更框架源码 -> CopyToX -> Assets/Game/GameHFScripte/CopyToHF/Audio/ -> 生成 HF_ / IHF_ 运行时代码 Assets/StrayFog/Editor/CopyToX/CopyToGeneral/Audio/ -> 编写 ESF_ / IESF_ 非热更桥接源码 -> CopyToX -> Assets/Game/GameGeneralScripte/CopyToGeneral/Audio/ -> 生成 HF_ / IHF_ 运行时代码 ``` 命名对应关系: | 真实源码 | 生成后的公开类型 | |---|---| | `ESF_AudioManager` | `HFAudioManager` | | `ESF_AudioHandle` | `HFAudioHandle` | | `IESF_AudioAssetLoader` | `IHFAudioAssetLoader` | | `IESF_UISoundPlayer` | `IHFUISoundPlayer` | | `ESF_UIAudioBridge` | `HFUIAudioBridge` | 本文后续 API 示例统一使用业务实际调用到的 `HF_` 生成名称。实施时必须修改对应 `ESF_` 源码并运行 CopyToX,禁止在生成结果中直接实现或修复功能。 ## 边界 ### 本模块负责 - 根据 Res ID 加载和释放 `AudioClip`。 - Music、SFX、UI 的播放、暂停、恢复、停止和音量控制。 - BGM 循环与交叉淡化。 - SFX/UI AudioSource 池、并发限制和实例抢占。 - 播放句柄状态、结果和实例级控制。 - 并发加载合并、引用计数、预加载和 LRU 缓存。 - Master、Music、SFX、UI 设置的读取与保存。 - 应用进入后台及恢复时的声音状态管理。 - UI 按钮默认点击音桥接。 ### 本模块不负责 - 不维护独立的声音 XLSX 配置表。 - 不向业务公开 `AudioClip`、`AudioSource` 或 `AudioMixerGroup`。 - 第一版不处理 3D 位置、距离衰减、跟随目标和空间混音。 - 不负责音频资源制作;只规定推荐的 Unity 导入策略。 - 不直接销毁资源系统加载的 `AudioClip`。 ## 总体架构 ```text 业务 / UI -> HFFunCatalogue.Audio -> HFAudioManager -> HFAudioHandle:一次加载与播放的状态和控制 -> 内部 Voice:Music 双源、SFX/UI 共享池 -> 内部 ClipCache:合并加载、引用计数、LRU -> HFResAudioAssetLoader -> Resource 表:Res ID -> Path -> HFABMgr:加载、等待下载、取消、失败、卸载 -> AudioMixer:Master / Music / SFX / UI -> IHFAudioSettingsStore:音量与静音持久化 ``` ## 关键类与接口 | 类/接口 | 用途 | |---|---| | `HFAudioManager` | 音效系统唯一入口和生命周期管理器 | | `HFAudioHandle` | 表示一次加载、排队和播放请求 | | `HFAudioManager` 内部 ClipCache | 管理 Clip 加载合并、引用计数和 LRU | | `HFAudioManager` 内部 Voice | 管理 AudioSource 的分配和归还 | | `HFResAudioAssetLoader` | 使用 Res 表和 `HFABMgr` 加载/卸载音频资源 | | `IHFAudioSettingsStore` | 音量和静音设置存储边界 | | `HFPlayerPrefsAudioSettingsStore` | 默认设置存储实现 | | `IHFUISoundPlayer` | GameMono 可依赖的 UI 音效最小接口 | | `HFUIAudioBridge` | UI 按钮与 GameHF 音效实现之间的依赖倒置桥 | ## HFFunCatalogue 音效播放入口 在项目层通过 `HFFunCatalogue` 的 partial 文件增加唯一业务入口: ```csharp public static partial class HFFunCatalogue { public static HFAudioManager Audio => HFAudioManager.Instance; } ``` 业务代码统一通过该入口调用: ```csharp HFAudioHandle music = HFFunCatalogue.Audio.PlayMusic(musicResourceId); HFAudioHandle sfx = HFFunCatalogue.Audio.PlaySfx(sfxResourceId); HFAudioHandle ui = HFFunCatalogue.Audio.PlayUi(uiResourceId); ``` SFX 播放期间需要临时压低 Music 时,将 Duck 参数固定在播放参数中传入: ```csharp private static readonly HFAudioPlayOptions BattleSfxOptions = new HFAudioPlayOptions { // SFX 播放期间将 Music 临时压到当前设置音量的 35%。 musicDuckVolume = 0.35f, musicDuckFadeIn = 0.08f, musicDuckFadeOut = 0.25f, }; HFFunCatalogue.Audio.PlaySfx(sfxResourceId, BattleSfxOptions); ``` - `musicDuckVolume = 1f` 表示不压低 Music,`0.35f` 表示临时保留 35% 音量。 - `musicDuckFadeIn` 是压低所需时间,`musicDuckFadeOut` 是 SFX 结束后的恢复时间,单位均为秒。 - 同时存在多个 Duck 音效时使用最低音量系数,所有相关音效结束后才恢复 Music。 - Duck 只作用于当前播放过程,不会修改或持久化玩家的 Music 音量设置。 - 播放参数会在管理器内部复制,因此可以安全复用同一个固定参数实例。 使用规则: - `HFFunCatalogue.Audio` 是业务层唯一允许使用的音效系统入口。 - 业务代码禁止直接访问 `HFAudioManager.Instance`。 - `HFFunCatalogue` 只负责暴露管理器,不重复包装 `PlayMusic`、`PlaySfx`、`PlayUi` 等方法,避免形成两套 API。 - 入口文件属于项目集成层,不能放入 `CopyToHF` 或 `CopyToGeneral` 生成目录。 - 初始化仍由 `HFGameStart` 在 XLSX 加载完成后调用 `HFFunCatalogue.InitializeAudio(...)`。 ## 后续完整 API 目标 管理器公开接口: ```csharp public AbsHFTask Initialize( HFAudioInitializeOptions options, IHFAudioSettingsStore settingsStore, Action onComplete = null); public HFAudioHandle PlayMusic( int resourceId, HFMusicOptions options = default); public HFAudioHandle PlaySfx( int resourceId, HFSfxOptions options = default); public HFAudioHandle PlayUi( int resourceId, HFUiAudioOptions options = default); public AbsHFTask Preload( int resourceId, Action onComplete = null); public void ReleasePreload(int resourceId); public void ClearUnused(); public void PauseMusic(); public void ResumeMusic(); public void StopMusic(float fadeOutSeconds = -1f); public void StopAll(enHFAudioBus bus = enHFAudioBus.All); public void SetVolume(enHFAudioBus bus, float normalizedVolume); public void SetMuted(enHFAudioBus bus, bool muted); public void Shutdown(); ``` ### 初始化参数 ```csharp public sealed class HFAudioInitializeOptions { public int AudioMixerResourceId; public int DefaultUiClickResourceId; public float DefaultMusicCrossFadeSeconds = 0.5f; public int MaxSfxVoices = 24; public int MaxInstancesPerResource = 4; public int MaxUnusedCachedClips = 32; public float UnusedClipLifetimeSeconds = 60f; } ``` - `AudioMixerResourceId` 必须是 Res 表中指向 `HFAudioMixer.mixer` 的有效 ID。 - `DefaultUiClickResourceId == 0` 表示默认按钮点击音关闭。 - 初始化必须验证 Mixer Group 和公开参数;缺失时整体失败,不静默降级。 ### 播放参数 `HFMusicOptions` 默认值: - `Loop = true` - `Volume = 1f` - `FadeInSeconds = 0.5f` - `CrossFadeSeconds = -1f`,负数表示使用初始化默认值 - `RestartIfSame = false` `HFSfxOptions` 默认值: - `Loop = false` - `Volume = 1f` - `DelaySeconds = 0f` - `Priority = 128` - `MaxInstancesPerResource = -1`,负数表示使用全局默认值 `HFUiAudioOptions` 默认值: - `Loop = false` - `Volume = 1f` - `DelaySeconds = 0f` - `Priority = 64`,优先级高于普通 SFX 所有输入音量必须限制到 `0~1`,延迟和淡化时间不得小于零,非法参数返回明确失败结果或使用文档规定的默认值。 ## 播放句柄 ```csharp public enum enHFAudioHandleState { Loading, WaitingDownload, Playing, Paused, Completed, Stopped, Failed, Canceled } ``` `HFAudioHandle` 至少公开: - `long InstanceId` - `int ResourceId` - `enHFAudioBus Bus` - `enHFAudioHandleState State` - `float Progress` - `HFAudioResult Result` - `Stop(float fadeOutSeconds = 0f)` - `Pause()` - `Resume()` - `SetVolume(float normalizedVolume)` - `OnCompleted(Action callback)` 规则: - `Play` 立即返回非空句柄;即使未初始化或 ID 非法,也返回已经失败的句柄。 - 一个句柄只能进入一次终态,完成回调只能执行一次。 - 加载期间调用 `Stop`,结果为 `Stopped`;调用 `Cancel`,结果为 `Canceled`。 - 被并发策略抢占时进入 `Stopped`,结果原因标记为 `VoiceStolen`。 - 不允许通过句柄取得底层 `AudioSource` 或 `AudioClip`。 建议结果码: ```text Success NotInitialized InvalidResourceId ResourceConfigMissing AssetTypeMismatch LoadFailed Canceled MixerUnavailable MixerLayoutInvalid VoiceStolen Shutdown ``` ## AudioMixer 规范 Mixer 必须包含以下 Group: ```text Master Music SFX UI ``` 必须公开以下参数: ```text MasterVolumeDb MusicVolumeDb SfxVolumeDb UiVolumeDb ``` 运行时保存 `0~1` 的线性音量,写入 Mixer 时转换为分贝: ```csharp db = normalizedVolume <= 0.0001f ? -80f : 20f * Mathf.Log10(normalizedVolume); ``` 静音使用 `-80 dB`。取消静音后恢复该总线最近一次保存的线性音量。 ## BGM 行为 - Music 使用两个专用 AudioSource,不占用 SFX/UI 的 24 个实例。 - 新 BGM 播放时,空闲 Music Source 淡入,旧 Source 同时淡出。 - 淡化使用 `Time.unscaledDeltaTime`,不受游戏暂停或 `timeScale` 影响。 - 相同 Res ID 且 `RestartIfSame == false` 时返回当前 Music 句柄,不重新加载或播放。 - `RestartIfSame == true` 时按一次新的切换执行。 - `PauseMusic` 只暂停当前 Music;`ResumeMusic` 只恢复由该接口暂停的 Music。 - `StopMusic(-1)` 使用初始化默认淡出时间,`StopMusic(0)` 立即停止。 - BGM 加载失败时旧 BGM 保持播放,不先停止旧音乐。 ## SFX/UI 并发与抢占 - SFX 和 UI 共用最多 24 个 AudioSource。 - 同一 Res ID 默认最多同时播放 4 个实例。 - 达到同 ID 上限时,停止该 ID 中最早启动的实例,再复用其 Source。 - 达到全局上限时: 1. 优先归还已经完成但尚未清理的 Source。 2. 在可抢占实例中选择最低优先级。 3. 同优先级时选择最早启动的实例。 - 数值越小优先级越高,UI 默认 `64`,普通 SFX 默认 `128`。 - Music Source 永远不参与 SFX/UI 抢占。 - 循环 SFX 也必须通过句柄停止,不能依赖 Res ID 查找最后一个实例。 ## 加载、缓存与释放 ### Res ID 加载 `HFResAudioClipLoader` 的固定流程: ```text 校验 resourceId > 0 -> 查询 Resource 表 -> 取得并规范化 Path -> HFABMgr.LoadAssetObject -> 返回 AudioClip、底层任务和实际卸载路径 ``` 加载器必须传递成功、失败、取消、进度和等待下载状态,不能只处理成功回调。 ### 并发加载合并 - 同一 Res ID 同时只允许一个底层加载任务。 - 后续播放或预加载请求加入等待列表。 - 任一调用方取消时只移除自己的等待句柄;仍有等待者时不得取消共享底层任务。 - 最后一个等待者取消后,才允许取消底层任务。 - 成功、失败或取消后必须从“正在加载”字典移除。 ### 引用计数 每个缓存项至少记录: ```text ResourceId ResolvedPath AudioClip ActiveReferenceCount PreloadPinCount LastUsedRealtime LoadState ``` - 活跃播放开始前增加引用,进入终态时减少引用。 - `Preload` 增加固定引用;`ReleasePreload` 减少固定引用。 - `ActiveReferenceCount > 0` 或 `PreloadPinCount > 0` 时禁止淘汰。 ### LRU 默认策略 - 无引用 Clip 空闲超过 60 秒后可被淘汰。 - 无引用缓存超过 32 个时,从最久未使用的条目开始淘汰。 - 先清理超过空闲时间的条目,再按数量上限清理。 - 卸载必须调用 `HFABMgr.UnloadAssetObject(resolvedPath)` 或等价资源接口。 - 禁止 `Destroy(AudioClip)`。 - 底层卸载失败时保留缓存记录并输出一次可定位日志,等待后续重试。 ## UI 点击音与程序集边界 `GameHF` 已引用 `SFMono`。如果 `AbsSFUI_Button` 直接调用 `HFFunCatalogue.Audio`,将形成程序集循环依赖。 固定使用依赖倒置: ```text GameGeneral -> 定义 IHFUISoundPlayer、HFUIAudioBridge SFMono -> AbsSFUI_Button 调用 HFUIAudioBridge GameHF -> HFAudioManager 实现 IHFUISoundPlayer -> Initialize 时注册 -> Shutdown 时注销 ``` 按钮增加: ```csharp public enum enHFUIButtonAudioMode { Default, Override, Disabled } ``` 序列化字段: ```csharp [SerializeField] private enHFUIButtonAudioMode mClickAudioMode = enHFUIButtonAudioMode.Default; [SerializeField] private int mClickAudioResourceId; ``` 行为: - `Default`:使用初始化参数中的默认 UI 点击音 ID。 - `Override`:使用按钮自身的 `mClickAudioResourceId`。 - `Disabled`:不播放。 - 仅在按钮通过 `interactable` 判断和现有 0.2 秒点击防抖后播放。 - 音效未初始化时桥接调用直接忽略,只允许输出一次警告,不能每次点击刷日志。 ## 设置持久化 ```csharp public interface IHFAudioSettingsStore { bool TryLoad(out HFAudioVolumeSettings settings); void Save(HFAudioVolumeSettings settings); } ``` `HFAudioVolumeSettings` 保存 Master、Music、SFX、UI 四组音量和静音值。 默认实现使用独立版本键,例如 `hf.audio.settings.v1`。项目可替换为微信本地存储、账号设置或云存储实现,音效核心不得直接调用平台 SDK。 ## 生命周期 ### 初始化顺序 ```text HFGameStart.Init -> 设置资源模式和语言 -> 加载 XLSX/Res 表 -> OnLoadDbFinish -> HFFunCatalogue.Audio.Initialize -> 初始化成功后进入业务场景 ``` Res 表未加载前无法使用 Res ID,因此 LoadingWindow 在数据库加载完成前默认没有 UI 点击音。如果启动界面未来需要声音,应单独设计首包固定资源通道,不能绕过当前 Res 加载约束。 ### 应用后台 - 进入后台时记录由系统暂停的播放实例。 - 返回前台时只恢复这些实例。 - 原本由业务手动暂停的实例保持暂停。 ### Shutdown `Shutdown` 必须: 1. 拒绝新的播放请求。 2. 取消或结束所有等待句柄。 3. 停止 Music、SFX 和 UI Source。 4. 注销 `HFUIAudioBridge`。 5. 释放预加载固定引用。 6. 通过资源管理器卸载可释放的 Clip 和 Mixer。 7. 清空池、缓存、回调和运行状态。 ## 迁移方案 ### 调用替换 ```csharp // 旧 HFFunCatalogue.SoundPool.PlayBgm(id); // 新 HFFunCatalogue.Audio.PlayMusic(id); ``` ```csharp // 旧 HFFunCatalogue.SoundPool.PlaySFX(id); // 新 HFFunCatalogue.Audio.PlaySfx(id); ``` ```csharp // 旧 HFFunCatalogue.SoundPool.SetPauseBGM(true); HFFunCatalogue.SoundPool.SetPauseBGM(false); // 新 HFFunCatalogue.Audio.PauseMusic(); HFFunCatalogue.Audio.ResumeMusic(); ``` 旧接口中返回或回调 `AudioClip`、`AudioSource` 的能力不迁移。业务如需停止循环声音,必须保存新接口返回的 `HFAudioHandle`。 ### 实施顺序 1. 在 `Assets/StrayFog/Editor/CopyToX` 对应源码目录创建 ESF 核心类型、状态和结果模型,并通过 CopyToX 生成 HF 代码。 2. 创建标准 AudioMixer 资源并登记 Res ID。 3. 实现 Res 加载适配器和缓存。 4. 实现 Music 双源和 SFX/UI Source 池。 5. 接入设置存储和 UI 桥。 6. 在项目层增加 `HFFunCatalogue.Audio` 播放入口。 7. 在 `HFGameStart` 中通过该入口完成显式初始化。 8. 一次性迁移业务调用,不增加旧 API 兼容层。 9. 完成 Editor、AssetBundle、CDN/等待下载验证。 10. 删除旧 `HFSoundManager`、`Sounds.xlsx` 音效依赖和 SoundManagerPro。 11. 删除 GameHF 对 `SoundManagerProDefine` 的 asmdef 引用。 ## 音频资源导入建议 针对微信小游戏: | 类型 | 建议 Load Type | 建议用途 | |---|---|---| | BGM | Streaming | 长音乐,避免整段解压常驻内存 | | 短 UI/SFX | Compressed In Memory | 控制常驻内存,适合重复播放 | | 极短且高频 SFX | 视性能测试选择 Decompress On Load | 只用于数量有限、确有解码性能瓶颈的资源 | 其他建议: - 无空间定位需求的音频优先转 Mono。 - 不建议所有资源统一使用质量 `1.0`,应按听感和包体测试设定。 - BGM 不得使用 Decompress On Load 作为默认策略。 - 平台 Override 必须通过 Unity 构建结果和运行时内存验证,而不是只看源文件大小。 ## 验证 ### EditMode - 非法、缺失 Res ID 返回正确失败结果。 - 同 ID 并发请求只触发一次底层加载。 - 成功、失败、取消时所有句柄都进入且只进入一次终态。 - 单个等待者取消不影响其他等待者。 - 活跃引用和预加载固定引用阻止错误淘汰。 - 32 个/60 秒 LRU 按约定顺序释放。 - 卸载通过资源管理器执行,没有直接销毁 Clip。 - 音量设置能够保存、恢复并正确转换为分贝。 ### PlayMode - BGM 双源交叉淡化正确,切换失败时旧 BGM 不停止。 - 相同 BGM 默认不重播,`RestartIfSame` 可以强制重启。 - SFX/UI 总 Source 不超过 24,同 ID 不超过 4。 - 抢占顺序符合优先级和启动时间规则。 - 循环 SFX 可以通过各自句柄独立停止。 - UI 默认、覆盖、禁用三种模式正确。 - 暂停、后台恢复和场景切换不会产生重复管理器或错误恢复。 ### 资源模式 - Editor、AssetBundle、本地包、CDN 和等待下载路径均能闭环。 - 下载失败、资源类型错误和取消不会留下永久等待回调。 - `Shutdown` 后没有活动 Source、等待任务、UI 桥或缓存引用。 ### 验收标准 - 工程内不存在业务侧 SoundManagerPro 类型引用。 - 所有业务播放调用均通过 `HFFunCatalogue.Audio`,不存在直接访问 `HFAudioManager.Instance` 的调用点。 - 音效框架的可维护实现全部位于 `Assets/StrayFog/Editor/CopyToX`,生成目录没有手工维护代码。 - `GameHF.asmdef` 不再引用 `SoundManagerProDefine`。 - 不再读取 `Sounds.xlsx` 播放音频。 - 所有播放失败都可以通过句柄得到明确结果。 - Music 固定使用两个 Source,SFX/UI Source 上限固定为 24。 - 资源只由 `HFABMgr` 加载和释放。 - SoundManagerPro 目录可以在不引发编译错误的情况下删除。 ## 当前状态 - 待验证。 - 已完成旧系统分析和新系统设计。 - 尚未实现代码、Mixer 资源、业务迁移和运行时验证。 ## 文档关联 | 文档 | 关联原因 | |---|---| | [模块索引](../模块索引.md) | 模块入口和状态 | | [资源管理](资源管理/README.md) | AudioClip 的加载、等待下载和卸载边界 | | [数据配置框架](数据配置框架.md) | Resource 表及 Res ID 解析 | | [第三方插件](第三方插件.md) | SoundManagerPro 的退出和 asmdef 清理 | | [Hotfix 启动](Hotfix启动.md) | `HFGameStart` 初始化时机 | | [架构说明](../../rules/10-架构说明.md) | GameGeneral、SFMono、GameHF 依赖方向 |