Рефакторинг звукового движка, ч. 2 - XashNT
Из предыдущей части, думаю очевидно, что оставлять систему в таком состоянии было нельзя.
И дело даже не в наборе аргументов для вызова функции воспроизведения звука. Требовалось пересмотреть сами принципы, удалить устаревшие и ненужные параметры, а также добавить недостающие.
После рефакторинга функция воспроизведения звука стала выглядеть следующим образом:
// это HeadShot, не C++ bool PlaySound( const class *e, const string sample, float volume, float radius, int attachment, int pitch, uint flags, const string mouth );
Параметры
class *e - указатель на абстрактный класс
По-прежнему является указателем на объект, который будет эмиттером звука.
параметр номера канала
string sample
Кроме прямого пути к звуковому сэмплу или имени сентенции, также может принимать имя скриптового описания soundDef.
Это является естественным расширением скриптовой системы эффектов, которая создаёт облако частиц с заранее определённым поведением, временем жизни и траекторией полёта. Зачастую, при воспроизведении того или иного эффекта, требовалось создать несколько короткоживущих систем частиц и проигрывать случайный звук из набора. Теперь звуки, объявленные в этих скриптовых файлах, можно использовать отдельно.
Пример такого звукового скрипта:
soundDef "ricochet_sound" { sound "weapons/ric1.wav" sound "weapons/ric2.wav" sound "weapons/ric3.wav" sound "weapons/ric4.wav" sound "weapons/ric5.wav" volume "0.7f..0.9f" radius "1000.0" pitch "90..105" }
Чтобы функция PlaySound могла отличить скрипт от обычного файла, его имя должно начинаться со знака доллара $, по аналогии с сентенциями, которым требуется восклицательный знак.
float volume - громкость
Никаких изменений не претерпел, всё осталось по-прежнему.
float radius - радиус слышимости
А вот параметр radius как раз и появился по итогам анализа недостатков старой системы, рассмотренной в первой части статьи. Теперь мы явным образом задаём радиус, в котором звук будет гарантированно слышен (с плавным затуханием по мере удаления от центра) и при условии, что он не перекрыт геометрией.
int attachment
Это уже уникальное нововведение, позволяющее в качестве эмиттера определить не просто центр объекта, а выбрать на нём определённую точку, откуда и будет идти звук.
Конечно, если модель NPC у нас размером с игрока, то это не имеет никакого значения. Но уже в первом Half-Life, как вы помните, были такие громадные монстры как тентакли. Они стучали своими клювами по стенам, порождая резкие металлические звуки. При этом центр модели находился далеко внизу и нормального выхода из положения не было.
В GoldSource приходилось прямо на сервере рассчитывать положение аттачмента и проигрывать звук из точки, в которой аттачмент находился на тот момент, то есть эти звуки не следовали за их источником.
В принципе, учитывая, что звук проигрывался в точке соприкосновения клюва монстра со стеной это не было нужно, но представьте себе ситуацию, когда монстр - говорящий. И что если у него несколько голов? Так что ситуация не надуманная.
int pitch
uint flags
Больше не завязан на механизмы сетевого протокола отправки сообщений воспроизведения звука на клиент, поэтому остался один необязательный флаг - SND_IGNORE_LOOP. Он запрещает зацикливать звуковые сэмплы, имеющие соответствующие метки. Это редко, но иногда всё же требуется.
Для того, чтобы просто изменить текущую громкость и тон, достаточно вызвать проигрывание звука ещё раз с тем же указателем на объект, тем же номером аттачмента и тем же звуковым сэмплом. Этих данных достаточно, чтобы найти целевой звук и применить к нему изменения.
const string mouth - последний параметр
В GoldSource липсинк имеет две жестких привязки – во-первых, как я уже упоминал в первой части, звук должен иметь исходный канал CHAN_VOICE (или CHAN_STREAM для игрока, говорящего в микрофон), а во-вторых, сам аниматор рта должен быть жестко привязан к контроллеру кости с определённым номером (в случае первого Half-life – номер 4). Но, как было показано выше в примере с аттачментом, что, если NPC имеет несколько говорящих голов? Именно эта возможность здесь и реализуется. Теперь вы можете задавать разные контроллеры рта для одного и того же NPC.
Другими словами, XashNT принимает имя кости рта mouth, находит номер кости по имени, после чего многократно меняет положение кости передавая в метод анимационного контроллера кость и текущее среднее значение проигрываемого сэмпла. Вследствие этого, рот NPC открывается и закрывается.
Контроль воспроизведения
В XashNT появился механизм для контроля над воспроизведением музыкальных треков. Он содержит следующие функции:
bool StopSound( const class *e, const string sample = "", int attachment = 0, uint flags = 0 );
Принцип такой же, как и при вызове PlaySound с целью изменить громкость или тон уже воспроизводимого звука - вы должны указать объект, играющий звук, имя сэмпла и номер аттачмента.
Параметр с флагами на данный момент не используется и добавлен из расчёта на будущие расширения.
Опционально вы можете не указывать сэмпл, тогда все звуки для данного объекта будут остановлены. Это может быть полезным, например, при удалении объекта с уровня. Впрочем, звуковые каналы умеют самостоятельно определять, что звук был удалён, так что в норме беспокоиться об этом не следует.
bool PlayBackgroundTrack( const string introName = "", const string loopName = "" );
Первый аргумент указывает путь к вступительному треку, а второй - к зацикленному.
Используя всё тот же управляющий знак $, вы можете указывать имя скрипта musicDef, который содержит дополнительные настройки для управления фоновой музыкой (на данный момент, только рандомизацию вступительного и зацикленного треков).
bool StopBackgroundTrack( float fadeOutTimeInSeconds = 0.0f );
Теперь количество музыкальных треков не ограничено одним, и система запоминает фоновую музыку в меню и в игре.
При вызове мягкой остановки (т.е. с постепенным уменьшением громкости до нуля) можно сразу запускать новый трек – он начнёт играть уже во время затихания предыдущего.
Таким образом реализуется бесшовная смена композиций. Подобная функция есть в MP3-плеерах, но далеко не во всех в игровых движках.
DSP-эффекты
Система DSP-эффектов на данный момент находится в разработке, создан лишь сам менеджер эффектов, позволяющий динамически применять их к каналам.
Прежнее многообразие эффектов из GoldSource убрано, добавлено только два экспериментальных эффекта:
- Lowpass-фильтр, то есть фильтр нижних частот (ФНЧ).
Этот фильтр уже активно используется звуковой подсистемой при расчёте вклада в звучание фактора абсорбции звуковой волны воздушным пространством, окклюзии звуков сквозь систему порталов и просто статичной геометрии. - Reverberation - задержка, повторение звуков.
В дальнейшем будет использован для особых зон на уровне, определённых автоматически или же указанных пользователем, как достаточно большие по размерам, чтобы в них могло возникнуть эхо.
Количество фильтров на звуковой канал не лимитировано, равно как и их набор для каждого канала.
В дальнейшем будут добавлены новые фильтры с возможностью пользовательского контроля. Вероятно, добавится возможность создания собственных фильтров или, как минимум, определения их набора.
На данный момент это не реализовано, поскольку рефакторинг получился масштабный. Написанный код должен пройти проверку временем, реальными игровыми ситуациями и вычищен от потенциальных ошибок.
Ну и последний момент, который возможно неочевиден - у проигрываемых звуков и музыки, разумеется, осталась поддержка сериализации. При загрузке сохраненной игры звуки и музыка продолжают играть с того момента, на котором они находились в момент сохранения.
© Seamless Realm www.seamless-realm.ru