[Из песочницы] Unity 4.5 для самых маленьких — работа со звуком (урок)
Вместо предисловия.Микширование в Unity 5.0 через AudioMixer это, наверное, очень круто.Но мне нужно было решение здесь и сейчас (на тот момент — в 4.5.2f1).Задач было три:
1а. Плавное затухание эмбиента (или саундтрека, если хотите) предыдущего уровня при переходе на следующий.1 б. Далее, звук удаляется через заданное количество времени. 2. Плавное возникновение (усиление громкости звука от 0 до 1) эмбиента после загрузки уровня. 3. Программное микширование эмбиента с самим собой — т.е. музыка, за ~10 секунд до своего финала, должна плавно затухать и плавно переходить с усилением в собственное начало.Другими словами — «программный» луп (закольцованность) на лету. Программист из меня, мягко говоря, пока что никакой, поэтому большинство задач я решаю с помощью гугла и изучения документации Юнити.Итак, начнём по порядку.0. Подготовительные работы1. Создаём проект, сохраняем. 2. В проекте создаём две сцены, сохраняем. Для удобства, можно назвать их просто — 0 и 1 3. Перетаскиваем созданные сцены в окошко Build Settings (Ctrl+Shift+B). 4. Перетаскиваем в окошко Project два музыкальных файла (рекомендую *ogg) — для первой и второй сцены.В какие папки и как разместить эти файлики — это на ваш вкус.Также, рекомендую для урока брать музыку покороче — чтобы не ждать момента закольцованности слишком долго.В принципе, это можно обойти (выставить время для перехода раньше), но для точности лучше так. 5. Настраиваем наши музыкальные файлы: снимаем галочку чекбокса 3D Sound — в нашем случае громкость саундтрека/эмбиента не зависит от положения игрока с пространстве относительно источника звука.Перетаскиваем один из звуковых файлов в первую сцену и другой во вторую.У камеры (или персонажа) должен быть Audio Listener 1. Плавное затухание эмбиента предыдущего уровня при переходе на следующий.Последующее удаление через заданное количество времени Создаём новый js скрипт, люблю дурацкие названия — назову его FadeOutAndDestrTimerWhenNextLvlLoadПервое, что нам нужно — чтобы объект «звук» не уничтожился при переходе на следующий уровень.Зачем? Затем, чтобы сделать ему плавное затухание и затем удалить.
Лезем читать документацию.
Ага, нам нужна строчка:
DontDestroyOnLoad (transform.gameObject); Всё, этого достаточно.Исходя из наших требований, обдумываем три «изменябельных» переменных:
продолжительность затухания звука время от момента загрузки уровня до удаления объекта «звук» номер уровня, на котором скрипт запустится —нужная штука, ведь скрипт универсальный — мы будем применять его на разных уровнях. Конвевертируем мысли в строчки: var fadeTime = 0; var levelToExecute = 0; var DestroyTime = 0; Ага, значит далее скрипт будет состоять из двух функций — «таймер удаления» и «механизм затухания».Начнем с «таймера удаления».Тут все элементарно:
function OnLevelWasLoaded (level: int) { if (level == (levelToExecute)) { Destroy (gameObject, DestroyTime); //Destroy (gameObject, 20); //- можно и так, конечно. } } Если номер загруженного уровня будет соответствовать указанному нами в окошке «levelToExecute», то запускается «таймер удаления» — от момента загрузки уровня до времени, указанного нами в окошке DestroyTime.Хм, а почему бы нам не добавить сразу и запуск (ещё не написанной) функции «механизм затухания» сюда же? Можно.
function OnLevelWasLoaded (level: int) { if (level == (levelToExecute)) { FadeAudio (fadeTime, Fade.Out); //продолжительность затухания, тип фейда — затухание (а не усиление) Destroy (gameObject, DestroyTime); } } Теперь осталось написать функцию «механизм затухания», допустим такой метод: function FadeAudio (timer: float, fadeType: Fade) { var start = fadeType == Fade.In? 0.0: 1.0; //на старте (указанного нового уровня) произойдёт усиление звука из 0 до 1 var end = fadeType == Fade.In? 1.0: 0.0; //в конце произойдёт угасание из 1 в 0 var i = 0.0; //переменное значение (громкости) var step = 1.0/timer; //шаг («плавного» изменения громкости) равен единице громкости / таймер while (i <= 1.0) { // до тех пор, ПОКА "0" (громкость) равна или меньше "1" исполнять ↓ , //вплоть до получения значения "0" i += step * Time.deltaTime; audio.volume = Mathf.Lerp(start, end, i); // Mathf.Lerp - находим промежуточные значения громкости соответственно имеющимся start, end, и значение "i" //растягиваем во времени изменение звука yield; } } Вот, что у нас получилось:
2,3 «Программный» луп эмбиента с самим собой, плавное усиление эмбиента при загрузке уровня Мы уже знаем почти все, что нужно для этой части.Добавим чуть-чуть.Что из себя представляет микширование/луп традиционным способом (как это понимаю дилетант я)? Вот, например, скриншот из Adobe Audition.
Берём трек (0), делаем возрастание громкости вначале (1) и затухание в конце (2), клонируем результат в начало (3) и в конец (4), со смещением до середины затуханий (5), обрезаем лишнее (6).7 — Сохраняем результат в ogg (или в mp3).
Мы теряем немного материала в начале и в конце, обрезанные, хм… начало и конец «загрязняются» примесью друг друга.Как-то так, можно, конечно и по-другому.Например , «срезать» полностью вначале или в конце, оставив эм… перекрестие затуханий в противоположном начале или в конце.Но это «съест» ещё больше материала.
Почему я делаю это программно?
1. Потому что лень. Достаточно было написать скрипт один раз, чтобы забыть об Audacity и Adobe Audition 2. Исходник сохраняется в первозданном виде — без «съедания» начала (переходом в конец), без искажений в начале и в конце. 3. Я могу сделать переход на любом отрезке трека, а если допилить скрипт, то и микшировать несколько треков —все это без издевательства над аудио- материалом в аудио-редакторах и без лишней траты времени. 4.Традиционный способ ограничивает выбор форматов одним лишь *ogg. Луп из *mp3 будет «цокать» в момент перехода.Мой способ лишён этого недостатка. 5. Программы вроде Audacity, извините, жутко неудобны. Программы вроде Adobe Audition — стоят денег. Создаём новый js скрипт, я назову его StartFadeIn_CloneTimer_FadeOut_DestrTimerОбратите внимание — название не просто дурацкое, в нем указана последовательность исполнения функций, мне так удобно.Кстати, изначально я написал п.1 и п.2–3 в один скрипт, но оказалось удобнее отключать «кусочек» при потребности.Можете их объединить, чуть подкорректировав.
Оглянемся назад, в начало статьи — какие требования предъявлены будущему скрипту? Музыка, за ~N секунд до своего финала, должна начать плавно затухать и плавно переходить с усилением в собственное начало.Что из этого выплывает?
Подумаем как это реализовать:
1. Фейд в начале трека, «нерегулируемый», происходит усиление громкости от 0 до 1.Он должен срабатывать как на старте уровня, так и у клона (об этом ниже). 2. Фейд в конце трека — происходит затухание громкости от 1 до 0, активируется за 10 (например) секунд до финала трека.Функция «нерегулируемая», но не совсем — не напрямую. Об этом ниже. 3. Уничтожение объекта «трек» — регулируемая цифра, по-умолчанию равна длине трека в секундах, но не обязательно.Мы вольны уничтожить трек в любой момент, не дожидаясь конца.Скрипт автоматически запустит фейд затухания за 10 секунд до уничтожения, соответственно этой цифре. 4. Регулируемая продолжительность фейда.Согласно написанному выше это 10с, но мы оставим возможность при надобности ее изменять. 5. Создание клона трека — регулируемая цифра, она равна длине трека в секундах, минус 10 секунд затухания. 6. Поскольку на разных уровнях разные треки, сделаем скрипт универсальным —добавим возможность выбирать нужный нам объект «звук». Конвертируем мысли в строчки, опять же, перечислим, что у нас здесь есть затухание и усиление, и создадим четыре переменных: enum Fade {In, Out} // для объявления перечисления — наших «усиление» и «затухание»
var prefab: Transform; //выбор объекта «звук» var DestroyTime: float = 1; //время (задержка) до удаления var CloneTime: float = 1; //время (задержка) до клонирования var fadeTime: float = 1; //продолжительность угасания/усиления Добавим функцию «Старт»: function Start (){ Spawn (); //↑запуск функции «Спавн» (клонирование) FadeAudio (fadeTime, Fade.In); //↑запуск функции «Фейд», включая указанную в переменной продолжительность и тип фейда — усиление Destroy ((prefab as Transform).gameObject, DestroyTime); //↑удаление выбранного объекта звук по истечению времени, указанного в DestroyTime } Теперь по порядку, добавим перечисленные выше функции. Клонирование: function Spawn (){ while (true){ yield WaitForSeconds (CloneTime); //задержка до исполнения, указана в переменной CloneTime for (var i: int = 0; i < 1; i++) { Instantiate (prefab, Vector3(i * 2.0, 0, 0), Quaternion.identity); } //клонирование выбранного объекта "звук" FadeAudio(fadeTime, Fade.Out); } } Функция «Фейд» у нас уже есть — копипастим её из первой части статьи: function FadeAudio (timer : float, fadeType : Fade) { var start = fadeType == Fade.In? 0.0 : 1.0; var end = fadeType == Fade.In? 1.0 : 0.0; var i = 0.0; var step = 1.0/timer; while (i <= 1.0) { i += step * Time.deltaTime; audio.volume = Mathf.Lerp(start, end, i); yield;
Debug.Log (audio.volume); //проверка срабатывания фейда логируется в консоль — можно удалить/отключить } } Вот, что у нас получилось:
Смотрим длину нашего трека, конвертируем её в секунды = DestroyTimeЕсли лень посчитать — можно использовать онлайн-конвертер минут (гугл в помощь) + добавить остаток секунд.Отнимаем продолжительность фейда = CloneTimeВписываем полученные значения в окошки.В перфаб добавляем нужный трек.Фейд по вкусу.
Вы должны проследить за тем, чтобы задержка до клонирования/продолжительность фейда/время удаленияне конфликтовали с временными параметрами скрипта из первой части статьи —иначе при переходе на следующий уровень трек может не успеть самоудалиться и начнёт создавать множество своих копий.…
И напоследок, если вам интересно, для чего это делается:
steamcommunity.com/sharedfiles/filedetails/? id=252305314
[embedded content]