diff --git a/VisualPinball.Engine/VPT/MechSounds.meta b/VisualPinball.Engine/VPT/MechSounds.meta new file mode 100644 index 000000000..4d6559344 --- /dev/null +++ b/VisualPinball.Engine/VPT/MechSounds.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f9a957a9e90457b45ba74b1c0b726c3f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Presets.meta b/VisualPinball.Unity/Assets/Presets.meta index 0d4105838..d3dab9898 100644 --- a/VisualPinball.Unity/Assets/Presets.meta +++ b/VisualPinball.Unity/Assets/Presets.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 74b6f483aa6bd6c49bc05dca5f2c6750 +guid: a61d04b442140514a9bfb858f9ed8f05 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound.meta new file mode 100644 index 000000000..dc4a50b28 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d6280d7fb0f340b09b058831259ab274 +timeCreated: 1677682143 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs new file mode 100644 index 000000000..84bac4b74 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs @@ -0,0 +1,112 @@ +// Visual Pinball Engine +// Copyright (C) 2023 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace VisualPinball.Unity.Editor +{ + [CustomPropertyDrawer(typeof(MechSound))] + public class MechSoundDrawer : PropertyDrawer + { + public override VisualElement CreatePropertyGUI(SerializedProperty property) + { + var container = new VisualElement(); + var treeAsset = AssetDatabase.LoadAssetAtPath( + "Packages/org.visualpinball.engine.unity/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml"); + treeAsset.CloneTree(container); + var triggerDropdown = container.Q("trigger-id"); + var stopTriggerDropdown = container.Q("stop-trigger-id"); + var hasStopTriggerToggle = container.Q("has-stop-trigger"); + var availableTriggers = GetAvailableTriggers(property); + if (availableTriggers.Length > 0) { + var triggerIdProp = property.FindPropertyRelative("TriggerId"); + var stopTriggerIdProp = property.FindPropertyRelative("StopTriggerId"); + ConfigureTriggerDropdown(triggerIdProp, triggerDropdown, availableTriggers); + ConfigureTriggerDropdown(stopTriggerIdProp, stopTriggerDropdown, availableTriggers); + hasStopTriggerToggle.RegisterValueChangedCallback( + e => stopTriggerDropdown.style.display = e.newValue ? DisplayStyle.Flex : DisplayStyle.None); + var hasStopTriggerProp = property.FindPropertyRelative("HasStopTrigger"); + ConfigureInfiniteLoopHelpBox(property, container, hasStopTriggerToggle, hasStopTriggerProp); + } else { + AddNoTriggersHelpBox(container, triggerDropdown, stopTriggerDropdown, hasStopTriggerToggle); + } + property.serializedObject.ApplyModifiedProperties(); + return container; + } + + private static void ConfigureTriggerDropdown(SerializedProperty triggerIdProp, DropdownField triggerDropdown, SoundTrigger[] availableTriggers) + { + var availableTriggerNames = availableTriggers.Select(t => t.Name).ToList(); + triggerDropdown.choices = availableTriggerNames; + + var isSelectedTriggerValid = availableTriggers.Any(t => t.Id == triggerIdProp.stringValue); + if (isSelectedTriggerValid) { + triggerDropdown.value = availableTriggers.First(t => t.Id == triggerIdProp.stringValue).Name; + } else { + triggerDropdown.value = availableTriggerNames[0]; + triggerIdProp.stringValue = availableTriggers[0].Id; + } + + triggerDropdown.RegisterValueChangedCallback( + e => { + triggerIdProp.stringValue = availableTriggers.FirstOrDefault(t => t.Name == e.newValue).Id; + triggerIdProp.serializedObject.ApplyModifiedProperties(); + }); + } + + private static void AddNoTriggersHelpBox(VisualElement container, DropdownField triggerDropdown, DropdownField stopTriggerDropdown, Toggle hasStopTriggerToggle) + { + container.Insert(0, new HelpBox("There are no triggers to choose from", HelpBoxMessageType.Info)); + triggerDropdown.style.display = DisplayStyle.None; + stopTriggerDropdown.style.display = DisplayStyle.None; + hasStopTriggerToggle.style.display = DisplayStyle.None; + } + + private static void ConfigureInfiniteLoopHelpBox(SerializedProperty rootProp, VisualElement container, Toggle hasStopTriggerToggle, SerializedProperty hasStopTriggerProp) + { + var soundAssetProp = rootProp.FindPropertyRelative("Sound"); + var infiniteLoopHelpBox = new HelpBox("The selected sound asset loops and no stop trigger is set, so the sound will loop forever once started.", HelpBoxMessageType.Warning); + infiniteLoopHelpBox.style.display = DisplayStyle.None; + container.Insert(0, infiniteLoopHelpBox); + var soundAssetField = container.Q("sound-asset"); + soundAssetField.RegisterValueChangedCallback( + e => UpdateInfiniteLoopHelpBoxVisbility(soundAssetProp.objectReferenceValue as SoundAsset, hasStopTriggerProp.boolValue, infiniteLoopHelpBox)); + hasStopTriggerToggle.RegisterValueChangedCallback( + e => UpdateInfiniteLoopHelpBoxVisbility(soundAssetProp.objectReferenceValue as SoundAsset, hasStopTriggerProp.boolValue, infiniteLoopHelpBox)); + } + + private static void UpdateInfiniteLoopHelpBoxVisbility(SoundAsset soundAsset, bool hasStopTrigger, VisualElement box) + { + if (soundAsset && soundAsset.Loop && !hasStopTrigger) + box.style.display = DisplayStyle.Flex; + else + box.style.display = DisplayStyle.None; + } + + private static SoundTrigger[] GetAvailableTriggers(SerializedProperty property) + { + var mechSoundsComponent = (MechSoundsComponent)property.serializedObject.targetObject; + if (mechSoundsComponent.TryGetComponent(out var emitter)) + return emitter.AvailableTriggers; + else + return Array.Empty(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs.meta new file mode 100644 index 000000000..67e6c6874 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 884fb5b527309ef489e8b27aa9e4809d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs new file mode 100644 index 000000000..894fd7253 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs @@ -0,0 +1,158 @@ +// Visual Pinball Engine +// Copyright (C) 2023 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace VisualPinball.Unity.Editor +{ + [CustomEditor(typeof(SoundAsset)), CanEditMultipleObjects] + public class SoundAssetInspector : UnityEditor.Editor + { + private SerializedProperty _nameProperty; + private SerializedProperty _descriptionProperty; + private SerializedProperty _volumeCorrectionProperty; + private SerializedProperty _clipsProperty; + private SerializedProperty _clipSelectionProperty; + private SerializedProperty _randomizePitchProperty; + private SerializedProperty _randomizeVolumeProperty; + private SerializedProperty _loopProperty; + + private SoundAsset _soundAsset; + + private AudioSource _editorAudioSource; + //private AudioMixer _editorAudioMixer; + + private const float ButtonHeight = 30; + private const float ButtonWidth = 50; + + private void OnEnable() + { + _nameProperty = serializedObject.FindProperty(nameof(SoundAsset.Name)); + _descriptionProperty = serializedObject.FindProperty(nameof(SoundAsset.Description)); + _volumeCorrectionProperty = serializedObject.FindProperty(nameof(SoundAsset.VolumeCorrection)); + _clipsProperty = serializedObject.FindProperty(nameof(SoundAsset.Clips)); + _clipSelectionProperty = serializedObject.FindProperty(nameof(SoundAsset.ClipSelection)); + _randomizePitchProperty = serializedObject.FindProperty(nameof(SoundAsset.RandomizePitch)); + _randomizeVolumeProperty = serializedObject.FindProperty(nameof(SoundAsset.RandomizeVolume)); + _loopProperty = serializedObject.FindProperty(nameof(SoundAsset.Loop)); + + _editorAudioSource = GetOrCreateAudioSource(); + //_editorAudioMixer = AssetDatabase.LoadAssetAtPath("Packages/org.visualpinball.engine.unity/VisualPinball.Unity/Assets/Resources/EditorMixer.mixer"); + //_editorAudioSource.outputAudioMixerGroup = _editorAudioMixer.outputAudioMixerGroup; + + _soundAsset = target as SoundAsset; + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.PropertyField(_nameProperty, true); + + using (var horizontalScope = new GUILayout.HorizontalScope()) + { + EditorGUILayout.PropertyField(_descriptionProperty, GUILayout.Height(100)); + } + + EditorGUILayout.PropertyField(_volumeCorrectionProperty, true); + EditorGUILayout.PropertyField(_clipsProperty); + EditorGUILayout.PropertyField(_clipSelectionProperty, true); + EditorGUILayout.PropertyField(_randomizePitchProperty, true); + EditorGUILayout.PropertyField(_randomizeVolumeProperty, true); + EditorGUILayout.PropertyField(_loopProperty); + + serializedObject.ApplyModifiedProperties(); + + // center button + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (PlayStopButton()) { + PlayStop(); + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + private void PlayStop() + { + if (_editorAudioSource.isPlaying) { + _soundAsset.Stop(_editorAudioSource); + } else { + _soundAsset.Play(_editorAudioSource); + } + } + + private bool PlayStopButton() + { + return _editorAudioSource.isPlaying + ? GUILayout.Button(new GUIContent("Stop", Icons.StopButton(IconSize.Small, IconColor.Orange)), + GUILayout.Height(ButtonHeight), GUILayout.Width(ButtonWidth)) + : GUILayout.Button(new GUIContent("Play", Icons.PlayButton(IconSize.Small, IconColor.Orange)), + GUILayout.Height(ButtonHeight), GUILayout.Width(ButtonWidth)); + } + + /// + /// Gets or creates the AudioSource for playing sounds in the editor. + /// The object containing the AudioSource is created in a new, additively loaded scene + /// to avoid making changes to the user's currently open scene. + /// + /// AudioSource for previewing audio assets in the editor + private static AudioSource GetOrCreateAudioSource() + { + Scene editorScene = GetOrCreatePreviewScene(); + GameObject editorAudio = GetOrCreatePreviewAudioObject(editorScene); + if (!editorAudio.TryGetComponent(out var audioSource)) { + audioSource = editorAudio.AddComponent(); + } + return audioSource; + } + + private static Scene GetOrCreatePreviewScene() + { + const string sceneName = "VpeEditorScene"; + + for (int i = 0; i < SceneManager.loadedSceneCount; i++) { + Scene scene = SceneManager.GetSceneAt(i); + if (scene.name == sceneName) + return scene; + } + + Scene previewScene = EditorSceneManager.NewPreviewScene(); + previewScene.name = sceneName; + return previewScene; + } + + private static GameObject GetOrCreatePreviewAudioObject(Scene previewScene) + { + const string audioObjName = "AudioPreview"; + + var audioObj = previewScene.GetRootGameObjects() + .FirstOrDefault(go => go.name == audioObjName); + + if (audioObj == null) { + audioObj = new GameObject(audioObjName); + SceneManager.MoveGameObjectToScene(audioObj, previewScene); + } + + return audioObj; + } + } +} + diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs.meta new file mode 100644 index 000000000..9530058fc --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bace99bbc8f020f49b66c0ce06780514 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml new file mode 100644 index 000000000..3229f55ee --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml @@ -0,0 +1,6 @@ + + + + + + diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml.meta new file mode 100644 index 000000000..01cf6511b --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundDrawer.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 5edbe5d4628bdb545a05bfa87ed2f700 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.cs new file mode 100644 index 000000000..d9dc09694 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.cs @@ -0,0 +1,25 @@ +using UnityEditor; +using UnityEngine; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + + +namespace VisualPinball.Unity.Editor +{ + [CustomEditor(typeof(MechSoundsComponent)), CanEditMultipleObjects] + public class SoundsComponentInspector : UnityEditor.Editor + { + [SerializeField] + private VisualTreeAsset inspectorXml; + + public override VisualElement CreateInspectorGUI() + { + VisualElement inspector = new VisualElement(); + var comp = target as MechSoundsComponent; + if (!comp!.TryGetComponent(out var _)) + inspector.Add(new HelpBox("Cannot find sound emitter. This component only works with a sound emitter on the same GameObject.", HelpBoxMessageType.Warning)); + inspectorXml.CloneTree(inspector); + return inspector; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.cs.meta new file mode 100644 index 000000000..80e6f578c --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: b54f8fa6e77f2104a898dca7e163c4e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - inspectorXml: {fileID: 9197481963319205126, guid: a6e8546981c540948ae9851157602432, + type: 3} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uss b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uss new file mode 100644 index 000000000..218263a81 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uss @@ -0,0 +1,5 @@ +.custom-label { + font-size: 20px; + -unity-font-style: bold; + color: rgb(68, 138, 255); +} \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uss.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uss.meta new file mode 100644 index 000000000..ef1e9b51a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30f22b4450bbae84eb18d9bc7f656e8c +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uxml b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uxml new file mode 100644 index 000000000..56f8b2094 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundsComponentInspector.uxml @@ -0,0 +1,5 @@ + +