Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ namespace VisualPinball.Unity.Editor
[CustomEditor(typeof(MechSoundsComponent)), CanEditMultipleObjects]
public class MechanicalSoundInspector : UnityEditor.Editor
{
private SerializedProperty _audioMixerProperty;
private SerializedProperty _soundsProperty;

private void OnEnable()
{
_audioMixerProperty = serializedObject.FindProperty(nameof(MechSoundsComponent.AudioMixer));
_soundsProperty = serializedObject.FindProperty(nameof(MechSoundsComponent.Sounds));

var comp = target as MechSoundsComponent;
var audioSource = comp!.GetComponent<AudioSource>();
if (audioSource != null) {
audioSource.playOnAwake = false;
}
}

public override void OnInspectorGUI()
Expand All @@ -45,16 +43,19 @@ public override void OnInspectorGUI()
return;
}

var audioSource = comp.GetComponent<AudioSource>();
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);

// unity doesnt use default values when adding items in a list so force it (Volume=1)
if (GUILayout.Button("Add New MechSound"))
{
comp.Sounds.Add(new MechSound());
EditorUtility.SetDirty(comp);
}

EditorGUILayout.PropertyField(_audioMixerProperty);

serializedObject.ApplyModifiedProperties();
}
}
Expand Down
1 change: 1 addition & 0 deletions VisualPinball.Unity/VisualPinball.Unity/Sound/MechSound.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class MechSound
public string TriggerId;

[Range(0.0001f, 1)]
// this initialization doesnt work in inspector https://www.reddit.com/r/Unity3D/comments/j5i6cj/inspector_struct_default_values/
public float Volume = 1;

public MechSoundAction Action = MechSoundAction.Play;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,49 @@
namespace VisualPinball.Unity
{
[AddComponentMenu("Visual Pinball/Sounds/Mechanical Sounds")]
[RequireComponent(typeof(AudioSource))]
public class MechSoundsComponent : MonoBehaviour
{
[SerializeField]
public List<MechSound> Sounds = new();


[SerializeField]
[Tooltip("If left blank, looks for an Audio Mixer in closest parent up the hierarchy.")]
public AudioMixerGroup AudioMixer;

[NonSerialized]
private ISoundEmitter _soundEmitter;
[NonSerialized]
private AudioSource _audioSource;
[NonSerialized]
private Dictionary<string, MechSound> _sounds = new();
private SerializableDictionary<SoundAsset, AudioSource> _audioSources = new SerializableDictionary<SoundAsset, AudioSource>();

private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private Coroutine _co;

private void Awake()
{
_soundEmitter = GetComponent<ISoundEmitter>();
_audioSource = GetComponent<AudioSource>();

_sounds = Sounds.ToDictionary(s => s.TriggerId, s => s);
}

private void Start()
{
if (_soundEmitter != null && _audioSource) {
if (AudioMixer == null)
{
// find an Audio Mixer by searching up the hierarchy
AudioSource audioSource = GetComponentInParent<AudioSource>();
if (audioSource != null)
{
AudioMixer = audioSource.outputAudioMixerGroup;
}
else
{
Logger.Warn($"Sounds will not play without an Audio Mixer.");
}
}

if (_soundEmitter != null) {
_soundEmitter.OnSound += EmitSound;

} else {
// ? is AudioSource really a dependency here??
Logger.Warn($"Cannot initialize mech sound for {name} due to missing ISoundEmitter or AudioSource.");
}
}
Expand All @@ -70,10 +83,36 @@ private void OnDestroy()

private void EmitSound(object sender, SoundEventArgs e)
{
int clipCount = 0;

foreach(MechSound sound in Sounds)
{
// filter for the TriggerId
if (sound.TriggerId != e.TriggerId) continue;

if (_sounds.ContainsKey(e.TriggerId)) {
// get or create the AudioSource
AudioSource audioSource;
if (_audioSources.ContainsKey(sound.Sound))
{
audioSource = _audioSources[sound.Sound];
}
else
{
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.outputAudioMixerGroup = AudioMixer;
_audioSources.Add(sound.Sound, audioSource);
}

float fade = _sounds[e.TriggerId].Fade;
if (sound.Action == MechSoundAction.Stop)
{
sound.Sound.Stop(audioSource);
Debug.Log($"Stopping sound {e.TriggerId} for {name}");
// we're done
continue;
}

// else sound.Action == MechSoundAction.Play
float fade = sound.Fade;
bool fadeVolume = false;

//convert fade duration from milliseconds to seconds for use with StartFade method
Expand All @@ -91,7 +130,7 @@ private void EmitSound(object sender, SoundEventArgs e)
float volume = e.Volume;

AudioMixer audioMixer = GetComponent<AudioSource>().outputAudioMixerGroup.audioMixer;
_sounds[e.TriggerId].Sound.Play(_audioSource, volume);
sound.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
Expand All @@ -115,9 +154,6 @@ volume of 1 in slider is equivalent to 0 dB


Debug.Log($"Playing sound {e.TriggerId} for {name}");

} else {
Debug.LogError($"Unknown trigger {e.TriggerId} for {name}");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
using Logger = NLog.Logger;
using NLog;
using UnityEngine;
using VisualPinball.Engine.VPT;
using System.Collections.Generic;

namespace VisualPinball.Unity
{
public class DropTargetBankApi : IApi, IApiCoilDevice, IApiSwitchDevice
public class DropTargetBankApi : ItemApi<DropTargetBankComponent, ItemData>, IApi, IApiCoilDevice, IApiSwitchDevice
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

Expand Down Expand Up @@ -57,7 +58,7 @@ private IApiCoil Coil(string deviceItem)
};
}

internal DropTargetBankApi(GameObject go, Player player, PhysicsEngine physicsEngine)
internal DropTargetBankApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine)
{
_dropTargetBankComponent = go.GetComponentInChildren<DropTargetBankComponent>();
_player = player;
Expand Down Expand Up @@ -102,6 +103,9 @@ private void OnResetCoilEnabled()
foreach (var dropTargetApi in _dropTargetApis) {
dropTargetApi.IsDropped = false;
}

// ? is this where this goes?
MainComponent.EmitSound(DropTargetBankComponent.SoundTargetBankReset);
}

void IApi.OnDestroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,25 @@
using System.Collections.Generic;
using UnityEngine;
using VisualPinball.Engine.Game.Engines;
using VisualPinball.Engine.VPT;
using System.ComponentModel;
using System;
using VisualPinball.Engine.VPT.HitTarget;
using VisualPinball.Engine.IO;
using VisualPinball.Engine.VPT.Table;

namespace VisualPinball.Unity
{
[AddComponentMenu("Visual Pinball/Mechs/Drop Target Bank")]
[HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/drop-target-banks.html")]
public class DropTargetBankComponent : MonoBehaviour, ICoilDeviceComponent, ISwitchDeviceComponent
public class DropTargetBankComponent : MainRenderableComponent<ItemData>, ICoilDeviceComponent, ISwitchDeviceComponent, ISoundEmitter
{
public const string ResetCoilItem = "reset_coil";

public const string SequenceCompletedSwitchItem = "sequence_completed_switch";

public const string SoundTargetBankReset = "sound_target_bank_reset";

[ToolboxItem("The number of the drop targets. See documentation of a description of each type.")]
public int BankSize = 1;

Expand Down Expand Up @@ -71,5 +77,55 @@ private void Awake()
}

#endregion

#region ISoundEmitter

public SoundTrigger[] AvailableTriggers => new[] {
new SoundTrigger { Id = SoundTargetBankReset, Name = "Sound Target Bank Reset" }
};

protected override Type MeshComponentType => throw new NotImplementedException();

protected override Type ColliderComponentType => throw new NotImplementedException();

public override bool HasProceduralMesh => throw new NotImplementedException();

public override ItemType ItemType => throw new NotImplementedException();

public override string ItemName => throw new NotImplementedException();

public event EventHandler<SoundEventArgs> OnSound;

internal void EmitSound(string triggerId, float volume = 1)
{
OnSound?.Invoke(this, new SoundEventArgs(triggerId, volume));
}

public override void CopyFromObject(GameObject go)
{
throw new NotImplementedException();
}

public override IEnumerable<MonoBehaviour> SetData(ItemData data)
{
throw new NotImplementedException();
}

public override IEnumerable<MonoBehaviour> SetReferencedData(ItemData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary<string, IMainComponent> components)
{
throw new NotImplementedException();
}

public override ItemData CopyDataTo(ItemData data, string[] materialNames, string[] textureNames, bool forExport)
{
throw new NotImplementedException();
}

public override ItemData InstantiateData()
{
throw new NotImplementedException();
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,11 @@ internal void EmitSound(string triggerId, float volume = 1)
OnSound?.Invoke(this, new SoundEventArgs(triggerId, volume));
}

/// <summary>
/// Returns the current position of the flipper between 0 and 1, where 0 is the
/// start position, and 1 the end position.
/// </summary>
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<GamelogicEngineSwitch> AvailableSwitches => new[] {
Expand Down Expand Up @@ -229,6 +219,18 @@ public float2 RotatedPosition {
}
}

/// <summary>
/// Returns the current position of the flipper between 0 and 1, where 0 is the
/// start position, and 1 the end position.
/// </summary>
public float RotatePosition {
get {
var start = (_startAngle + 360) % 360;
var end = (EndAngle + 360) % 360;
return 1 - (transform.localEulerAngles.y - start) / (end - start);
}
}

#endregion

#region Conversion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ void IApi.OnDestroy()
void IApiHittable.OnHit(int ballId, bool _)
{
Hit?.Invoke(this, new HitEventArgs(ballId));

MainComponent.EmitSound(TargetComponent.SoundTargetHit);
}
void IApiDroppable.OnDropStatusChanged(bool isDropped, int ballId)
{
if (!isDropped)
{
MainComponent.EmitSound(DropTargetComponent.SoundTargetReset);
}
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ protected override float ZOffset {
}
}

#region Overrides and Constants

public const string SoundTargetReset = "sound_target_reset";

#endregion

#region Conversion

public override IEnumerable<MonoBehaviour> SetData(HitTargetData data)
Expand Down Expand Up @@ -175,5 +181,14 @@ internal DropTargetState CreateState()
}

#endregion

#region ISoundEmitter

public override SoundTrigger[] AvailableTriggers => new[] {
new SoundTrigger { Id = SoundTargetHit, Name = "Target Drop" },
new SoundTrigger { Id = SoundTargetReset, Name = "Target Reset" },
};

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ void IApiHittable.OnHit(int ballId, bool _)
Hit?.Invoke(this, new HitEventArgs(ballId));
Switch?.Invoke(this, new SwitchEventArgs(true, ballId));
OnSwitch(true);

MainComponent.EmitSound(TargetComponent.SoundTargetHit);
}

#endregion
Expand Down
Loading