重构音频模块 1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优 2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历 3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点 4. 支持3D环境音并具备距离衰减、遮挡等空间属性 5. 新增音频类型(BGM/SFX/Voice/Ambient) 6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
202 lines
8.6 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|