Files
UnityAI/modules/frameworks/音效系统重构方案.md

635 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 音效系统重构方案
## 目的
使用 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<HFAudioInitializeResult> 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<HFAudioLoadResult> 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<HFAudioResult> 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>
-> 返回 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 依赖方向 |