Синхронизация музыки и игровых событий на Unity

imageПример редактора уровня в игре.Если вы когда либо играли в игры типа Guitar Hero, Osu или Bit Trip Runner вы знаете, как сильно погружает в «поток» простая зависимость геймплея от музыки играющей на фоне. Удивительно, что таких игр, на самом деле не так уж и много. Кроме того, такая синхронизация может быть полезна для создания спецэффектов, но тем не менее почти нигде не встречается, кроме обозначенных выше игр типа rhythm. Вот и я решил воспользоваться таким бесхитростным приемом в собственной игре, а также поделиться наработками.

Я постарался описать конструкцию, удобную как для программирования большого количества игровых событий, так и работающую как можно более оптимальным образом. Применить которую можно почти в любой игре, возможно и вам она пригодится, если вы занимаетесь их разработкой.

Итак, для начала нужно определить класс-событие:

[Serializable] public class Game_event { public char key; //В зависимости от ключа будет происходить то или иное событие public float time; //Момент старта события [NonSerialized]public float finish_time; //Необходим, чтобы событие не было создано повторно после завершения

public bool isFinish (){ //Функция, проверяющая завершение события return false; }

public void Create (){ //Создаем необходимые для события объекты //Важно, что бы все движения объектов зависели от (Main.sound_time — time) }

public void Destroy (){ //удаляем их }

public Game_event (float time, char key){ this.time = time; this.key = key; } } Далее, потребуется класс наследованный от MonoBehaviour, в котором будет основной код и, конечно, ссылка на звуковой объект. В моем случае это класс Main.

public static float sound_time=0; //глобальная переменная, в которой будет храниться текущее время проигрываемого звука public static List game_event = new List(); //список событий

void Update () { sound_time = sound.time; //sound — объект типа AudioSource, содержащий проигрываемую музыку foreach (Game_event e in game_event) { if (sound_time>=e._time && sound_time

Наиболее удобный способ редактирования, на мой взгляд, привязка определенных событий к клавишам, тогда создание уровня превращается в «игру на пианино», где ваша задача лишь нажимать клавиши в ритм музыке. Именно поэтому в качестве ключа используются соответствующие символы.

Для реализации, потребуется определить доступные для ввода клавиши:

public static char[] keys_s = { 'Q','W','E','R','T', 'A','S','D','F','G', 'Z','X','C','V','B'};

//И добавить следующий код void Update () { …

Event c_e = Event.current; if (c_e.isKey && c_e.type == EventType.KeyDown) { if (Array.Exists (Main.keys_s, c=>c==c_e.keyCode.ToString ()[0])) // Проверяем, существует ли нажатая клавиша в массиве допустимых { game_event.Add (new Game_event (sound_time, c_e.keyCode.ToString ()[0])); } } } Теперь в момент нажатия клавиши, в список запишется соответствующее событие, которое будет проигрываться синхронно звуку.

Может быть очень удобно, подстраивать события под рисунок звуковой волны. Получить текстуру с ее изображением можно следующим способом:

float[] samples = new float[sound.clip.samples * sound.clip.channels]; sound.clip.GetData (samples, 0); //Получаем массив с данными сэмпла по которому будет строиться текстура int frequency = sound.clip.frequency; //битрейт сэмпла int scale = 10; //пикселей на 1с сэмпла

SoundTex = new Texture2D ((int)(sound.clip.length*sound.clip.channels*scale), 200); int height = (int)(SoundTex.height / 2); for (int i=0; i

//Подсчитываем среднее нижнее и среднее верхнее значение на 1 px текстуры for (int k=0; k<(int)(frequency/scale); k++) { if (samples[k+i*(int)(frequency/scale)]>=0) { c_hi++; s_hi+=samples[k+i*(int)(frequency/scale)]; } else { c_low++; s_low+=samples[k+i*(int)(frequency/scale)]; } } //Рисуем линию от среднего нижнего до среднего верхнего //Поделена она на более светлую внутреннюю и более темную верхнюю часть, исключительно для красоты for (int j=0; j<(int)(SoundTex.height); j++) { if (j<(int)((s_hi/c_hi)*height*0.6f+height) && j>(int)((s_low/c_low)*height*0.6f+height)) SoundTex.SetPixel (i, j, new Color (0.7f,1,0.7f)); else if (j<=(int)((s_hi/c_hi)*height+height) && j>=(int)((s_low/c_low)*height+height)) SoundTex.SetPixel (i, j, new Color (0,1,0)); else SoundTex.SetPixel (i, j, new Color (0,0,0,0)); } }

SoundTex.Apply (); //Применяем изменение к текстуре //Результат можно посмотреть на заглавной картинке Посмотреть, как все работает в действии можно в этом видео:[embedded content]

© Habrahabr.ru