using System.Collections.Generic; using UnityEngine; using UnityEngine.Audio; namespace Prototype { [CreateAssetMenu(menuName = "Prototype/Sound Effect")] public class SoundEffect : ScriptableObject { [SerializeField] Vector2 volumeMultiplier = new(1, 1); [SerializeField] Vector2 pitch = new(.95f, 1.05f); [Header("Additional Settings")] public AudioMixerGroup audioMixerGroup; public float volumeMultiplierFactor = 1; [SerializeField] bool loop; [SerializeField] bool _3d; [SerializeField] bool spatialize; [Tooltip("Occlude/muffle the sound behind walls via Steam Audio. Requires spatialize and exported scene geometry.")] [SerializeField] bool occlusion; [Header("Additional Settings")] [SerializeField, Range(0, 1)] float defaultVolume = .8f; [SerializeField, Range(-1, 1)] float additionalGain = 0; [SerializeField] float maxPlayInterval = .1f; [Header("Clips")] [SerializeField] List clips; public int ClipCount => clips.Count; float lastPlayTime; public SoundEffectOptions ToOptions() => new() { audioMixerGroup = audioMixerGroup, volumeMultiplier = Random.Range(volumeMultiplier.x, volumeMultiplier.y) * volumeMultiplierFactor, pitch = Random.Range(pitch.x, pitch.y), loop = loop, _3d = _3d, spatialize = spatialize, occlusion = occlusion }; #if UNITY_EDITOR public void ApplyVolumeMultiplierFactor() { volumeMultiplier *= volumeMultiplierFactor; volumeMultiplierFactor = 1; } void OnEnable() { Initialize(); UnityEditor.EditorApplication.playModeStateChanged -= PlayModeStateChanged; UnityEditor.EditorApplication.playModeStateChanged += PlayModeStateChanged; } void PlayModeStateChanged(UnityEditor.PlayModeStateChange change) { if (change == UnityEditor.PlayModeStateChange.EnteredEditMode) { Initialize(); return; } if (change == UnityEditor.PlayModeStateChange.ExitingEditMode) { Initialize(); return; } } #endif void Initialize() { lastPlayTime = -1; } public AudioClip GetClip(int index) { return clips[index]; } public SoundEffectInstance Play() { return PlayInternal(Vector3.zero, 0); } public SoundEffectInstance Play(Vector3 position) { return PlayInternal(position, 0); } public SoundEffectInstance Play(Transform transform) { return PlayInternal(transform.position, 0); } public SoundEffectInstance Play(Vector3 position, float delay) { return PlayInternal(position, delay); } public SoundEffectInstance Play(Transform transform, float delay) { return PlayInternal(transform.position, delay); } public SoundEffectInstance PlayFirstClip() { return PlayInternal(Vector3.zero, 0, true); } public SoundEffectInstance PlayFirstClip(bool destroyOnLoad) { return PlayInternal(Vector3.zero, 0, true, destroyOnLoad); } public SoundEffectInstance PlayFirstClipAt(Vector3 position) { return PlayInternal(position, 0, true); } public SoundEffectInstance PlayFirstClipWithDelayAt(Vector3 position, float delay) { return PlayInternal(position, delay, true); } SoundEffectInstance PlayInternal(Vector3 position, float delay, bool firstClip = false, bool destroyOnLoad = true) { var options = ToOptions(); options.delay = delay; options.destroyOnLoad = destroyOnLoad; AudioClip clip; if (firstClip) { clip = clips[0]; } else { int rngIndex = Random.Range(0, clips.Count); clip = clips[rngIndex]; } var time = Time.time; if (!Mathf.Approximately(maxPlayInterval, 0)) { if (time - lastPlayTime < maxPlayInterval) { return null; } } lastPlayTime = time; var gameObject = new GameObject($"{this.name} -- {clip.name}"); gameObject.SetActive(false); gameObject.transform.position = position; if (!options.volume.HasValue) { options.volume = defaultVolume; } options.volume += additionalGain; var soundEffectInstance = gameObject.AddComponent(); soundEffectInstance.clip = clip; soundEffectInstance.options = options; gameObject.SetActive(true); return soundEffectInstance; } } }