重构音频模块 1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优 2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历 3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点 4. 支持3D环境音并具备距离衰减、遮挡等空间属性 5. 新增音频类型(BGM/SFX/Voice/Ambient) 6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
750 lines
17 KiB
Markdown
750 lines
17 KiB
Markdown
# Audio 模块使用文档
|
||
|
||
## 1. 模块目标
|
||
|
||
`Audio` 模块为框架提供统一的音频播放、缓存、回收、3D 空间音频与配置管理能力。
|
||
|
||
当前实现目标:
|
||
|
||
- 高频播放路径尽量稳定,避免热路径 GC
|
||
- `AudioClip` / `AudioSource` 生命周期可控,避免泄漏
|
||
- 支持 `Music`、`Sound`、`UISound`、`Voice`、`Ambient`
|
||
- 支持 2D 音频、3D 定点音频、跟随目标音频
|
||
- 支持距离衰减、空间混合、遮挡低通
|
||
- 配置从组件 Inspector 中抽离为可复用 `ScriptableObject`
|
||
|
||
模块核心代码位于:
|
||
|
||
- `Runtime/Audio/AudioComponent.cs`
|
||
- `Runtime/Audio/IAudioService.cs`
|
||
- `Runtime/Audio/AudioService.cs`
|
||
- `Runtime/Audio/AudioCategory.cs`
|
||
- `Runtime/Audio/AudioAgent.cs`
|
||
- `Runtime/Audio/AudioGroupConfig.cs`
|
||
- `Runtime/Audio/AudioGroupConfigCollection.cs`
|
||
|
||
|
||
## 2. 架构概览
|
||
|
||
模块运行时由以下几层组成:
|
||
|
||
### 2.1 AudioComponent
|
||
|
||
`AudioComponent` 是场景中的挂载入口,负责:
|
||
|
||
- 注册 `AudioService`
|
||
- 绑定 `AudioMixer`
|
||
- 绑定 `AudioListener`
|
||
- 绑定 `AudioGroupConfigCollection`
|
||
- 初始化运行时音频系统
|
||
|
||
### 2.2 AudioService
|
||
|
||
`AudioService` 是模块核心调度层,负责:
|
||
|
||
- 播放请求入口
|
||
- `AudioType` 维度的音量与开关管理
|
||
- `AudioClip` 缓存、引用计数、TTL、LRU
|
||
- `AudioSource` 对象池管理
|
||
- 播放句柄分配与控制
|
||
- 监听器注册
|
||
|
||
### 2.3 AudioCategory
|
||
|
||
每个 `AudioType` 对应一个 `AudioCategory`,负责:
|
||
|
||
- 维护该分类下的固定数量 `AudioAgent`
|
||
- 用空闲栈管理可用槽位
|
||
- 用最老播放堆管理满载抢占
|
||
- 更新该分类下活跃音频
|
||
|
||
### 2.4 AudioAgent
|
||
|
||
每个 `AudioAgent` 对应一个运行时播放槽位,负责:
|
||
|
||
- 绑定一个 `AudioSource`
|
||
- 管理单条播放状态
|
||
- 管理当前 `AudioClip` 引用
|
||
- 处理跟随、淡出、遮挡检测
|
||
|
||
### 2.5 AudioSourceObject
|
||
|
||
`AudioSourceObject` 是 `AudioSource` 的池化包装对象,接入框架 `ObjectPoolService`。
|
||
|
||
### 2.6 AudioClipCacheEntry
|
||
|
||
`AudioClipCacheEntry` 是单个地址的缓存条目,负责:
|
||
|
||
- `AssetHandle`
|
||
- `AudioClip`
|
||
- 引用计数
|
||
- LRU 链表节点
|
||
- pending 加载请求链
|
||
|
||
|
||
## 3. 生命周期
|
||
|
||
### 3.1 初始化
|
||
|
||
初始化链路:
|
||
|
||
1. 场景中挂载 `AudioComponent`
|
||
2. `Awake` 时注册 `AudioService`
|
||
3. `Start` 时读取 `AudioGroupConfigCollection`
|
||
4. 显式注册 `AudioListener`
|
||
5. 创建每个分类的 `AudioCategory`
|
||
6. 为每个分类预创建固定数量 `AudioAgent`
|
||
7. 每个 `AudioAgent` 从 `ObjectPoolService` 获取一个 `AudioSourceObject`
|
||
|
||
### 3.2 播放
|
||
|
||
播放链路:
|
||
|
||
1. 业务通过 `IAudioService` 发起播放
|
||
2. `AudioService` 生成池化播放请求
|
||
3. 对应 `AudioCategory` 从空闲栈取槽位,或从最老播放堆抢占
|
||
4. `AudioAgent` 绑定配置
|
||
5. 直接播放 `AudioClip` 或进入 `AudioClip` 加载/缓存逻辑
|
||
6. 播放完成、停止、淡出结束后释放引用
|
||
|
||
### 3.3 回收
|
||
|
||
回收链路:
|
||
|
||
- `AudioAgent` 停止后释放 `AudioClip` 引用
|
||
- `AudioClip` 引用计数归零后进入 LRU
|
||
- 到达 TTL 或容量超限时淘汰
|
||
- 服务销毁时统一停止播放、释放缓存、释放未使用 `AudioSource`
|
||
|
||
|
||
## 4. 场景配置
|
||
|
||
### 4.1 AudioComponent
|
||
|
||
在场景中创建一个 GameObject 挂载 `AudioComponent`。
|
||
|
||
推荐字段配置:
|
||
|
||
- `Audio Mixer`
|
||
- 指向音频混音器资源
|
||
- `Instance Root`
|
||
- 音频实例根节点
|
||
- `Audio Listener`
|
||
- 显式绑定实际生效的监听器
|
||
- `Audio Group Configs`
|
||
- 指向 `AudioGroupConfigCollection` 资源
|
||
|
||
### 4.2 为什么要显式绑定 AudioListener
|
||
|
||
当前实现不再扫描全场景查找监听器。
|
||
|
||
这样做的原因:
|
||
|
||
- 避免运行时全场景线性查找
|
||
- 避免多监听器场景中的不确定性
|
||
- 保证 3D 音频和遮挡始终基于明确目标
|
||
|
||
如果不手动绑定,`AudioComponent` 会在自身层级下尝试一次 `GetComponentInChildren<AudioListener>(true)`。
|
||
|
||
推荐规则:
|
||
|
||
- 主场景主相机上的监听器手动拖给 `AudioComponent`
|
||
- 运行时如果切换监听器,应显式重新注册
|
||
|
||
|
||
## 5. 配置资产
|
||
|
||
### 5.1 AudioGroupConfigCollection
|
||
|
||
`AudioGroupConfigCollection` 是一个 `ScriptableObject`,保存全部分类配置。
|
||
|
||
默认通过 `CreateAssetMenu` 创建:
|
||
|
||
- `AlicizaX/Audio/Audio Group Configs`
|
||
|
||
### 5.2 默认配置
|
||
|
||
新建资源后默认包含以下五组:
|
||
|
||
- `Music`
|
||
- `Sound`
|
||
- `UISound`
|
||
- `Voice`
|
||
- `Ambient`
|
||
|
||
### 5.3 AudioGroupConfig 字段说明
|
||
|
||
每个 `AudioGroupConfig` 包含:
|
||
|
||
- `音频类型`
|
||
- 对应 `AudioType`
|
||
- `名称`
|
||
- 配置展示名
|
||
- `静音`
|
||
- 初始化时该分类是否关闭
|
||
- `音量`
|
||
- 初始线性音量
|
||
- `通道数`
|
||
- 该分类最大并发 `AudioAgent` 数量
|
||
- `Mixer音量参数`
|
||
- 对应 `AudioMixer` 暴露参数名
|
||
- `空间混合`
|
||
- 2D/3D 混合比例
|
||
- `多普勒`
|
||
- 多普勒系数
|
||
- `扩散`
|
||
- 声场扩散
|
||
- `AudioSource优先级`
|
||
- Unity AudioSource Priority
|
||
- `混响区混合`
|
||
- Reverb Zone Mix
|
||
- `衰减模式`
|
||
- `AudioRolloffMode`
|
||
- `最小距离`
|
||
- 3D 音频近距离阈值
|
||
- `最大距离`
|
||
- 3D 音频远距离阈值
|
||
- `开启遮挡`
|
||
- 是否进行遮挡检测
|
||
- `遮挡检测层`
|
||
- 遮挡 Raycast LayerMask
|
||
- `遮挡检测间隔`
|
||
- 检测频率
|
||
- `遮挡低通截止频率`
|
||
- 遮挡时低通频率
|
||
- `遮挡音量系数`
|
||
- 遮挡时音量乘数
|
||
|
||
|
||
## 6. Inspector 使用
|
||
|
||
### 6.1 AudioComponent Inspector
|
||
|
||
`AudioComponentInspector` 提供:
|
||
|
||
- `音频监听器` 字段
|
||
- `音频分组配置` 字段
|
||
- 一键创建默认配置资源按钮
|
||
|
||
### 6.2 AudioGroupConfigCollection Inspector
|
||
|
||
`AudioGroupConfigCollectionInspector` 提供:
|
||
|
||
- 中文字段名
|
||
- 分类分块显示
|
||
- `Occlusion` 未开启时隐藏遮挡相关字段
|
||
|
||
|
||
## 7. 运行时 API
|
||
|
||
核心接口为 `IAudioService`。
|
||
|
||
### 7.1 全局开关
|
||
|
||
```csharp
|
||
IAudioService audio = GameApp.Audio;
|
||
|
||
audio.Volume = 1f;
|
||
audio.Enable = true;
|
||
```
|
||
|
||
### 7.2 通用分类音量接口
|
||
|
||
推荐统一使用以下接口:
|
||
|
||
```csharp
|
||
audio.SetCategoryVolume(AudioType.Music, 0.8f);
|
||
audio.SetCategoryVolume(AudioType.Sound, 1f);
|
||
|
||
float musicVolume = audio.GetCategoryVolume(AudioType.Music);
|
||
```
|
||
|
||
### 7.3 通用分类开关接口
|
||
|
||
```csharp
|
||
audio.SetCategoryEnable(AudioType.Music, true);
|
||
audio.SetCategoryEnable(AudioType.Ambient, false);
|
||
|
||
bool ambientEnabled = audio.GetCategoryEnable(AudioType.Ambient);
|
||
```
|
||
|
||
### 7.4 2D 地址播放
|
||
|
||
```csharp
|
||
ulong handle = audio.Play(
|
||
AudioType.Sound,
|
||
"Audio/SFX/Click",
|
||
loop: false,
|
||
volume: 1f,
|
||
async: false,
|
||
cacheClip: true);
|
||
```
|
||
|
||
参数说明:
|
||
|
||
- `type`
|
||
- 分类
|
||
- `path`
|
||
- 资源地址
|
||
- `loop`
|
||
- 是否循环
|
||
- `volume`
|
||
- 播放音量
|
||
- `async`
|
||
- 是否异步加载
|
||
- `cacheClip`
|
||
- 是否在播放后保留到缓存
|
||
|
||
### 7.5 直接播放 AudioClip
|
||
|
||
```csharp
|
||
ulong handle = audio.Play(AudioType.Music, clip, loop: true, volume: 1f);
|
||
```
|
||
|
||
### 7.6 3D 定点播放
|
||
|
||
```csharp
|
||
Vector3 position = hitPoint;
|
||
ulong handle = audio.Play3D(
|
||
AudioType.Sound,
|
||
"Audio/SFX/Explosion",
|
||
position,
|
||
loop: false,
|
||
volume: 1f,
|
||
async: true,
|
||
cacheClip: true);
|
||
```
|
||
|
||
### 7.7 跟随目标播放
|
||
|
||
```csharp
|
||
ulong handle = audio.PlayFollow(
|
||
AudioType.Voice,
|
||
"Audio/Voice/Npc001",
|
||
npcTransform,
|
||
Vector3.zero,
|
||
loop: false,
|
||
volume: 1f,
|
||
async: true,
|
||
cacheClip: true);
|
||
```
|
||
|
||
注意:
|
||
|
||
- 跟随播放不会把池化 `AudioSource` 挂到业务节点下
|
||
- 实际上是保持在音频根节点下,并同步世界位置/旋转
|
||
|
||
### 7.8 停止、暂停、恢复
|
||
|
||
```csharp
|
||
audio.Stop(handle, fadeout: true);
|
||
audio.Pause(handle);
|
||
audio.Resume(handle);
|
||
```
|
||
|
||
### 7.9 按分类停止
|
||
|
||
```csharp
|
||
audio.Stop(AudioType.Music, fadeout: true);
|
||
audio.StopAll(fadeout: false);
|
||
```
|
||
|
||
### 7.10 预加载与卸载
|
||
|
||
```csharp
|
||
audio.Preload(preloadList, pin: true);
|
||
audio.Unload(unloadList);
|
||
audio.ClearCache();
|
||
```
|
||
|
||
说明:
|
||
|
||
- `Preload(..., pin: true)`
|
||
- 预加载并常驻
|
||
- `Unload(...)`
|
||
- 取消 pin,并在无引用时释放
|
||
- `ClearCache()`
|
||
- 只清 unused 条目,不会强拆播放中和加载中的 clip
|
||
|
||
|
||
## 8. 句柄语义
|
||
|
||
播放返回值为 `ulong handle`。
|
||
|
||
句柄特点:
|
||
|
||
- 不是 `AudioAgent` 对象引用
|
||
- 对外只暴露控制句柄,不暴露内部实现
|
||
- 使用代际句柄避免旧句柄误命中新播放实例
|
||
|
||
推荐规则:
|
||
|
||
- 业务层如需后续停止某条音频,保存 `handle`
|
||
- 不需要控制的瞬时音效可忽略返回值
|
||
|
||
|
||
## 9. 缓存策略
|
||
|
||
### 9.1 基本机制
|
||
|
||
每个 `AudioClip` 地址对应一个 `AudioClipCacheEntry`。
|
||
|
||
条目包含:
|
||
|
||
- `AssetHandle`
|
||
- `AudioClip`
|
||
- `RefCount`
|
||
- `Pinned`
|
||
- `CacheAfterUse`
|
||
- `Loading`
|
||
- `PendingHead / PendingTail`
|
||
- `LRU` 节点
|
||
|
||
### 9.2 引用计数
|
||
|
||
播放开始时:
|
||
|
||
- `RetainClip`
|
||
|
||
播放结束时:
|
||
|
||
- `ReleaseClip`
|
||
|
||
### 9.3 LRU 与 TTL
|
||
|
||
当 `RefCount == 0` 时:
|
||
|
||
- 若 `CacheAfterUse == true`,进入 LRU
|
||
- 若 `CacheAfterUse == false`,立即清理
|
||
|
||
缓存会在以下情况被淘汰:
|
||
|
||
- 超过容量
|
||
- 超过 TTL
|
||
|
||
### 9.4 Pinned 资源
|
||
|
||
通过 `Preload(..., pin: true)` 的资源为 pinned。
|
||
|
||
特点:
|
||
|
||
- 不会被普通缓存淘汰
|
||
- 必须显式 `Unload(...)` 解除 pin
|
||
|
||
|
||
## 10. AudioSource 池化
|
||
|
||
### 10.1 池化来源
|
||
|
||
`AudioSourceObject` 使用框架内置 `ObjectPoolService`。
|
||
|
||
每个分类初始化时会创建固定数量 `AudioAgent`。
|
||
每个 `AudioAgent` 都会对应一个可复用 `AudioSourceObject`。
|
||
|
||
### 10.2 回收时机
|
||
|
||
模块销毁时:
|
||
|
||
- 停止所有播放
|
||
- 分类销毁
|
||
- `AudioSourceObject` 归还池
|
||
- 未使用池对象统一释放
|
||
|
||
|
||
## 11. 3D 空间音频
|
||
|
||
### 11.1 空间混合
|
||
|
||
由以下字段决定:
|
||
|
||
- `AudioGroupConfig.SpatialBlend`
|
||
- 播放请求是否是 3D / Follow / WorldPosition
|
||
|
||
### 11.2 距离衰减
|
||
|
||
由以下字段决定:
|
||
|
||
- `RolloffMode`
|
||
- `MinDistance`
|
||
- `MaxDistance`
|
||
|
||
### 11.3 遮挡
|
||
|
||
如果开启 `Occlusion`:
|
||
|
||
- 按配置时间间隔执行 `Physics.Raycast`
|
||
- 命中遮挡层后:
|
||
- 启用 `AudioLowPassFilter`
|
||
- 设置低通频率
|
||
- 按 `OcclusionVolumeMultiplier` 压低音量
|
||
|
||
### 11.4 监听器来源
|
||
|
||
监听器只来自显式注册:
|
||
|
||
- `AudioComponent` 绑定的 `AudioListener`
|
||
- 或未来业务显式切换注册
|
||
|
||
|
||
## 12. 抢占策略
|
||
|
||
当某个 `AudioCategory` 已满:
|
||
|
||
- 先尝试空闲栈取空槽
|
||
- 若没有空槽,则从“最老播放堆”中取最早开始播放的槽位进行复用
|
||
|
||
特点:
|
||
|
||
- 无线性扫描
|
||
- 高并发下行为确定
|
||
- CPU 成本稳定
|
||
|
||
|
||
## 13. 与资源模块的关系
|
||
|
||
音频模块不直接把 `AudioClip` 交给资源模块对象池管理,而是:
|
||
|
||
- 使用 `IResourceService.LoadAssetSyncHandle<T>()`
|
||
- 使用 `IResourceService.LoadAssetAsyncHandle<T>()`
|
||
- 自己持有 `AssetHandle`
|
||
- 在缓存淘汰或销毁时 `Dispose()`
|
||
|
||
这样做的原因:
|
||
|
||
- 音频需要精细控制引用计数
|
||
- 音频有自己的 `pin/LRU/TTL/pending` 语义
|
||
|
||
|
||
## 14. 常见使用模式
|
||
|
||
### 14.1 BGM
|
||
|
||
```csharp
|
||
var audio = GameApp.Audio;
|
||
audio.SetCategoryVolume(AudioType.Music, 0.8f);
|
||
|
||
ulong bgmHandle = audio.Play(
|
||
AudioType.Music,
|
||
"Audio/BGM/MainTheme",
|
||
loop: true,
|
||
volume: 1f,
|
||
async: true,
|
||
cacheClip: true);
|
||
```
|
||
|
||
### 14.2 UI 点击音效
|
||
|
||
```csharp
|
||
GameApp.Audio.Play(
|
||
AudioType.UISound,
|
||
"Audio/UI/Click",
|
||
loop: false,
|
||
volume: 1f,
|
||
async: false,
|
||
cacheClip: true);
|
||
```
|
||
|
||
### 14.3 角色语音
|
||
|
||
```csharp
|
||
GameApp.Audio.PlayFollow(
|
||
AudioType.Voice,
|
||
"Audio/Voice/Hero/Greeting",
|
||
heroTransform,
|
||
Vector3.up * 1.5f,
|
||
loop: false,
|
||
volume: 1f,
|
||
async: true,
|
||
cacheClip: false);
|
||
```
|
||
|
||
### 14.4 环境循环声
|
||
|
||
```csharp
|
||
GameApp.Audio.Play3D(
|
||
AudioType.Ambient,
|
||
"Audio/Ambient/WaterfallLoop",
|
||
waterfallPosition,
|
||
loop: true,
|
||
volume: 1f,
|
||
async: true,
|
||
cacheClip: true);
|
||
```
|
||
|
||
|
||
## 15. 运行时切换监听器
|
||
|
||
如果项目存在相机切换或监听器切换逻辑,应显式调用:
|
||
|
||
```csharp
|
||
IAudioService audio = GameApp.Audio;
|
||
audio.UnregisterListener(oldListener);
|
||
audio.RegisterListener(newListener);
|
||
```
|
||
|
||
建议规则:
|
||
|
||
- 保证同一时刻只有一个主监听器负责注册
|
||
- 切换时先注销旧监听器,再注册新监听器
|
||
|
||
|
||
## 16. 扩展组件
|
||
|
||
### 16.1 AudioListenerBinder
|
||
|
||
`AudioListenerBinder` 用于把场景内的 `AudioListener` 显式注册到 `IAudioService`。
|
||
|
||
适用场景:
|
||
|
||
- 主相机不在 `AudioComponent` 同一节点下
|
||
- 运行时相机或监听器会切换
|
||
- 不希望 `AudioComponent` 扫描子节点查找监听器
|
||
|
||
使用方式:
|
||
|
||
1. 在带有 `AudioListener` 的对象上添加 `AudioListenerBinder`。
|
||
2. 确保场景中已经有 `AudioComponent` 初始化音频服务。
|
||
3. 对象启用时自动注册,禁用时自动注销。
|
||
|
||
### 16.2 AudioEmitter
|
||
|
||
`AudioEmitter` 是场景 3D 声源组件,面向篝火、电台、瀑布、机器噪音等固定或跟随物体的循环环境声。
|
||
|
||
核心字段:
|
||
|
||
- `Audio Type`:播放分类,环境声通常使用 `Ambient`。
|
||
- `Address`:音频资源地址。
|
||
- `Play On Enable`:对象启用时播放。
|
||
- `Loop`:是否循环。
|
||
- `Follow Self`:是否跟随当前物体。
|
||
- `Min Distance`:近距离清晰范围。
|
||
- `Max Distance`:超过该距离后听不到或接近听不到。
|
||
- `Use Trigger Range`:是否进入半径才播放。
|
||
- `Trigger Range`:自定义进入播放区域。
|
||
- `Trigger Hysteresis`:离开判定缓冲,避免边界频繁启停。
|
||
- `Draw Gizmos`:绘制触发范围和衰减范围。
|
||
|
||
篝火使用方式:
|
||
|
||
1. 在篝火对象上添加 `AudioEmitter`。
|
||
2. `Audio Type` 设为 `Ambient`。
|
||
3. `Address` 填篝火循环音效地址。
|
||
4. 开启 `Play On Enable`、`Loop`、`Follow Self`。
|
||
5. 关闭 `Use Trigger Range`。
|
||
6. 设置 `Min Distance` 为清晰听到的距离,例如 `2`。
|
||
7. 设置 `Max Distance` 为听不到的距离,例如 `18`。
|
||
|
||
这样玩家靠近篝火时声音更清晰,远离到最大距离后由 Unity 3D 衰减处理到不可闻。
|
||
|
||
电台使用方式:
|
||
|
||
1. 在电台对象上添加 `AudioEmitter`。
|
||
2. `Audio Type` 设为 `Ambient` 或 `Voice`。
|
||
3. `Address` 填电台循环音效地址。
|
||
4. 开启 `Loop`、`Follow Self`、`Use Trigger Range`。
|
||
5. 设置 `Trigger Range` 为进入区域,例如 `8`。
|
||
6. 设置 `Min Distance` 和 `Max Distance` 控制区域内的清晰范围与衰减。
|
||
7. 开启 `Draw Gizmos`,在 Scene 视图查看触发范围和衰减范围。
|
||
|
||
注意事项:
|
||
|
||
- `AudioEmitter` 不做场景扫描,不读取私有配置,不创建临时对象。
|
||
- 进入区域只触发一次;非循环音效播放完后不会在范围内每帧重播,离开再进入才会重新触发。
|
||
- `Play3D` 静态声源和 `PlayFollow` 跟随声源都支持单次播放覆盖 `MinDistance`、`MaxDistance`、`RolloffMode`、`SpatialBlend`。
|
||
|
||
|
||
## 17. 性能建议
|
||
|
||
### 17.1 推荐
|
||
|
||
- 高频短音效使用同步加载 + `cacheClip: true`
|
||
- 大型语音或低频资源可异步加载
|
||
- 常驻 BGM / 高频 UI 音效建议提前 `Preload`
|
||
- 明确设置分类通道数,避免过小导致过度抢占
|
||
- 3D 语音/环境音再开启遮挡,不要给所有分类都开
|
||
|
||
### 17.2 不推荐
|
||
|
||
- 每次播放都 `cacheClip: false` 且地址重复
|
||
- 不显式绑定 `AudioListener`
|
||
- 给 `Music` 设置过高空间混合
|
||
- 给大量瞬时音效开启高频遮挡检测
|
||
|
||
|
||
## 18. 常见问题
|
||
|
||
### 18.1 为什么播放返回 0
|
||
|
||
可能原因:
|
||
|
||
- `AudioService` 尚未初始化
|
||
- `AudioType` 越界
|
||
- `AudioComponent` 未正确配置
|
||
- `AudioListener` 未注册时 3D 音频相关行为不完整
|
||
|
||
### 18.2 为什么 `ClearCache()` 后仍有部分资源未释放
|
||
|
||
因为:
|
||
|
||
- 播放中的 clip 仍有引用
|
||
- 正在加载的 clip 仍有 pending request
|
||
- pinned 资源未调用 `Unload`
|
||
|
||
### 18.3 为什么跟随音频不会挂到目标节点下
|
||
|
||
这是刻意设计:
|
||
|
||
- 防止业务节点销毁时把池对象一起销毁
|
||
- 防止池生命周期被业务层意外接管
|
||
|
||
|
||
## 19. 扩展建议
|
||
|
||
如果后续继续扩展模块,优先遵守以下方向:
|
||
|
||
- 核心接口只保留 `AudioType` 通用访问
|
||
- 不再继续膨胀 `IAudioService`
|
||
- 新分类优先通过:
|
||
- `AudioType`
|
||
- `AudioGroupConfigCollection`
|
||
- `AudioMixer` 暴露参数
|
||
- 通用 `Get/SetCategory` API
|
||
|
||
不推荐继续在核心接口里新增:
|
||
|
||
- `NpcVoiceVolume`
|
||
- `BattleMusicEnable`
|
||
- `CutsceneAmbientVolume`
|
||
|
||
这类业务语义应放在业务 facade 或上层系统里。
|
||
|
||
|
||
## 20. 自测清单
|
||
|
||
修改音频模块后建议至少回归以下场景:
|
||
|
||
- 2D 同步播放
|
||
- 2D 异步播放
|
||
- 3D 定点播放
|
||
- Follow 播放
|
||
- 目标销毁时的 Follow 回收
|
||
- 分类满载时抢占
|
||
- `Preload(pin: true)` 与 `Unload`
|
||
- `ClearCache()` 对 unused 资源的清理
|
||
- 遮挡开启/关闭切换
|
||
- 监听器切换
|
||
|
||
|
||
## 21. 总结
|
||
|
||
当前 `Audio` 模块的核心使用原则可以归纳为:
|
||
|
||
- 入口统一从 `IAudioService` 走
|
||
- 分类控制统一用 `AudioType`
|
||
- 监听器显式注册,不做全场景扫描
|
||
- 高频资源使用缓存与预加载
|
||
- 3D 音频通过配置资产控制,不在业务层硬编码
|
||
- 池对象生命周期永远由音频系统自己掌握
|
||
|
||
如果严格遵守以上规则,模块可以稳定支撑常规项目中的 BGM、SFX、UI、Voice、Ambient 五类音频需求。
|