Пользователь
0,0
рейтинг
8 ноября 2014 в 18:05

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

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> game_event = new List<Game_event>(); //список событий

void Update () {
	sound_time = sound.time;
	//sound - объект типа AudioSource, содержащий проигрываемую музыку
	
	foreach (Game_event e in game_event) {
		if (sound_time>=e._time && sound_time<e.finish_time && !e.active)
		{
			e.active = true;
			e.finish_time = float.MaxValue;
			current_event =e;
			e.Create();
		} 
			
		if (e.active)
			if (e.isFinish()) 	//функция isFinish может быть ресурсоёмкой, потому, прежде проверяется активность события
			{
				e.active=false;
				e.finish_time = sound_time;
				e.Destroy();
			}
	}
}


Есть несколько вариантов создания различных событий: путем перечисления непосредственно в коде Game_event, создание дополнительных классов, либо использование скриптового языка вроде Lua, что конечно удобнее.



Редактор



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

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

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<SoundTex.width; i++) {
	int c_hi = 0;
	int c_low = 0;
	float s_hi = 0;
	float s_low = 0;

	//Подсчитываем среднее нижнее и среднее верхнее значение на 1px текстуры
	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 (); //Применяем изменение к текстуре
//Результат можно посмотреть на заглавной картинке


Посмотреть, как все работает в действии можно в этом видео:
@AlexKrysin
карма
32,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (13)

  • 0
    Мне вспомнилась очень клевая игра Duckstazy, где окружающий мир и музыка менялась зависимости от упоротости уточки. :)
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      В названии и тегах написано Unity3d.
  • +4
    Круто! Но обязательно поставьте предупреждение для эпилептиков в вашей игре!
    • 0
      Видимо, с этим я действительно перестарался… Впрочем, испытания на людях должны устранить эту проблему. Ну а если не получиться, конечно, поставлю предупреждение.
  • +3
    А можно на игру посмотреть то?
    • 0
      Игра, пока, на ранней стадии развития. Как только она достаточно вырастет, обязательно напишу полноценную статью про нее, возможно не на сам хабр, но на geektimes точно. Впрочем, в описании ролика, есть несколько статей чуть более подробно раскрывающих идею игры.
  • +2
    Простите, но я не смог просмотреть ролик даже до середины… Судя по всему, Вы решили повертеть мышкой… бесконечно. Скажу честно, у меня с вестибулярным аппаратом все отлично, но тут укачало.
    • 0
      Странно, потому что у самого, напротив, с этим проблемы. Из-за чего не могу долго играть в игры от первого лица. Можно, уточнить качество в котором вы смотрели? Возможно это связано с фреймрейтом.
  • 0
    Не до конца уловил суть геймплея, но задумка вроде бы неплохая. Советую Вам посмотреть на игру AudioSurf для поиска вдохновения, так сказать :)
  • 0
    Это, конечно, круто. Но интересно было бы разработать автоматический генератор уровней по музыке, как в том же AudioSurf. Тоже немного экспериментировал в этом направлении.
    • +1
      Такая идея была изначально, но я ее быстро отбросил, так как реализовать действительно хороший алгоритм, который воспринимает музыку так же как мозг, весьма сложно, а то, как это реализовано в AudioSurf, лично мне не нравится. Выглядит и играется это далеко не всегда органично, особенно если мелодия достаточно сложная.
      • 0
        Как вариант — расставить акценты по треку вручную и скормить алгоритму генерации уровня…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.