...

вторник, 20 января 2015 г.

[Из песочницы] 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.


image


Берём трек (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

Вписываем полученные значения в окошки.

В перфаб добавляем нужный трек.

Фейд по вкусу.


Вы должны проследить за тем, чтобы задержка до клонирования/продолжительность фейда/время удаления

не конфликтовали с временными параметрами скрипта из первой части статьи

иначе при переходе на следующий уровень трек может не успеть самоудалиться и начнёт создавать множество своих копий.


И напоследок, если вам интересно, для чего это делается:


http://ift.tt/1yGD1yc



Recommended article: Chomsky: We Are All – Fill in the Blank.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий