From 0be503c927414c75ccba539c64bce38173ceb9cd Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Fri, 10 Jul 2026 13:25:56 +0200 Subject: [PATCH] Add SoundEffect audio system (ScriptableObject-based) with test assets Co-Authored-By: Claude Fable 5 --- Assets/Scripts/Audio.meta | 8 + Assets/Scripts/Audio/Editor.meta | 8 + .../Audio/Editor/AudioMixerSliderGUI.cs | 114 +++++++++++ .../Audio/Editor/AudioMixerSliderGUI.cs.meta | 11 ++ .../Scripts/Audio/Editor/AudioMixerWindow.cs | 142 ++++++++++++++ .../Audio/Editor/AudioMixerWindow.cs.meta | 2 + .../Scripts/Audio/Editor/AudioMixerWindow.uss | 41 ++++ .../Audio/Editor/AudioMixerWindow.uss.meta | 11 ++ .../Audio/Editor/AudioMixerWindow.uxml | 10 + .../Audio/Editor/AudioMixerWindow.uxml.meta | 10 + Assets/Scripts/Audio/SoundEffect.cs | 177 ++++++++++++++++++ Assets/Scripts/Audio/SoundEffect.cs.meta | 11 ++ .../Scripts/Audio/SoundEffectDistanceRange.cs | 10 + .../Audio/SoundEffectDistanceRange.cs.meta | 11 ++ Assets/Scripts/Audio/SoundEffectInstance.cs | 65 +++++++ .../Scripts/Audio/SoundEffectInstance.cs.meta | 11 ++ Assets/Scripts/Audio/SoundEffectOptions.cs | 32 ++++ .../Scripts/Audio/SoundEffectOptions.cs.meta | 11 ++ Assets/Sound Effect Distance Range.asset | 15 ++ Assets/Sound Effect Distance Range.asset.meta | 8 + Assets/Test Sound Effect.asset | 25 +++ Assets/Test Sound Effect.asset.meta | 8 + 22 files changed, 741 insertions(+) create mode 100644 Assets/Scripts/Audio.meta create mode 100644 Assets/Scripts/Audio/Editor.meta create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs.meta create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerWindow.cs create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerWindow.cs.meta create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerWindow.uss create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerWindow.uss.meta create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerWindow.uxml create mode 100644 Assets/Scripts/Audio/Editor/AudioMixerWindow.uxml.meta create mode 100644 Assets/Scripts/Audio/SoundEffect.cs create mode 100644 Assets/Scripts/Audio/SoundEffect.cs.meta create mode 100644 Assets/Scripts/Audio/SoundEffectDistanceRange.cs create mode 100644 Assets/Scripts/Audio/SoundEffectDistanceRange.cs.meta create mode 100644 Assets/Scripts/Audio/SoundEffectInstance.cs create mode 100644 Assets/Scripts/Audio/SoundEffectInstance.cs.meta create mode 100644 Assets/Scripts/Audio/SoundEffectOptions.cs create mode 100644 Assets/Scripts/Audio/SoundEffectOptions.cs.meta create mode 100644 Assets/Sound Effect Distance Range.asset create mode 100644 Assets/Sound Effect Distance Range.asset.meta create mode 100644 Assets/Test Sound Effect.asset create mode 100644 Assets/Test Sound Effect.asset.meta diff --git a/Assets/Scripts/Audio.meta b/Assets/Scripts/Audio.meta new file mode 100644 index 0000000..61ece2b --- /dev/null +++ b/Assets/Scripts/Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8938d848d62e6114092296326d25e582 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Audio/Editor.meta b/Assets/Scripts/Audio/Editor.meta new file mode 100644 index 0000000..5af3d59 --- /dev/null +++ b/Assets/Scripts/Audio/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bb15841fc23ed264e98e4b3c07a3c742 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs b/Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs new file mode 100644 index 0000000..36d48af --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs @@ -0,0 +1,114 @@ +using UnityEditor; + +using UnityEngine; +using UnityEngine.UIElements; + +namespace Prototype.Editor +{ + public class AudioMixerSliderGUI : IMGUIContainer + { + public Object @object; + public string propertyPath; + public Vector2 valueRange = new(0, 1); + public float defaultValue; + + bool dragging; + + public AudioMixerSliderGUI() : base() + { + onGUIHandler = OnGUI; + + RegisterCallback(OnMouseDown); + RegisterCallback(OnMouseMove); + RegisterCallback(OnMouseUp); + } + + SerializedProperty property + => @object && !string.IsNullOrEmpty(propertyPath) + ? new SerializedObject(@object).FindProperty(propertyPath) + : null; + + public float value + { + get + { + return property?.floatValue ?? 0; + } + set + { + var property = this.property; + if (property is null) + return; + + var newValue = Mathf.Clamp(value, valueRange.x, valueRange.y); + if (property.floatValue == newValue) + return; + + property.floatValue = newValue; + property.serializedObject.ApplyModifiedProperties(); + MarkDirtyRepaint(); + } + } + + Rect r => contentRect; + float s => r.width / 12; + float h => r.height / 16; + Rect r_inner => new(0, h / 2 + 8, r.width - s * 2, r.height - h - 18); + Rect r_slide => new(r.width / 2 - s / 2, r_inner.y, s, r_inner.height); + + Rect r_button + { + get + { + var t = Mathf.InverseLerp(valueRange.x, valueRange.y, value); + + return new( + (r.width - r_inner.width) / 2, + r_inner.y + r_inner.height * (1 - t) - h / 2, + r_inner.width, + h + ); + } + } + + void OnGUI() + { + EditorGUI.DrawRect(r, new(0, 0, 0, .3f)); + EditorGUI.DrawRect(r_slide, new(0, 0, 0, .4f)); + EditorGUI.DrawRect(r_button, new(1, 1, .6f, dragging ? .8f : .4f)); + } + + void OnMouseDown(MouseDownEvent e) + { + switch (e.button) + { + case 0: + dragging = true; + break; + + case 1: + value = defaultValue; + break; + } + } + + void OnMouseMove(MouseMoveEvent e) + { + if (!dragging) + return; + + var t = 1 - (e.localMousePosition.y - r_inner.y) / r_inner.height; + value = Mathf.Lerp(valueRange.x, valueRange.y, t); + } + + void OnMouseUp(MouseUpEvent e) + { + switch (e.button) + { + case 0: + dragging = false; + break; + } + } + } +} diff --git a/Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs.meta b/Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs.meta new file mode 100644 index 0000000..f80bd5f --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerSliderGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c818c5d558214584f80ca414dd1cab70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Audio/Editor/AudioMixerWindow.cs b/Assets/Scripts/Audio/Editor/AudioMixerWindow.cs new file mode 100644 index 0000000..215e288 --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerWindow.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; + +using UnityEditor; +using UnityEditor.UIElements; + +using UnityEngine; +using UnityEngine.Audio; +using UnityEngine.UIElements; + +namespace Prototype.Editor +{ + public class AudioMixerWindow : EditorWindow + { + [MenuItem("Tools/Audio Mixer")] + static void Init() + { + GetWindow().titleContent = new("Audio Mixer"); + } + + AudioMixerGroup audioMixerGroup; + + List soundEffects; + + readonly List<(SoundEffect soundEffect, VisualElement element)> soundEffectTracks = new(); + + void OnEnable() + { + audioMixerGroup = null; + + soundEffects = null; + + RefreshLayout(); + } + + void RefreshLayout() + { + var root = AssetDatabase.LoadAssetAtPath( + AssetDatabase.GUIDToAssetPath( + AssetDatabase.FindAssets( + $"t:{nameof(VisualTreeAsset)} {nameof(AudioMixerWindow)}" + )[0] + ) + ).Instantiate(); + + rootVisualElement.Clear(); + rootVisualElement.Add(root); + + root.StretchToParentSize(); + + root.Q().RegisterValueChangedCallback(x => + { + audioMixerGroup = x.newValue as AudioMixerGroup; + RefreshTracksVisibility(); + }); + } + + void OnInspectorUpdate() + { + var refreshTracksVisibility = false; + + if (soundEffects is null) + { + soundEffects = new(); + + foreach (var guid in AssetDatabase.FindAssets($"t:{nameof(SoundEffect)}")) + { + soundEffects.Add( + AssetDatabase.LoadAssetAtPath( + AssetDatabase.GUIDToAssetPath(guid) + ) + ); + } + + RefreshSoundEffectTracks(); + refreshTracksVisibility = true; + } + + if (refreshTracksVisibility) + RefreshTracksVisibility(); + } + + void RefreshSoundEffectTracks() + { + soundEffectTracks.Clear(); + + if (soundEffects is not null) + { + foreach (var soundEffect in soundEffects) + { + var track = new VisualElement(); + track.AddToClassList("track"); + var applyButton = new Button(soundEffect.ApplyVolumeMultiplierFactor) + { + text = "Apply" + }; + applyButton.AddToClassList("accent"); + track.Add(applyButton); + track.Add( + new AudioMixerSliderGUI + { + @object = soundEffect, + propertyPath = "volumeMultiplierFactor", + valueRange = new Vector2(0, 2), + defaultValue = 1 + } + ); + track.Add( + new Label( + soundEffect.name + .Replace(" (Sound Effect)", "") + .Replace(" - BGM", "") + .Replace(" - SFX", "") + ) + ); + track.Add( + new Button(() => Selection.activeObject = soundEffect) + { + text = "Select" + } + ); + soundEffectTracks.Add((soundEffect, track)); + } + } + } + + void RefreshTracksVisibility() + { + var content = rootVisualElement.Q().contentContainer; + content.Clear(); + + foreach (var (soundEffect, track) in soundEffectTracks) + { + if (audioMixerGroup && + audioMixerGroup != soundEffect.audioMixerGroup) + { + continue; + } + content.Add(track); + } + } + } +} diff --git a/Assets/Scripts/Audio/Editor/AudioMixerWindow.cs.meta b/Assets/Scripts/Audio/Editor/AudioMixerWindow.cs.meta new file mode 100644 index 0000000..2191ae6 --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerWindow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: db79bb44fd8fed0449189fb735c16e0a \ No newline at end of file diff --git a/Assets/Scripts/Audio/Editor/AudioMixerWindow.uss b/Assets/Scripts/Audio/Editor/AudioMixerWindow.uss new file mode 100644 index 0000000..ed54c6b --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerWindow.uss @@ -0,0 +1,41 @@ +#root #unity-content-container { + padding-left: 2px; +} + +.track { + margin-right: 2px; + width: 50px; +} + +.track > AudioMixerSliderGUI { + flex-grow: 1; +} + +.track > Label { + overflow: hidden; + height: 42px; + white-space: normal; + -unity-text-align: middle-center; + font-size: 9px; +} + +Button { + padding: 2.5px 0; + margin: 2px 0; + font-size: 10.5px; + border-color: transparent; + border-radius: 0; +} + +Button { background-color: rgba(105, 155, 205, .25) } +Button:hover { background-color: rgba(55, 155, 255, .4) } +Button:active { background-color: rgba(0, 155, 255, .6) } + +Button.accent { background-color: rgba(55, 205, 105, .25) } +Button.accent:hover { background-color: rgba(0, 205, 105, .4) } +Button.accent:active { background-color: rgba(0, 205, 105, .55) } + +Button.disabled { + background-color: rgba(0, 0, 0, .2); + color: transparent; +} diff --git a/Assets/Scripts/Audio/Editor/AudioMixerWindow.uss.meta b/Assets/Scripts/Audio/Editor/AudioMixerWindow.uss.meta new file mode 100644 index 0000000..adfda1a --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerWindow.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe45eeb67beba6548bfeab8e7921df39 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Assets/Scripts/Audio/Editor/AudioMixerWindow.uxml b/Assets/Scripts/Audio/Editor/AudioMixerWindow.uxml new file mode 100644 index 0000000..4710a55 --- /dev/null +++ b/Assets/Scripts/Audio/Editor/AudioMixerWindow.uxml @@ -0,0 +1,10 @@ + +