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