Реализовать простой менеджер аудио
Условие
Реализуйте менеджер аудио для управления звуками и музыкой в игре.
Требования
- Singleton паттерн для доступа из любого места
- Раздельное управление громкостью музыки и звуковых эффектов
- Методы:
- PlayMusic(AudioClip clip) - играть фоновую музыку с плавным переходом
- PlaySFX(AudioClip clip) - воспроизвести звуковой эффект
- PlaySFXAtPosition(AudioClip clip, Vector3 position) - 3D звук
- SetMusicVolume(float volume)
- SetSFXVolume(float volume)
- Mute() / Unmute()
- Сохранение настроек громкости в PlayerPrefs
Бонус
- Пул AudioSource для звуковых эффектов
- Плавный кроссфейд между треками
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Архитектура AudioManager
Хороший аудио менеджер должен быть простым, гибким и эффективным. Я реализую систему с поддержкой пула AudioSource для звуковых эффектов, раздельным управлением музыкой и SFX, а также плавными кроссфейдами между треками.
Основной AudioManager
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class AudioManager : Singleton<AudioManager>
{
[Header("Audio Sources")]
[SerializeField] private AudioSource musicSource;
[SerializeField] private Transform sfxPoolParent;
[SerializeField] private int sfxPoolSize = 10;
[Header("Volume Settings")]
[SerializeField] private float masterVolume = 1f;
[SerializeField] private float musicVolume = 0.7f;
[SerializeField] private float sfxVolume = 0.8f;
[SerializeField] private float musicFadeDuration = 1f; // Кроссфейд между треками
[Header("Mute Settings")]
[SerializeField] private bool isMuted = false;
// Пул звуковых эффектов
private Queue<AudioSource> sfxPool;
private AudioSource currentMusicSource;
private Coroutine musicFadeCoroutine;
// Константы для PlayerPrefs
private const string MUSIC_VOLUME_KEY = "AudioManager_MusicVolume";
private const string SFX_VOLUME_KEY = "AudioManager_SFXVolume";
private const string MASTER_VOLUME_KEY = "AudioManager_MasterVolume";
private const string MUTE_KEY = "AudioManager_IsMuted";
protected override void Awake()
{
base.Awake();
// Инициализируем пул
InitializeSFXPool();
// Загружаем сохранённые настройки
LoadVolumeSettings();
}
void Start()
{
// Создаём AudioSource для музыки если его нет
if (musicSource == null)
{
GameObject musicObj = new GameObject("MusicSource");
musicObj.transform.SetParent(transform);
musicSource = musicObj.AddComponent<AudioSource>();
musicSource.loop = true;
currentMusicSource = musicSource;
}
}
/// <summary>Инициализируем пул AudioSource для SFX</summary>
private void InitializeSFXPool()
{
sfxPool = new Queue<AudioSource>(sfxPoolSize);
// Создаём парент для организации в иерархии
if (sfxPoolParent == null)
{
GameObject poolParent = new GameObject("SFXPool");
poolParent.transform.SetParent(transform);
sfxPoolParent = poolParent.transform;
}
// Создаём и добавляем AudioSource в пул
for (int i = 0; i < sfxPoolSize; i++)
{
CreateSFXSource();
}
}
/// <summary>Создать новый AudioSource для SFX</summary>
private void CreateSFXSource()
{
GameObject sfxObj = new GameObject("SFX_Source");
sfxObj.transform.SetParent(sfxPoolParent);
AudioSource audioSource = sfxObj.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
audioSource.loop = false;
sfxPool.Enqueue(audioSource);
}
/// <summary>Получить AudioSource из пула (или создать новый если пул пуст)</summary>
private AudioSource GetSFXSource()
{
AudioSource source;
if (sfxPool.Count > 0)
{
source = sfxPool.Dequeue();
}
else
{
// Если пул пуст, создаём новый источник
CreateSFXSource();
source = sfxPool.Dequeue();
Debug.LogWarning("SFX пул переполнен, создан дополнительный AudioSource");
}
return source;
}
/// <summary>Вернуть AudioSource в пул</summary>
private void ReturnSFXSource(AudioSource source)
{
if (sfxPool.Count < sfxPoolSize)
{
source.clip = null;
source.Stop();
sfxPool.Enqueue(source);
}
else
{
Destroy(source.gameObject);
}
}
/// <summary>Воспроизвести музыку с кроссфейдом</summary>
public void PlayMusic(AudioClip clip)
{
if (clip == null)
{
Debug.LogError("Попытка проиграть null AudioClip как музыку");
return;
}
// Останавливаем текущий кроссфейд если он есть
if (musicFadeCoroutine != null)
{
StopCoroutine(musicFadeCoroutine);
}
// Начинаем кроссфейд из текущей музыки в новую
musicFadeCoroutine = StartCoroutine(CrossfadeMusic(clip));
}
/// <summary>Кроссфейд между музыкальными треками</summary>
private IEnumerator CrossfadeMusic(AudioClip newClip)
{
float fadeOutTime = musicFadeDuration * 0.5f;
float fadeInTime = musicFadeDuration * 0.5f;
// Fade out текущей музыки
float elapsedTime = 0f;
while (elapsedTime < fadeOutTime && musicSource.volume > 0)
{
elapsedTime += Time.deltaTime;
musicSource.volume = Mathf.Lerp(musicVolume, 0, elapsedTime / fadeOutTime);
yield return null;
}
musicSource.Stop();
musicSource.clip = newClip;
musicSource.volume = 0;
musicSource.Play();
// Fade in новой музыки
elapsedTime = 0f;
while (elapsedTime < fadeInTime && musicSource.volume < musicVolume)
{
elapsedTime += Time.deltaTime;
musicSource.volume = Mathf.Lerp(0, musicVolume, elapsedTime / fadeInTime);
yield return null;
}
musicSource.volume = musicVolume;
Debug.Log($"Музыка изменена на {newClip.name}");
}
/// <summary>Воспроизвести звуковой эффект</summary>
public void PlaySFX(AudioClip clip, float volumeOverride = -1f)
{
if (clip == null)
{
Debug.LogError("Попытка проиграть null AudioClip как SFX");
return;
}
AudioSource source = GetSFXSource();
source.clip = clip;
source.volume = volumeOverride >= 0 ? volumeOverride : sfxVolume;
source.spatialBlend = 0f; // 2D звук
source.Play();
// Возвращаем источник в пул после окончания звука
StartCoroutine(ReturnSourceToPool(source, clip.length));
}
/// <summary>Воспроизвести 3D звук в определённой позиции</summary>
public void PlaySFXAtPosition(AudioClip clip, Vector3 position, float volumeOverride = -1f)
{
if (clip == null)
{
Debug.LogError("Попытка проиграть null AudioClip как 3D SFX");
return;
}
AudioSource source = GetSFXSource();
source.clip = clip;
source.volume = volumeOverride >= 0 ? volumeOverride : sfxVolume;
source.spatialBlend = 1f; // 3D звук
source.transform.position = position;
source.Play();
// Возвращаем источник в пул после окончания звука
StartCoroutine(ReturnSourceToPool(source, clip.length));
}
/// <summary>Возвращение источника в пул после завершения воспроизведения</summary>
private IEnumerator ReturnSourceToPool(AudioSource source, float duration)
{
yield return new WaitForSeconds(duration);
ReturnSFXSource(source);
}
/// <summary>Остановить музыку</summary>
public void StopMusic()
{
if (musicSource != null)
{
musicSource.Stop();
}
}
/// <summary>Паузировать музыку</summary>
public void PauseMusic()
{
if (musicSource != null && musicSource.isPlaying)
{
musicSource.Pause();
}
}
/// <summary>Возобновить музыку</summary>
public void ResumeMusic()
{
if (musicSource != null && !musicSource.isPlaying)
{
musicSource.UnPause();
}
}
/// <summary>Установить громкость музыки (0-1)</summary>
public void SetMusicVolume(float volume)
{
musicVolume = Mathf.Clamp01(volume);
if (musicSource != null)
{
musicSource.volume = musicVolume * masterVolume * (isMuted ? 0 : 1);
}
SaveVolumeSettings();
}
/// <summary>Установить громкость SFX (0-1)</summary>
public void SetSFXVolume(float volume)
{
sfxVolume = Mathf.Clamp01(volume);
SaveVolumeSettings();
}
/// <summary>Установить главную громкость (0-1)</summary>
public void SetMasterVolume(float volume)
{
masterVolume = Mathf.Clamp01(volume);
UpdateAllVolumes();
SaveVolumeSettings();
}
/// <summary>Получить текущую громкость музыки</summary>
public float GetMusicVolume() => musicVolume;
/// <summary>Получить текущую громкость SFX</summary>
public float GetSFXVolume() => sfxVolume;
/// <summary>Получить главную громкость</summary>
public float GetMasterVolume() => masterVolume;
/// <summary>Отключить звук</summary>
public void Mute()
{
isMuted = true;
UpdateAllVolumes();
SaveVolumeSettings();
Debug.Log("Звук отключён");
}
/// <summary>Включить звук</summary>
public void Unmute()
{
isMuted = false;
UpdateAllVolumes();
SaveVolumeSettings();
Debug.Log("Звук включён");
}
/// <summary>Переключить звук</summary>
public void ToggleMute()
{
if (isMuted)
{
Unmute();
}
else
{
Mute();
}
}
/// <summary>Проверить отключён ли звук</summary>
public bool IsMuted() => isMuted;
/// <summary>Обновить громкость всех источников</summary>
private void UpdateAllVolumes()
{
float effectiveVolume = isMuted ? 0 : 1;
if (musicSource != null)
{
musicSource.volume = musicVolume * masterVolume * effectiveVolume;
}
}
/// <summary>Сохранить настройки громкости</summary>
private void SaveVolumeSettings()
{
PlayerPrefs.SetFloat(MASTER_VOLUME_KEY, masterVolume);
PlayerPrefs.SetFloat(MUSIC_VOLUME_KEY, musicVolume);
PlayerPrefs.SetFloat(SFX_VOLUME_KEY, sfxVolume);
PlayerPrefs.SetInt(MUTE_KEY, isMuted ? 1 : 0);
PlayerPrefs.Save();
}
/// <summary>Загрузить сохранённые настройки громкости</summary>
private void LoadVolumeSettings()
{
masterVolume = PlayerPrefs.GetFloat(MASTER_VOLUME_KEY, 1f);
musicVolume = PlayerPrefs.GetFloat(MUSIC_VOLUME_KEY, 0.7f);
sfxVolume = PlayerPrefs.GetFloat(SFX_VOLUME_KEY, 0.8f);
isMuted = PlayerPrefs.GetInt(MUTE_KEY, 0) == 1;
UpdateAllVolumes();
}
/// <summary>Получить информацию о пуле</summary>
public string GetPoolStats()
{
return $"SFX Pool: {sfxPool.Count}/{sfxPoolSize} доступно";
}
}
Интеграция с UI (громкость)
public class AudioSettingsUI : MonoBehaviour
{
[SerializeField] private Slider musicVolumeSlider;
[SerializeField] private Slider sfxVolumeSlider;
[SerializeField] private Slider masterVolumeSlider;
[SerializeField] private Toggle muteToggle;
[SerializeField] private Text volumeLabel;
private AudioManager audioManager;
void Start()
{
audioManager = AudioManager.Instance;
// Инициализируем слайдеры
musicVolumeSlider.value = audioManager.GetMusicVolume();
sfxVolumeSlider.value = audioManager.GetSFXVolume();
masterVolumeSlider.value = audioManager.GetMasterVolume();
muteToggle.isOn = audioManager.IsMuted();
// Подписываемся на события
musicVolumeSlider.onValueChanged.AddListener(audioManager.SetMusicVolume);
sfxVolumeSlider.onValueChanged.AddListener(audioManager.SetSFXVolume);
masterVolumeSlider.onValueChanged.AddListener(audioManager.SetMasterVolume);
muteToggle.onValueChanged.AddListener(_ => audioManager.ToggleMute());
}
}
Примеры использования
Воспроизведение музыки:
AudioManager.Instance.PlayMusic(battleMusicClip);
Воспроизведение звука:
AudioManager.Instance.PlaySFX(swordSwingClip);
Воспроизведение 3D звука:
AudioManager.Instance.PlaySFXAtPosition(explosionClip, explosionPosition);
Управление громкостью:
AudioManager.Instance.SetMasterVolume(0.5f);
AudioManager.Instance.SetMusicVolume(0.8f);
Отключение звука:
AudioManager.Instance.Mute();
Ключевые особенности
1. Пул AudioSource — переиспользуем источники вместо создания новых каждый раз.
2. Кроссфейд — плавный переход между музыкальными треками без пауз.
3. Раздельное управление — независимые громкости для музыки и SFX.
4. Сохранение настроек — громкость сохраняется в PlayerPrefs.
5. 2D и 3D звуки — поддержка как обычных так и пространственных звуков.
6. Оптимизация — Coroutine для автоматического возврата источников в пул.
Эта система обеспечивает гибкость, производительность и удобство в управлении аудио.