From f6dbee5b5bb0fd95dff5b2319cf2d5a7a4b5c8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Tue, 28 Apr 2026 19:34:08 +0800 Subject: [PATCH] 1 --- Runtime/Audio/Audio.md | 749 ------------------------------------ Runtime/Audio/Audio.md.meta | 7 - 2 files changed, 756 deletions(-) delete mode 100644 Runtime/Audio/Audio.md delete mode 100644 Runtime/Audio/Audio.md.meta diff --git a/Runtime/Audio/Audio.md b/Runtime/Audio/Audio.md deleted file mode 100644 index dd09e6e..0000000 --- a/Runtime/Audio/Audio.md +++ /dev/null @@ -1,749 +0,0 @@ -# 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(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()` -- 使用 `IResourceService.LoadAssetAsyncHandle()` -- 自己持有 `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 五类音频需求。 diff --git a/Runtime/Audio/Audio.md.meta b/Runtime/Audio/Audio.md.meta deleted file mode 100644 index 9ac8a9e..0000000 --- a/Runtime/Audio/Audio.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 6a19d8c0bd042124c9adb78b14cbb9c0 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: