Воспроизвести звук в Unity просто. Нужно создать компонент AudioSource прикрепить к нему звуковой файл в виде AudioClip и вызвать audioSource.Play() из скрипта. Или даже поставить автовоспроизведение на при создании объекта(Play on Awake).
Сложности начинаются когда звуков в игре становится много. Их все нужно расставить, прописать приоритеты. Звуки отдельно, музыку отдельно. При регулировке громкости звуков и музыки раздельно тоже сложности. Можно, конечно, регулировать громкость разных каналов в AudioMixer, но он не работает в WebGL. А Webplayer сейчас считается устаревшим.
А если какой то звук повторяется несколько раз подряд(например игрок быстро нажимает на кнопку и играет звук клика), то хорошо бы чтобы тот не обрывался на середине, а начинался новый, не мешая предыдущим. Да еще и при включении паузы звуки игры нужно ставить на паузу, а звуки меню нет. Из коробки такая возможность в Unity есть, но почему то доступна только из скрипта и не все о ней знают.
В общем хочется простой и удобный SoundManager, создание которого я и опишу. Для крупных проектов он не подойдет, а вот для прототипов и небольших игр вполне.

Итак что же должен представлять собой SoundManager? Ну во первых им должно быть удобно пользоваться. То есть никаких «найти объект на сцене», «присоеденить компонент» и прочего для пользователя, все внутри. Так что сразу делаем его синглтоном(Код сокращен, чтобы выделить суть).
Теперь менеджер сам создаст себя на сцене, так что добавлять его самостоятельно не нужно(и не рекомендуется).Создается он по префабу, путь до которого прописан в коде, так что перемещать префаб не стоит. Можно создавать и с помощью new GameObject() и AddComponent() если хочется. UPD. Теперь создается не по префабу. Кроме того объект сразу помечается с помощью DontDestroyOnLoad. Нужно это для того чтобы музыка и звуки продолжали играть без перебоев при перезагрузках сцен.
Теперь к любым методам можно обращаться просто написав SoundManager.Instance.Method(). Чтобы еще немного сократить эту запись для всех методов я дописал статический враппер:
Так что писать можно даже еще короче SoundManager.Method().
Объект есть, работать с ним удобно. Дальше добавляем функциональность. Самая необходимая функция это PlaySound:
Для начала несколько проверок звука. Что он не пустой и что таких звуков не стало слишком много(Если где то в цикле по ошибке вызывается). После чего загружаем звук из ресурсов, ждем загрузки, создаем новый объект на сцену, добавляем AudioSource, настраиваем его и запускаем. Функция LoadClipAsync запускает асинхронную загрузку звукового файла из ресурсов по имени. Так что файл надо будет положить в папку «Resources/Sounds/Sounds». Создание объекта происходит по префабу, который загружен из ресурсов. Так что часть параметров(вроде приоритета звука), можно установить префабу из инспектора. Громкость устанавливается так же у каждого объекта отдельно. В отличие от установки громкости AudioListener-у это позволяет регулировать громкость звуков и музыки раздельно. Сохраним объект в списке звуков _sounds, чтобы иметь возможность регулировать его громкость и уничтожать по окончанию.
Параметр pausable нужен чтобы разделить UI звуки и игровые звуки. Первые должны играться всегда и никогда не ставиться на паузу. Вторые приостанавливаются во время паузы и продолжаются при возобновлении игры. Делается это автоматически с помощью флага soundSource.ignoreListenerPause, который почему то недоступен из Inspector-а.
Далее нам нужен метод для добавления музыки в игру. В целом код похож на добавление звука, но используется другой префаб(с дургим приоритетом и настройкой loop).
В большинстве неболших проектов достаточно одного трека проигрывающегося в данный момент, так что запуск новой музыки останавливает предыдущие треки автоматически, так что на каждой сцене достаточно вызвать лишь SoundManager.PlayMusic(«MusicForCurrentScene»); Кроме того при создании и остановке музыки добавляется плавное нарастание громкости и плавное угасание. Это позволяет сделать переход плавным и не бьет по слуху. Само плавное изменение громкости можно делать Tween-ом, но можно и ручками, чтобы было меньше зависимостей.
Дальше нам нужна возможность поставить паузу. Так как у всех звуков уже проставлена настройка ставятся ли они на паузу при паузе AudioListener-а, то методы получаются очень простыми.
Либо можно настроить автоматическое включение паузы.
Дальше нам потребуются методы установки и получения громкости.
Тут все просто. Сохраняем и читаем настройки с помощью PlayerPrefs, при изменении пробегаемся по звукам и применяем новую громкость. Аналогично можно сделать настройку mute и все тоже самое для музыки.
Ну вот и все. SoundManager, которым легко пользоваться готов. Так как мы вынесли шаблоны для звуков и музыки в префабы, то к ним легко можно подключить output из AudioMixer-а. Кроме того есть еще небольшой класс, упрощающий вызовы нужных методов из анимаций, обработчиков кнопок и пр, чтобы не нужно было писать скрипт из-за одной строчки.

Плюсы полученного менеджера:
+ Простота в использовании
+ Чистый код и объекты сцены. Не нужно вешать компоненты звука нигде, искать и вызывать их из кода
+ Музыка, которая не прерывается при загрузке сцены и меняется плавно
+ Геймплейные и UI звуки
+ Поддержка паузы
+ Поддержка AudioMixer
+ Работа на всех платформах, включая не поддерживающие AudioMixer (например WebGL)
+ Поддержка голоса рассказчика(в статье не упомянуто, но в полном коде реализовано)
Ограничения текущей реализации(Пока нету):
— Пока нет позиционного 3d звука
— Изменения pitch-а звука, чтобы много кратное повторение одинаковых звуков не приедалось
— Загрузка звука при использовании может приводить к лагам(Незаметно на мелких проектах и небольших звуках)
— Нет регулировки громкости отдельно взятого звука
— Нет зацикленных звуков, вроде амбиента
Полный код менеджера можно посмотреть на моем GitHub-е:
https://github.com/Gasparfx/SoundManager
Наш проект использующий этот менеджер на GreenLight:
http://steamcommunity.com/sharedfiles/filedetails/?id=577337491
Сложности начинаются когда звуков в игре становится много. Их все нужно расставить, прописать приоритеты. Звуки отдельно, музыку отдельно. При регулировке громкости звуков и музыки раздельно тоже сложности. Можно, конечно, регулировать громкость разных каналов в AudioMixer, но он не работает в WebGL. А Webplayer сейчас считается устаревшим.
А если какой то звук повторяется несколько раз подряд(например игрок быстро нажимает на кнопку и играет звук клика), то хорошо бы чтобы тот не обрывался на середине, а начинался новый, не мешая предыдущим. Да еще и при включении паузы звуки игры нужно ставить на паузу, а звуки меню нет. Из коробки такая возможность в Unity есть, но почему то доступна только из скрипта и не все о ней знают.
В общем хочется простой и удобный SoundManager, создание которого я и опишу. Для крупных проектов он не подойдет, а вот для прототипов и небольших игр вполне.

Итак что же должен представлять собой SoundManager? Ну во первых им должно быть удобно пользоваться. То есть никаких «найти объект на сцене», «присоеденить компонент» и прочего для пользователя, все внутри. Так что сразу делаем его синглтоном(Код сокращен, чтобы выделить суть).
private static SoundManager _instance;
public static SoundManager Instance
{
get
{
if (_instance != null)
{
return _instance;
}
// Do not modify _instance here. It will be assigned in awake
return new GameObject("(singleton) SoundManager").AddComponent<SoundManager>();
}
}
void Awake()
{
// Only one instance of SoundManager at a time!
if (_instance != null)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
}
Теперь менеджер сам создаст себя на сцене, так что добавлять его самостоятельно не нужно(и не рекомендуется).
Теперь к любым методам можно обращаться просто написав SoundManager.Instance.Method(). Чтобы еще немного сократить эту запись для всех методов я дописал статический враппер:
public static void PlayMusic(string name)
{
Instance.PlayMusicInternal(name);
}
Так что писать можно даже еще короче SoundManager.Method().
Объект есть, работать с ним удобно. Дальше добавляем функциональность. Самая необходимая функция это PlaySound:
void PlaySoundInternal(string soundName, bool pausable)
{
if (string.IsNullOrEmpty(soundName)) {
Debug.Log("Sound null or empty");
return;
}
int sameCountGuard = 0;
foreach (AudioSource audioSource in _sounds)
{
if (audioSource.clip.name == soundName)
sameCountGuard++;
}
if (sameCountGuard > 8)
{
Debug.Log("Too much duplicates for sound: " + soundName);
return;
}
if (_sounds.Count > 16) {
Debug.Log("Too much sounds");
return;
}
StartCoroutine(PlaySoundInternalSoon(soundName, pausable));
}
IEnumerator PlaySoundInternalSoon(string soundName, bool pausable)
{
ResourceRequest request = LoadClipAsync("Sounds/" + soundName);
while (!request.isDone)
{
yield return null;
}
AudioClip soundClip = (AudioClip)request.asset;
if (null == soundClip)
{
Debug.Log("Sound not loaded: " + soundName);
}
GameObject sound = (GameObject)Instantiate(soundPrefab);
sound.transform.parent = transform;
AudioSource soundSource = sound.GetComponent<AudioSource>();
soundSource.mute = _mutedSound;
soundSource.volume = _volumeSound * DefaultSoundVolume;
soundSource.clip = soundClip;
soundSource.Play();
soundSource.ignoreListenerPause = !pausable;
_sounds.Add(soundSource);
}
Для начала несколько проверок звука. Что он не пустой и что таких звуков не стало слишком много(Если где то в цикле по ошибке вызывается). После чего загружаем звук из ресурсов, ждем загрузки, создаем новый объект на сцену, добавляем AudioSource, настраиваем его и запускаем. Функция LoadClipAsync запускает асинхронную загрузку звукового файла из ресурсов по имени. Так что файл надо будет положить в папку «Resources/Sounds/Sounds». Создание объекта происходит по префабу, который загружен из ресурсов. Так что часть параметров(вроде приоритета звука), можно установить префабу из инспектора. Громкость устанавливается так же у каждого объекта отдельно. В отличие от установки громкости AudioListener-у это позволяет регулировать громкость звуков и музыки раздельно. Сохраним объект в списке звуков _sounds, чтобы иметь возможность регулировать его громкость и уничтожать по окончанию.
Параметр pausable нужен чтобы разделить UI звуки и игровые звуки. Первые должны играться всегда и никогда не ставиться на паузу. Вторые приостанавливаются во время паузы и продолжаются при возобновлении игры. Делается это автоматически с помощью флага soundSource.ignoreListenerPause, который почему то недоступен из Inspector-а.
Далее нам нужен метод для добавления музыки в игру. В целом код похож на добавление звука, но используется другой префаб(с дургим приоритетом и настройкой loop).
void PlayMusicInternal(string musicName)
{
if (string.IsNullOrEmpty(musicName)) {
Debug.Log("Music empty or null");
return;
}
if (_currentMusicName == musicName) {
Debug.Log("Music already playing: " + musicName);
return;
}
StopMusicInternal();
_currentMusicName = musicName;
AudioClip musicClip = LoadClip("Music/" + musicName);
GameObject music = (GameObject)Instantiate(musicPrefab);
if (null == music) {
Debug.Log("Music not found: " + musicName);
}
music.transform.parent = transform;
AudioSource musicSource = music.GetComponent<AudioSource>();
musicSource.mute = _mutedMusic;
musicSource.ignoreListenerPause = true;
musicSource.clip = musicClip;
musicSource.Play();
musicSource.volume = 0;
StartFadeMusic(musicSource, MusicFadeTime, _volumeMusic * DefaultMusicVolume, false);
_currentMusicSource = musicSource;
}
В большинстве неболших проектов достаточно одного трека проигрывающегося в данный момент, так что запуск новой музыки останавливает предыдущие треки автоматически, так что на каждой сцене достаточно вызвать лишь SoundManager.PlayMusic(«MusicForCurrentScene»); Кроме того при создании и остановке музыки добавляется плавное нарастание громкости и плавное угасание. Это позволяет сделать переход плавным и не бьет по слуху. Само плавное изменение громкости можно делать Tween-ом, но можно и ручками, чтобы было меньше зависимостей.
Дальше нам нужна возможность поставить паузу. Так как у всех звуков уже проставлена настройка ставятся ли они на паузу при паузе AudioListener-а, то методы получаются очень простыми.
public static void Pause()
{
AudioListener.pause = true;
}
public static void UnPause()
{
AudioListener.pause = false;
}
Либо можно настроить автоматическое включение паузы.
void Update()
{
if (AutoPause)
{
bool curPause = Time.timeScale < 0.1f;
if (curPause != AudioListener.pause)
{
AudioListener.pause = curPause;
}
}
}
Дальше нам потребуются методы установки и получения громкости.
void SetSoundVolumeInternal(float volume)
{
_volumeSound = volume;
SaveSettings();
ApplySoundVolume();
}
float GetSoundVolumeInternal()
{
return _volumeSound;
}
void SaveSettings()
{
PlayerPrefs.SetFloat("SM_SoundVolume", _volumeSound);
}
void LoadSettings()
{
_volumeSound = PlayerPrefs.GetFloat("SM_SoundVolume", 1);
ApplySoundVolume();
}
void ApplySoundVolume()
{
foreach (AudioSource sound in _sounds)
{
sound.volume = _volumeSound * DefaultSoundVolume;
}
}
Тут все просто. Сохраняем и читаем настройки с помощью PlayerPrefs, при изменении пробегаемся по звукам и применяем новую громкость. Аналогично можно сделать настройку mute и все тоже самое для музыки.
Ну вот и все. SoundManager, которым легко пользоваться готов. Так как мы вынесли шаблоны для звуков и музыки в префабы, то к ним легко можно подключить output из AudioMixer-а. Кроме того есть еще небольшой класс, упрощающий вызовы нужных методов из анимаций, обработчиков кнопок и пр, чтобы не нужно было писать скрипт из-за одной строчки.

Плюсы полученного менеджера:
+ Простота в использовании
+ Чистый код и объекты сцены. Не нужно вешать компоненты звука нигде, искать и вызывать их из кода
+ Музыка, которая не прерывается при загрузке сцены и меняется плавно
+ Геймплейные и UI звуки
+ Поддержка паузы
+ Поддержка AudioMixer
+ Работа на всех платформах, включая не поддерживающие AudioMixer (например WebGL)
+ Поддержка голоса рассказчика(в статье не упомянуто, но в полном коде реализовано)
Ограничения текущей реализации(Пока нету):
— Пока нет позиционного 3d звука
— Изменения pitch-а звука, чтобы много кратное повторение одинаковых звуков не приедалось
— Загрузка звука при использовании может приводить к лагам(Незаметно на мелких проектах и небольших звуках)
— Нет регулировки громкости отдельно взятого звука
— Нет зацикленных звуков, вроде амбиента
Полный код менеджера можно посмотреть на моем GitHub-е:
https://github.com/Gasparfx/SoundManager
Наш проект использующий этот менеджер на GreenLight:
http://steamcommunity.com/sharedfiles/filedetails/?id=577337491