Воспроизвести звук в Unity просто. Нужно создать компонент AudioSource прикрепить к нему звуковой файл в виде AudioClip и вызвать audioSource.Play() из скрипта. Или даже поставить автовоспроизведение на при создании объекта(Play on Awake).
Сложности начинаются когда звуков в игре становится много. Их все нужно расставить, прописать приоритеты. Звуки отдельно, музыку отдельно. При регулировке громкости звуков и музыки раздельно тоже сложности. Можно, конечно, регулировать громкость разных каналов в AudioMixer, но он не работает в WebGL. А Webplayer сейчас считается устаревшим.
А если какой то звук повторяется несколько раз подряд(например игрок быстро нажимает на кнопку и играет звук клика), то хорошо бы чтобы тот не обрывался на середине, а начинался новый, не мешая предыдущим. Да еще и при включении паузы звуки игры нужно ставить на паузу, а звуки меню нет. Из коробки такая возможность в Unity есть, но почему то доступна только из скрипта и не все о ней знают.
В общем хочется простой и удобный SoundManager, создание которого я и опишу. Для крупных проектов он не подойдет, а вот для прототипов и небольших игр вполне.
Итак что же должен представлять собой SoundManager? Ну во первых им должно быть удобно пользоваться. То есть никаких «найти объект на сцене», «присоеденить компонент» и прочего для пользователя, все внутри. Так что сразу делаем его синглтоном(Код сокращен, чтобы выделить суть).
private static SoundManager _instance; public static SoundManager Instance { get { if (_instance == null) { _instance = (SoundManager)FindObjectOfType(typeof(SoundManager)); if (_instance == null) { GameObject singleton = (GameObject)Instantiate(Resources.Load<GameObject>(PrefabPath)); _instance = singleton.GetComponent<SoundManager>(); singleton.name = "(singleton) " + typeof(SoundManager).ToString(); DontDestroyOnLoad(singleton); } } return _instance; } }
Теперь менеджер сам создаст себя на сцене, так что добавлять его самостоятельно не нужно(и не рекомендуется). Создается он по префабу, путь до которого прописан в коде, так что перемещать префаб не стоит. Можно создавать и с помощью new GameObject() и AddComponent() если хочется. Кроме того объект сразу помечается с помощью DontDestroyOnLoad. Нужно это для того чтобы музыка и звуки продолжали играть без перебоем при перезагрузках сцен.
Теперь к любым методам можно обращаться просто написав 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 (_sounds.Count > 16) { Debug.Log("Too much duplicates for sound: " + soundName); 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
ссылка на оригинал статьи http://habrahabr.ru/post/274529/
Добавить комментарий