using System.Collections.Generic; using Cysharp.Threading.Tasks; using Unity.Mathematics; using UnityEngine; using UnityEngine.Audio; using Object = UnityEngine.Object; #if UNITY_EDITOR using UnityEditor; #endif namespace DanieleMarotta.RiversongCodeShowcase { [Service(typeof(ISoundPlayer))] [GameSystemGroup(typeof(AudioSystemGroup))] [InitializeAfter(typeof(BackgroundMusicSystem))] public class SoundPlayerSystem : GameSystem, IInitializable, IUpdatable, ISoundPlayer, IDrawGizmos { private const int ChannelCount = 16; [InjectService] private ITileSpace _tileSpace; [InjectService] private IScene _scene; [InjectService] private ICameraProperties _cameraProperties; [InjectService] private GameConfig _config; private AudioSourcePool _poolNonSpatial; private AudioSourcePool _poolSpatial; private SystemSoundLibrary _systemSoundLibrary; public SoundPlayerSystem(IServiceLocator serviceLocator) : base(serviceLocator) { } public async UniTask InitializeAsync() { var systemSoundLibraryTask = _config.Audio.SystemSoundLibrary.LoadAssetAsync().ToUniTask(); var audioSourcePrefabTask = _config.Audio.AudioSourcePrefab.LoadAssetAsync().ToUniTask(); _systemSoundLibrary = await systemSoundLibraryTask; var audioSourcePrefab = (await audioSourcePrefabTask).GetComponent(); await InitializePoolAsync(audioSourcePrefab); } private async UniTask InitializePoolAsync(AudioSource audioSourcePrefab) { _poolNonSpatial = InitializePool(audioSourcePrefab, "Sound_{0:00} (2D)"); await UniTask.NextFrame(); _poolSpatial = InitializePool(audioSourcePrefab, "Sound_{0:00} (3D)"); await UniTask.NextFrame(); } private AudioSourcePool InitializePool(AudioSource audioSourcePrefab, string nameFormat) { var audioSources = new AudioSource[ChannelCount]; for (var i = 0; i < ChannelCount; i++) { var source = Object.Instantiate(audioSourcePrefab, _scene.SceneFolders.AudioSources); source.name = string.Format(nameFormat, i); audioSources[i] = source; } return new AudioSourcePool(audioSources); } public void Update() { UpdateSpatialAudioSources(); } private void UpdateSpatialAudioSources() { var cameraPosition = ((float3)_scene.MainCamera.transform.position).xz; var horizontalDistanceRange = _config.Audio.SpatialAudioHorizontalDistanceRange; var zoomRange = _config.Audio.SpatialAudioZoomRange; foreach (var audioSource in _poolSpatial.AudioSources) { #if !UNITY_EDITOR if (!audioSource.isPlaying) continue; #endif var p = ((float3)audioSource.transform.position).xz; var horizontalDistance = math.distance(cameraPosition, p); var t = math.saturate(math.unlerp(horizontalDistanceRange.x, horizontalDistanceRange.y, horizontalDistance)); var attenuation = 1 - t * t; var zoomFactor = 1 - math.unlerp(zoomRange.x, zoomRange.y, _cameraProperties.Zoom); audioSource.volume = attenuation * zoomFactor; } } public void Play(AudioResource resource) { var source = _poolNonSpatial.GetAudioSource(); source.resource = resource; source.Play(); } public void PlayAt(AudioResource resource, float3 position) { var source = _poolSpatial.GetAudioSource(); source.resource = resource; source.transform.position = position; source.Play(); } public void PlayAt(AudioResource resource, int2 tile) { PlayAt(resource, _tileSpace.TileToWorld(tile)); } public AudioResource GetSystemSound(SystemSoundId soundId) { return _systemSoundLibrary.Sounds.GetValueOrDefault(soundId); } public void DrawGizmos(bool selected) { #if UNITY_EDITOR const int width = 60; const int height = 8; var camera = _scene.MainCamera; foreach (var audioSource in _poolSpatial.AudioSources) { var screenPoint = camera.WorldToScreenPoint(audioSource.transform.position); if (screenPoint.z < 0) continue; var p = new Vector2(screenPoint.x - width * 0.5f, camera.pixelHeight - screenPoint.y); Handles.BeginGUI(); EditorGUI.DrawRect(new Rect(p.x, p.y, width, height), new Color(0, 0, 0, 0.6f)); var volume = audioSource.volume; var color = Color.Lerp(Color.red, Color.green, volume); EditorGUI.DrawRect(new Rect(p.x + 0.5f * (width * (1 - volume)), p.y, width * volume, height), color); Handles.EndGUI(); } #endif } private class AudioSourcePool { private int _nextChannel; public AudioSourcePool(AudioSource[] audioSources) { AudioSources = audioSources; } public AudioSource[] AudioSources { get; } public AudioSource GetAudioSource() { var source = AudioSources[_nextChannel]; _nextChannel = (_nextChannel + 1) % ChannelCount; return source; } } } }