com.alicizax.unity.framework/Editor/Audio/AudioComponentInspector.cs
陈思海 46194ddee8 重构音频模块 1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优 2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历 3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点 4. 支持3D环境音并具备距离衰减、遮挡等空间属性 5. 新增音频类型(BGM/SFX/Voice/Ambient) 6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
重构音频模块
1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优
2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历
3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点
4. 支持3D环境音并具备距离衰减、遮挡等空间属性
5. 新增音频类型(BGM/SFX/Voice/Ambient)
6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
2026-04-23 17:21:36 +08:00

202 lines
8.6 KiB
C#

using System;
using AlicizaX.Audio.Runtime;
using AlicizaX.Editor;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.Audio.Editor
{
[CustomEditor(typeof(AudioComponent))]
internal sealed class AudioComponentInspector : GameFrameworkInspector
{
private readonly AudioServiceDebugInfo _serviceInfo = new AudioServiceDebugInfo();
private readonly AudioCategoryDebugInfo _categoryInfo = new AudioCategoryDebugInfo();
private readonly AudioAgentDebugInfo _agentInfo = new AudioAgentDebugInfo();
private readonly AudioClipCacheDebugInfo _clipCacheInfo = new AudioClipCacheDebugInfo();
private SerializedProperty m_InstanceRoot = null;
private SerializedProperty m_AudioListener = null;
private SerializedProperty m_AudioMixer = null;
private SerializedProperty m_AudioGroupConfigs = null;
private static readonly GUIContent GroupConfigLabel = new GUIContent("音频分组配置");
private static readonly GUIContent CreateButtonLabel = new GUIContent("创建默认配置资源");
private static readonly GUIContent AudioListenerLabel = new GUIContent("音频监听器");
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
AudioComponent t = (AudioComponent)target;
EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
{
EditorGUILayout.PropertyField(m_InstanceRoot);
EditorGUILayout.PropertyField(m_AudioListener, AudioListenerLabel);
EditorGUILayout.PropertyField(m_AudioMixer);
EditorGUILayout.PropertyField(m_AudioGroupConfigs, GroupConfigLabel);
if (m_AudioGroupConfigs.objectReferenceValue == null && GUILayout.Button(CreateButtonLabel))
{
CreateDefaultConfigAsset();
}
}
EditorGUI.EndDisabledGroup();
serializedObject.ApplyModifiedProperties();
DrawRuntimeDebugInfo(t);
Repaint();
}
private void OnEnable()
{
m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
m_AudioListener = serializedObject.FindProperty("m_AudioListener");
m_AudioMixer = serializedObject.FindProperty("m_AudioMixer");
m_AudioGroupConfigs = serializedObject.FindProperty("m_AudioGroupConfigs");
}
private void CreateDefaultConfigAsset()
{
AudioGroupConfigCollection asset = ScriptableObject.CreateInstance<AudioGroupConfigCollection>();
asset.EnsureDefaults();
string path = EditorUtility.SaveFilePanelInProject("创建音频分组配置", "AudioGroupConfigs", "asset", "请选择保存路径");
if (string.IsNullOrEmpty(path))
{
UnityEngine.Object.DestroyImmediate(asset);
return;
}
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
m_AudioGroupConfigs.objectReferenceValue = asset;
serializedObject.ApplyModifiedProperties();
EditorGUIUtility.PingObject(asset);
}
private void DrawRuntimeDebugInfo(AudioComponent component)
{
if (!EditorApplication.isPlaying)
{
return;
}
if (!AppServices.TryGet<IAudioService>(out IAudioService audioService) || audioService is not IAudioDebugService debugService)
{
EditorGUILayout.HelpBox("音频服务未初始化。", MessageType.Info);
return;
}
debugService.FillServiceDebugInfo(_serviceInfo);
EditorGUILayout.Space();
EditorGUILayout.LabelField("运行时调试", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Initialized", _serviceInfo.Initialized.ToString());
EditorGUILayout.LabelField("Unity Audio Disabled", _serviceInfo.UnityAudioDisabled.ToString());
EditorGUILayout.LabelField("Enable", _serviceInfo.Enable.ToString());
EditorGUILayout.LabelField("Volume", _serviceInfo.Volume.ToString("F3"));
EditorGUILayout.ObjectField("Listener", _serviceInfo.Listener, typeof(AudioListener), true);
EditorGUILayout.ObjectField("Instance Root", _serviceInfo.InstanceRoot, typeof(Transform), true);
EditorGUILayout.LabelField("Active Agents", _serviceInfo.ActiveAgentCount.ToString());
EditorGUILayout.LabelField("Handle Capacity", _serviceInfo.HandleCapacity.ToString());
EditorGUILayout.LabelField("Clip Cache", _serviceInfo.ClipCacheCount + " / " + _serviceInfo.ClipCacheCapacity);
DrawCategoryDebugInfo(debugService);
DrawAgentDebugInfo(debugService);
DrawClipCacheDebugInfo(debugService);
}
private void DrawCategoryDebugInfo(IAudioDebugService debugService)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("分类状态", EditorStyles.boldLabel);
for (int i = 0; i < debugService.CategoryCount; i++)
{
if (!debugService.FillCategoryDebugInfo(i, _categoryInfo))
{
continue;
}
EditorGUILayout.LabelField(
_categoryInfo.Type.ToString(),
"Enabled " + _categoryInfo.Enabled
+ " | Volume " + _categoryInfo.Volume.ToString("F2")
+ " | Active " + _categoryInfo.ActiveCount
+ " | Free " + _categoryInfo.FreeCount
+ " | Capacity " + _categoryInfo.Capacity);
}
}
private void DrawAgentDebugInfo(IAudioDebugService debugService)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("活跃播放", EditorStyles.boldLabel);
bool hasActive = false;
for (int typeIndex = 0; typeIndex < debugService.CategoryCount; typeIndex++)
{
if (!debugService.FillCategoryDebugInfo(typeIndex, _categoryInfo))
{
continue;
}
for (int agentIndex = 0; agentIndex < _categoryInfo.Capacity; agentIndex++)
{
if (!debugService.FillAgentDebugInfo(typeIndex, agentIndex, _agentInfo) || _agentInfo.State == AudioAgentRuntimeState.Free)
{
continue;
}
hasActive = true;
string clipName = _agentInfo.Clip != null ? _agentInfo.Clip.name : "<None>";
string address = string.IsNullOrEmpty(_agentInfo.Address) ? "<Direct Clip>" : _agentInfo.Address;
EditorGUILayout.LabelField(
_agentInfo.Type + "[" + _agentInfo.Index + "]",
_agentInfo.State
+ " | Handle " + _agentInfo.Handle
+ " | Clip " + clipName
+ " | Address " + address
+ " | Vol " + _agentInfo.Volume.ToString("F2")
+ " | 3D " + _agentInfo.Spatial);
}
}
if (!hasActive)
{
EditorGUILayout.LabelField("无活跃播放。");
}
}
private void DrawClipCacheDebugInfo(IAudioDebugService debugService)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Clip 缓存", EditorStyles.boldLabel);
AudioClipCacheEntry entry = debugService.FirstClipCacheEntry;
if (entry == null)
{
EditorGUILayout.LabelField("缓存为空。");
return;
}
while (entry != null)
{
AudioClipCacheEntry next = entry.AllNext;
if (debugService.FillClipCacheDebugInfo(entry, _clipCacheInfo))
{
EditorGUILayout.LabelField(
_clipCacheInfo.Address,
"Ref " + _clipCacheInfo.RefCount
+ " | Pending " + _clipCacheInfo.PendingCount
+ " | Loaded " + _clipCacheInfo.IsLoaded
+ " | Loading " + _clipCacheInfo.Loading
+ " | Pinned " + _clipCacheInfo.Pinned
+ " | LRU " + _clipCacheInfo.InLru);
}
entry = next;
}
}
}
}