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..7194aedd6 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundDrawer.cs @@ -0,0 +1,84 @@ +// 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 UnityEngine; + +namespace VisualPinball.Unity.Editor +{ + [CustomPropertyDrawer(typeof(MechSound))] + public class MechSoundDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 5 + 4f; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + // retrieve reference to GO and component + var mechSoundsComponent = (MechSoundsComponent)property.serializedObject.targetObject; + var soundEmitter = mechSoundsComponent.GetComponent(); + + EditorGUI.BeginProperty(position, label, property); + + // init height + position.height = EditorGUIUtility.singleLineHeight; + + // trigger drop-down + var triggerIdProperty = property.FindPropertyRelative(nameof(MechSound.TriggerId)); + var triggers = soundEmitter.AvailableTriggers; + if (triggers.Length > 0) { + var triggerIndex = triggers.ToList().FindIndex(t => t.Id == triggerIdProperty.stringValue); + if (triggerIndex == -1) { // pre-select first trigger in list, if none set. + triggerIndex = 0; + } + triggerIndex = EditorGUI.Popup(position, "Trigger on", triggerIndex, triggers.Select(t => t.Name).ToArray()); + triggerIdProperty.stringValue = triggers[triggerIndex].Id; + } else { + EditorGUI.LabelField(position, "No Triggers found."); + } + position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + // sound object picker + var soundProperty = property.FindPropertyRelative(nameof(MechSound.Sound)); + EditorGUI.BeginChangeCheck(); + var soundValue = EditorGUI.ObjectField(position, "Sound", soundProperty.objectReferenceValue, typeof(SoundAsset), true); + if (EditorGUI.EndChangeCheck()) { + soundProperty.objectReferenceValue = soundValue; + } + position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + // volume + var volumeProperty = property.FindPropertyRelative(nameof(MechSound.Volume)); + EditorGUI.PropertyField(position, volumeProperty); + position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + // action + var actionProperty = property.FindPropertyRelative(nameof(MechSound.Action)); + EditorGUI.PropertyField(position, actionProperty); + position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + // fade + var fadeProperty = property.FindPropertyRelative(nameof(MechSound.Fade)); + EditorGUI.PropertyField(position, fadeProperty); + position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + EditorGUI.EndProperty(); + } + } +} 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/MechSoundsInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundsInspector.cs new file mode 100644 index 000000000..66e38ca81 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundsInspector.cs @@ -0,0 +1,61 @@ +// 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 UnityEditor; +using UnityEngine; + +namespace VisualPinball.Unity.Editor +{ + [CustomEditor(typeof(MechSoundsComponent)), CanEditMultipleObjects] + public class MechanicalSoundInspector : UnityEditor.Editor + { + private SerializedProperty _soundsProperty; + + private void OnEnable() + { + _soundsProperty = serializedObject.FindProperty(nameof(MechSoundsComponent.Sounds)); + + var comp = target as MechSoundsComponent; + var audioSource = comp!.GetComponent(); + if (audioSource != null) { + audioSource.playOnAwake = false; + } + } + + public override void OnInspectorGUI() + { + var comp = target as MechSoundsComponent; + + var soundEmitter = comp!.GetComponent(); + if (soundEmitter == null) { + EditorGUILayout.HelpBox("Cannot find sound emitter. This component only works with a sound emitter on the same GameObject.", MessageType.Error); + return; + } + + var audioSource = comp.GetComponent(); + if (audioSource == null) { + EditorGUILayout.HelpBox("Cannot find audio source. This component only works with an audio source on the same GameObject.", MessageType.Error); + return; + } + + serializedObject.Update(); + + EditorGUILayout.PropertyField(_soundsProperty); + + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundsInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundsInspector.cs.meta new file mode 100644 index 000000000..3c428cf24 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/MechSoundsInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8dca340821f92c74e8cf97cd3fff29df +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..9713d2922 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Sound/SoundAssetInspector.cs @@ -0,0 +1,156 @@ +// 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 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 editor GameObject for playing sounds in the editor. + /// + /// The hierarchy looks like that: + /// + /// [scene root] + /// | + /// -- EditorScene + /// | + /// -- EditorAudio (with AudioSource component) + /// + /// AudioSource of the editor GameObject for test playing audio. + private static AudioSource GetOrCreateAudioSource() + { + // todo check whether we'll instantiate those live in the future or rely on a provided prefab + var editorSceneGo = SceneManager.GetActiveScene().GetRootGameObjects() + .FirstOrDefault(go => go.name == "EditorScene"); + + if (editorSceneGo == null) { + editorSceneGo = new GameObject("EditorScene"); + } + + GameObject editorAudioGo = null; + for (var i = 0; i < editorSceneGo.transform.childCount; i++) { + var go = editorSceneGo.transform.GetChild(i).gameObject; + if (go.name != "EditorAudio") { + continue; + } + editorAudioGo = go; + break; + } + + if (editorAudioGo == null) { + editorAudioGo = new GameObject("EditorAudio"); + editorAudioGo.transform.SetParent(editorSceneGo.transform); + } + + var audioSource = editorAudioGo.GetComponent(); + if (!audioSource) { + audioSource = editorAudioGo.AddComponent(); + } + + return audioSource; + } + } +} + 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/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs index d47362d73..af0087258 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs @@ -79,9 +79,11 @@ public IconVariant(string name, IconSize size, IconColor color) private const string KickerName = "kicker"; private const string LightGroupName = "light_group"; private const string LightName = "light"; + private const string LoopButtonName = "player"; private const string MechName = "mech"; private const string MechPinMameName = "mech_pinmame"; private const string PlayfieldName = "playfield"; + private const string PlayButtonName = "player"; private const string PlugName = "plug"; private const string PlungerName = "plunger"; private const string PrimitiveName = "primitive"; @@ -92,6 +94,7 @@ public IconVariant(string name, IconSize size, IconColor color) private const string ScoreReelSingleName = "score_reel_single"; private const string SlingshotName = "slingshot"; private const string SpinnerName = "spinner"; + private const string StopButtonName = "kicker"; private const string SurfaceName = "surface"; private const string SwitchNcName = "switch_nc"; private const string SwitchNoName = "switch_no"; @@ -115,8 +118,8 @@ public IconVariant(string name, IconSize size, IconColor color) private static readonly string[] Names = { AssetLibraryName, BallRollerName, BoltName, BumperName, CalendarName, CannonName, CoilName, DropTargetBankName, DropTargetName, FlasherName, - FlipperName, GateName, GateLifterName, HitTargetName, KeyName, KickerName, LightGroupName, LightName, MechName, MechPinMameName, PlayfieldName, PlugName, - PlungerName, PrimitiveName, RampName, RotatorName, RubberName, ScoreReelName, ScoreReelSingleName, SlingshotName, SpinnerName, SurfaceName, + FlipperName, GateName, GateLifterName, HitTargetName, KeyName, KickerName, LightGroupName, LightName, LoopButtonName, MechName, MechPinMameName, PlayfieldName, PlayButtonName, PlugName, + PlungerName, PrimitiveName, RampName, RotatorName, RubberName, ScoreReelName, ScoreReelSingleName, SlingshotName, SpinnerName, StopButtonName, SurfaceName, SwitchNcName, SwitchNoName, TableName, TeleporterName, TriggerName, TroughName, CoilEventName, SwitchEventName, LampEventName, LampSeqName, MetalWireGuideName, PlayerVariableName, PlayerVariableEventName, TableVariableName, TableVariableEventName, UpdateDisplayName, DisplayEventName @@ -179,11 +182,13 @@ private static IIconLookup[] GetLookups() { public static Texture2D Key(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(KeyName, size, color); public static Texture2D Kicker(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(KickerName, size, color); public static Texture2D Light(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(LightName, size, color); + public static Texture2D LoopButton(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(LoopButtonName, size, color); public static Texture2D LightGroup(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(LightGroupName, size, color); public static Texture2D Mech(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(MechName, size, color); public static Texture2D MechPinMame(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(MechPinMameName, size, color); public static Texture2D MetalWireGuide(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(MetalWireGuideName, size, color); public static Texture2D Playfield(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlayfieldName, size, color); + public static Texture2D PlayButton(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlayButtonName, size, color); public static Texture2D Plug(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlugName, size, color); public static Texture2D Plunger(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlungerName, size, color); public static Texture2D Primitive(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PrimitiveName, size, color); @@ -194,6 +199,7 @@ private static IIconLookup[] GetLookups() { public static Texture2D ScoreReelSingle(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(ScoreReelSingleName, size, color); public static Texture2D Slingshot(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(SlingshotName, size, color); public static Texture2D Spinner(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(SpinnerName, size, color); + public static Texture2D StopButton(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(StopButtonName, size, color); public static Texture2D Surface(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(SurfaceName, size, color); public static Texture2D Switch(bool isClosed, IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(isClosed ? SwitchNcName : SwitchNoName, size, color); public static Texture2D Table(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(TableName, size, color); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs index 47d088618..cf1d8648d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs @@ -182,6 +182,7 @@ private void Start() if (EngineProvider.Exists) { EngineProvider.Get().Init(_tableComponent); } + } private void Update() diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound.meta new file mode 100644 index 000000000..6554ff4e7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 24e3e95b7dc44c3ebd511ab4c4194a65 +timeCreated: 1677678507 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/FadeMixerGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Sound/FadeMixerGroup.cs new file mode 100644 index 000000000..5004059be --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/FadeMixerGroup.cs @@ -0,0 +1,40 @@ +// 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.Collections; +using System.Collections.Generic; +using UnityEngine.Audio; +using UnityEngine; +public static class FadeMixerGroup +{ + //note: fade duration below 1 second causes a breakdown of this method + public static IEnumerator StartFade(AudioMixer audioMixer, string exposedParam, float duration, float targetVolume) + { + float currentTime = 0; + float currentVol; + audioMixer.GetFloat(exposedParam, out currentVol); + currentVol = Mathf.Pow(10, currentVol / 20); + float targetValue = Mathf.Clamp(targetVolume, 0.0001f, 1); + while (currentTime < duration) + { + currentTime += Time.deltaTime; + float newVol = Mathf.Lerp(currentVol, targetValue, currentTime / duration); + audioMixer.SetFloat(exposedParam, Mathf.Log10(newVol) * 20); + yield return null; + } + yield break; + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/FadeMixerGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound/FadeMixerGroup.cs.meta new file mode 100644 index 000000000..aba75f0dd --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/FadeMixerGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39c1161b40f535947ad995908584d38a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/ISoundEmitter.cs b/VisualPinball.Unity/VisualPinball.Unity/Sound/ISoundEmitter.cs new file mode 100644 index 000000000..306387eb7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/ISoundEmitter.cs @@ -0,0 +1,48 @@ +// 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; + +namespace VisualPinball.Unity +{ + /// + /// An interface for item components that emit mechanical sounds. + /// + public interface ISoundEmitter + { + /// + /// A list of triggers that can be linked to emitting a sound. + /// + SoundTrigger[] AvailableTriggers { get; } + + /// + /// The sound event, to which the subscribes to. + /// + event EventHandler OnSound; + } + + public readonly struct SoundEventArgs + { + public readonly string TriggerId; + public readonly float Volume; + + public SoundEventArgs(string triggerId, float volume) + { + TriggerId = triggerId; + Volume = volume; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/ISoundEmitter.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound/ISoundEmitter.cs.meta new file mode 100644 index 000000000..ca209a4c5 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/ISoundEmitter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7129feb5a774383458adb84d3b08fabe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs new file mode 100644 index 000000000..3f328045b --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs @@ -0,0 +1,44 @@ +// 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 . + +// ReSharper disable InconsistentNaming + +using System; +using UnityEngine; + +namespace VisualPinball.Unity +{ + [Serializable] + public class MechSound + { + [SerializeReference] + public SoundAsset Sound; + + public string TriggerId; + + [Range(0.0001f, 1)] + public float Volume = 1; + + public MechSoundAction Action = MechSoundAction.Play; + + [Tooltip("Increments of 1000")] + [Min(0)] + [Unit("ms")] + public float Fade; + } + + public enum MechSoundAction { Play, Stop }; +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs.meta new file mode 100644 index 000000000..4917bf27a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e750cca599b34fd1aa5c29d5b38788f4 +timeCreated: 1677682479 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSoundsComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSoundsComponent.cs new file mode 100644 index 000000000..96b1948fa --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSoundsComponent.cs @@ -0,0 +1,125 @@ +// 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 . + +// ReSharper disable InconsistentNaming + +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using UnityEngine; +using UnityEngine.Audio; +using Logger = NLog.Logger; + +namespace VisualPinball.Unity +{ + [AddComponentMenu("Visual Pinball/Sounds/Mechanical Sounds")] + [RequireComponent(typeof(AudioSource))] + public class MechSoundsComponent : MonoBehaviour + { + [SerializeField] + public List Sounds = new(); + + [NonSerialized] + private ISoundEmitter _soundEmitter; + [NonSerialized] + private AudioSource _audioSource; + [NonSerialized] + private Dictionary _sounds = new(); + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private Coroutine _co; + + private void Awake() + { + _soundEmitter = GetComponent(); + _audioSource = GetComponent(); + + _sounds = Sounds.ToDictionary(s => s.TriggerId, s => s); + } + + private void Start() + { + if (_soundEmitter != null && _audioSource) { + _soundEmitter.OnSound += EmitSound; + + } else { + Logger.Warn($"Cannot initialize mech sound for {name} due to missing ISoundEmitter or AudioSource."); + } + } + + private void OnDestroy() + { + if (_soundEmitter != null) { + _soundEmitter.OnSound -= EmitSound; + } + } + + private void EmitSound(object sender, SoundEventArgs e) + { + + if (_sounds.ContainsKey(e.TriggerId)) { + + float fade = _sounds[e.TriggerId].Fade; + bool fadeVolume = false; + + //convert fade duration from milliseconds to seconds for use with StartFade method + if (fade > 0) + { + //dont have a fade minimum of less than 1 second, if there is a fade. Less than 1 second fade, + //and the underlying method 'FadeMixerGroup.StartFade' will break down and not work correctly + if (fade < 1000) + { fade = 1000; } + + fade = fade / 1000; + fadeVolume = true; + } + + float volume = e.Volume; + + AudioMixer audioMixer = GetComponent().outputAudioMixerGroup.audioMixer; + _sounds[e.TriggerId].Sound.Play(_audioSource, volume); + + /* set audio mixer volume to decibel equivalent of volume slider value + mixer volume is set at 0 dB when added to audiosource + volume of 1 in slider is equivalent to 0 dB + */ + string exposedParameter = "vol1"; + float sliderDBVolume = Mathf.Log10(volume) * 20; + float mixerVolume; + + audioMixer.GetFloat(exposedParameter, out mixerVolume); + + //current coroutine is still fading the audio clip and needs to be stopped and volume reset + if (mixerVolume < sliderDBVolume) + { + StopCoroutine(_co); + audioMixer.SetFloat(exposedParameter, sliderDBVolume); + } + + if (fadeVolume) + { _co = StartCoroutine(FadeMixerGroup.StartFade(audioMixer, exposedParameter, 1, 0)); } + + + Debug.Log($"Playing sound {e.TriggerId} for {name}"); + + } else { + Debug.LogError($"Unknown trigger {e.TriggerId} for {name}"); + } + } + } +} + diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSoundsComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSoundsComponent.cs.meta new file mode 100644 index 000000000..c79190540 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/MechSoundsComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c28cae51000318145b40983f440013ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundAsset.cs b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundAsset.cs new file mode 100644 index 000000000..888f2db37 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundAsset.cs @@ -0,0 +1,104 @@ +// 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 . + +// ReSharper disable InconsistentNaming + +using System; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace VisualPinball.Unity +{ + [CreateAssetMenu(fileName = "Sound", menuName = "Visual Pinball/Sound", order = 102)] + public class SoundAsset : ScriptableObject + { + #region Properties + + public string Name; + public string Description; + + [Range(0, 1)] + public float VolumeCorrection = 1; //audio clips in unity have a volume range of 0 to 1 + + public AudioClip[] Clips; + + public enum Selection + { + RoundRobin, + Random + } + + public Selection ClipSelection; + + [Range(0, 0.3f)] + public float RandomizePitch; + + // todo needs to go through the mixer + // [Range(0, 0.3f)] + // public float RandomizeSpeed; + + [Range(0, 0.5f)] + public float RandomizeVolume; + + public bool Loop; + + #endregion + + #region Runtime + + [NonSerialized] + private int _clipIndex = 0; + + #endregion + + public void Play(AudioSource audioSource, float volume = 1) + { + if (Clips.Length == 0) { + return; + } + audioSource.volume = Volume * volume; + audioSource.pitch = Pitch; + audioSource.loop = Loop; + audioSource.clip = GetClip(); + audioSource.Play(); + } + + + public void Stop(AudioSource audioSource) + { + audioSource.Stop(); + } + + private float Pitch => 1f + Random.Range(-RandomizePitch / 2, RandomizePitch / 2); + private float Volume => VolumeCorrection - Random.Range(0, RandomizeVolume); + + private AudioClip GetClip() + { + switch (ClipSelection) { + case Selection.RoundRobin: + var clip = Clips[_clipIndex]; + _clipIndex = (_clipIndex + 1) % Clips.Length; + return clip; + + case Selection.Random: + return Clips[Random.Range(0, Clips.Length)]; + + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundAsset.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundAsset.cs.meta new file mode 100644 index 000000000..8fdd6acfd --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a6287696a5efed489f573445fda4418 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundTrigger.cs b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundTrigger.cs new file mode 100644 index 000000000..ff5c3c506 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundTrigger.cs @@ -0,0 +1,39 @@ +// 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 . + +namespace VisualPinball.Unity +{ + /// + /// A sound trigger describes how a mechanical sound is triggered. + /// + /// During edit time, sound triggers are declared by game items so they + /// can be linked to a . During runtime, they + /// are used to identify which sound to play. + /// + public struct SoundTrigger + { + /// + /// The ID of the trigger. When you change the ID of a trigger, + /// all already associated triggers will be cleared. + /// + public string Id; + + /// + /// Name of the trigger, used for display purposes only. + /// + public string Name; + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundTrigger.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundTrigger.cs.meta new file mode 100644 index 000000000..523a6340d --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Sound/SoundTrigger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 82f302a40f4f455d819e782d1f1a6127 +timeCreated: 1677678720 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 496e4441c..ac1244aea 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -100,6 +100,8 @@ void IApiHittable.OnHit(Entity ballEntity, bool isUnHit) Hit?.Invoke(this, new HitEventArgs(ballEntity)); Switch?.Invoke(this, new SwitchEventArgs(!isUnHit, ballEntity)); OnSwitch(true); + + MainComponent.EmitSound(BumperComponent.SoundBumperHit); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 326c34b6f..1877f3559 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -35,7 +35,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Bumper")] public class BumperComponent : MainRenderableComponent, - ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity, ISoundEmitter { #region Data @@ -81,6 +81,22 @@ public class BumperComponent : MainRenderableComponent, private const float DataMeshScale = 100f; public const string SocketSwitchItem = "socket_switch"; + public const string SoundBumperHit = "sound_bumper_hit"; + + #endregion + + #region ISoundEmitter + + public SoundTrigger[] AvailableTriggers => new[] { + new SoundTrigger { Id = SoundBumperHit, Name = "Bumper Hit" } + }; + + public event EventHandler OnSound; + + internal void EmitSound(string triggerId, float volume = 1) + { + OnSound?.Invoke(this, new SoundEventArgs(triggerId, volume)); + } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index fe8fc21e5..e31ca41c6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . // ReSharper disable EventNeverSubscribedTo.Global -#pragma warning disable 67 using System; using System.Collections.Generic; @@ -38,7 +37,6 @@ public class FlipperApi : CollidableApi public event EventHandler Init; - /// /// Event emitted when the flipper was touched by the ball, but did /// not collide. @@ -96,6 +94,7 @@ void IApi.OnDestroy() public void RotateToEnd() { EngineProvider.Get().FlipperRotateToEnd(Entity); + MainComponent.EmitSound(FlipperComponent.SoundCoilOn, MainComponent.RotatePosition); } /// @@ -105,6 +104,7 @@ public void RotateToEnd() public void RotateToStart() { EngineProvider.Get().FlipperRotateToStart(Entity); + MainComponent.EmitSound(FlipperComponent.SoundCoilOff); } internal float StartAngle diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index e03fc4839..9443342ac 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -41,7 +41,7 @@ namespace VisualPinball.Unity [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] public class FlipperComponent : MainRenderableComponent, IFlipperData, ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent, - IRotatableComponent, IConvertGameObjectToEntity + IRotatableComponent, IConvertGameObjectToEntity, ISoundEmitter { #region Data @@ -131,9 +131,43 @@ public class FlipperComponent : MainRenderableComponent, public const string MainCoilItem = "main_coil"; public const string HoldCoilItem = "hold_coil"; public const string EosSwitchItem = "eos_switch"; + + public const string SoundCoilOn = "sound_coil_on"; + public const string SoundCoilOff = "sound_coil_off"; + public const string SoundCoilCollision = "sound_ball_collision"; #endregion + #region ISoundEmitter + + public SoundTrigger[] AvailableTriggers => new[] { + new SoundTrigger { Id = SoundCoilOn, Name = "Coil On" }, + new SoundTrigger { Id = SoundCoilOff, Name = "Coil Off"}, + new SoundTrigger { Id = SoundCoilCollision, Name = "Ball Collision" }, + }; + + public event EventHandler OnSound; + + internal void EmitSound(string triggerId, float volume = 1) + { + OnSound?.Invoke(this, new SoundEventArgs(triggerId, volume)); + } + + /// + /// Returns the current position of the flipper between 0 and 1, where 0 is the + /// start position, and 1 the end position. + /// + public float RotatePosition { + get { + var start = (_startAngle + 360) % 360; + var end = (EndAngle + 360) % 360; + return 1 - (transform.localEulerAngles.y - start) / (end - start); + } + } + + #endregion + + #region Wiring public IEnumerable AvailableSwitches => new[] { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index 2d387eacd..e9c7a716c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -170,6 +170,8 @@ private void OnBallDestroyed() kickerCollisionData.BallEntity = Entity.Null; entityManager.SetComponentData(Entity, kickerCollisionData); } + + MainComponent.EmitSound(KickerComponent.SoundKickerDrain); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index de586fab7..4698a5524 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -39,7 +39,7 @@ namespace VisualPinball.Unity [AddComponentMenu("Visual Pinball/Game Item/Kicker")] public class KickerComponent : MainRenderableComponent, ICoilDeviceComponent, ITriggerComponent, IBallCreationPosition, IOnSurfaceComponent, - IRotatableComponent, IConvertGameObjectToEntity, ISerializationCallbackReceiver + IRotatableComponent, IConvertGameObjectToEntity, ISerializationCallbackReceiver, ISoundEmitter { #region Data @@ -83,6 +83,25 @@ public class KickerComponent : MainRenderableComponent, public const string SwitchItem = "kicker_switch"; + public const string SoundKickerDrain = "sound_kicker_drain"; + public const string SoundKickerBallRelease = "sound_kicker_ball_release"; + + #endregion + + #region ISoundEmitter + + public SoundTrigger[] AvailableTriggers => new[] { + new SoundTrigger { Id = SoundKickerDrain, Name = "Ball Drain" }, + new SoundTrigger { Id = SoundKickerBallRelease, Name = "Ball Release" }, + }; + + public event EventHandler OnSound; + + internal void EmitSound(string triggerId, float volume = 1) + { + OnSound?.Invoke(this, new SoundEventArgs(triggerId, volume)); + } + #endregion #region Wiring diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs index 04f363c28..a25a90531 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs @@ -105,6 +105,8 @@ public void PullBack() EntityManager.SetComponentData(Entity, movementData); EntityManager.SetComponentData(Entity, velocityData); + + MainComponent.EmitSound(PlungerComponent.SoundPlungerPull); } public void Fire() @@ -138,6 +140,8 @@ public void Fire() EntityManager.SetComponentData(Entity, movementData); EntityManager.SetComponentData(Entity, velocityData); + + MainComponent.EmitSound(PlungerComponent.SoundPlungerRelease); } IApiCoil IApiCoilDevice.Coil(string deviceItem) => Coil(deviceItem); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index fb81a5baf..afcf20935 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -33,7 +33,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Plunger")] public class PlungerComponent : MainRenderableComponent, - ICoilDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + ICoilDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity, ISoundEmitter { #region Data @@ -71,6 +71,25 @@ public class PlungerComponent : MainRenderableComponent, public const string PullCoilId = "c_pull"; public const string FireCoilId = "c_autofire"; + public const string SoundPlungerPull = "sound_plunger_pull"; + public const string SoundPlungerRelease = "sound_plunger_release"; + + #endregion + + #region ISoundEmitter + + public SoundTrigger[] AvailableTriggers => new[] { + new SoundTrigger { Id = SoundPlungerPull, Name = "Plunger Pull" }, + new SoundTrigger { Id = SoundPlungerRelease, Name = "Plunger Release"} + }; + + public event EventHandler OnSound; + + internal void EmitSound(string triggerId, float volume = 1) + { + OnSound?.Invoke(this, new SoundEventArgs(triggerId, volume)); + } + #endregion #region Wiring