Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More mechanical sound support #466

Open
wants to merge 11 commits into
base: feature/sounds
Choose a base branch
from
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