Звуковые эффекты До недавнего времени работа со звуком в играх не отличалась особой легкостью. В том же DirectX SDK на организацию звукового движка уходило очень много времени, а с появлением Xbox 360 программирование звука в играх на базе DirectX SDK стало и вовсе невозможным. В связи с этим для платформы XNA был создан новый компонент под названием Microsoft Cross-Platform Audio Creation Tool, или просто XACT. Это кросс-платформенный механизм, позволяющий создавать отдельные звуковые проекты, которые впоследствии вы можете встроить в свою игру как для ПК, так и для Xbox 360, затратив при этом минимум усилий и времени. Все проекты с использованием XACT создаются по одному принципу. Вам необходимо произвести стандартный набор операций – и у вас на руках будет готовый звуковой проект. В этой главе вы научитесь создавать XACT-проекты и интегрировать их в свои программы. Что касается нашей игры, то мы добавим в программу два звуковых эффекта. Один звуковой эффект будет проигрываться в момент касания ковра-самолета с падающим объектом, а второй звуковой эффект будет воспроизводиться, когда объект упадет в пропасть. Сейчас главное – разобраться с общим принципом работы проектов XACT, а затем вы сможете интегрировать в свои игры любое количество звуковых эффектов или отдельных мелодий. Начнем с того, что рассмотрим технику и механику создания проектов на базе Microsoft Cross-Platform Audio Creation Tool.
13.1. Создаем проект XACT Прежде чем начинать создавать проект XACT, необходимо подготовить все звуковые файлы, которые вы собираетесь использовать в игре. Причем отнеситесь к этому делу ответственно, поскольку заменить один или несколько не понравившихся вам звуковых файлов в проекте простой операцией удалить/вставить не получится. Придется заново создавать новый проект XACT, а старый проект – удалять из рабочего каталога создаваемой программы или обновлять старый проект XACT, благо делается это легко и достаточно быстро. Алгоритм действий по созданию проекта XACT и добавления его в программу следующий. Вы создаете определенное количество звуковых файлов и явно добавляете их в свой проект. Затем с помощью программы Microsoft Cross-Platform Audio Creation Tool создается XACT-проект, в котором выполняется ряд простых действий (пара-тройка кликов кнопкой мыши) для присоединения к XACT-проекту звуковых файлов. Когда все операции выполнены, нужно просто сохранить проект XACT в рабочем каталоге вашего проекта в том месте, где хранятся звуковые файлы. После этого начинается работа над исходным кодом проекта, в котором вам необходимо описать ряд стандартных действий для воспроизведения звуковых эффектов в игре. Программа Microsoft Cross-Platform Audio Creation Tool понимает три формата: WAV, AIF и AIFF. Самый распространенный и часто используемый в играх – это, конечно, WAV-формат. Кстати, очень много начинающих программистов склоняются к формату MP3, но на самом деле не учитывают один недостаток этого формата. Если вы не в курсе, то знайте, за использование формата MP3 в играх необходимо платить, то есть приобретать соответствующую лицензию, которая стоит приличных денег. Поэтому бесплатный WAV-формат, по крайней мере для начинающих программистов, смотрится проще, да и всегда можно преобразовать звуковые файлы из формата MP3 в формат WAV. Для игры я использовал два звуковых файла в формате WAV, которые были взяты из проекта Spacewar Xbox 360 Starter Kit, идущего в составе инструментария XNA Game Studio Express. Файлы были подобраны (насколько это было возможно) под игру и переименованы в boom.wav и platform.wav. Первый файл boom.wav мы будем воспроизводить, когда пользователь не сможет поймать падающий объект и он выйдет за пределы экрана. Второй файл используется при столкновении ковра и объекта. На самом деле в коммерческих проектах необходимо очень ответственно подходить к созданию звуковой дорожки игры. Лучше всего для этих целей привлекать хороших аранжировщиков, которые действительно могут без видимых усилий сыграть на слух любую мелодию. Таких предложений в том же Интернете очень много, и цена на эту работу может быть различной, вплоть до того, что вы сможете найти единомышленника, желающего вписать свое имя в историю создаваемой игры. Соответствующая информация находится в разделе Работа для читателей. Предложение также действительно для начинающих модельеров, художников, дизайнеров… Возвращаемся на землю и переходим к созданию XACT проекта, который изучим в пошаговом режиме.
13.1.1. Пошаговая инструкция 1. Создайте в каталоге рабочего проекта Audio (новый проект этой главы) папку с названием Sound (команды Add -> New Folder). Эту папку лучше всего переместить или изначально создать как вложенную в папке Content (рис. 13.1).
Рис. 13.1. Создание в каталоге проекта новой папки Sound
2. В дальнейшем в эту папку мы добаим два звуковых файла, редназначенных для воспроиз-ведения в игре. Звуковые файлы могут находиться где угодно, поскольку их добавление в проект, как вы помните, должно происходить явно. Для добавления в проект звуковых файлов в панели Solution Explorer щелкните правой кнопкой мыши на названии папки Sound и в контекстном меню изберите команды Add -> Exiting Item. Откроется диалоговое окно Add Exiting Item. В этом окне в списке Files of Types нужно выбрать тип Audio files и найти на вашем компьютере звуковые файлы. В нашей игре в качестве таких файлов выступают WAV-файлы с названиями boom и platform. После этой операции в папку Sound добавятся два этих файла (рис. 13.2).
Рис. 13.2. Добавление звуковых файлов в проект
Всегда сначала явно добавляйте звуковые файлы в свой проект и только потом приступайте к созданию проекта XACT, который, в свою очередь, берет эти звуковые файлы из папки вашего проекта!
3. Сейчас можно на некоторое время отложить в сторону работу с Visual C# Express и перейти к созданию XACT-проекта. Инструментарий Visual C# Express можно оставить открытым, а можно закрыть, существенной разницы в этом нет. Открываем на компьютере программу Microsoft Cross-Platform Audio Creation Tool, которая поставляется вместе с инструментарием XNA Game Studio Express. Для этого выполните на компьютере команды Пуск -> Все программы -> Microsoft XNA Game Studio Express -> Tools -> Microsoft Cross_Platform Audio Creation Tool. Откроется рабочее окно программы, изображенное на рис. 13.3. Приступим к созданию нового проекта.
4. Поочередно в рабочем окне программы выполните следующие команды. Сначала в линейке меню изберите команды Wave Banks -> New Wave Bank, а затем команды Sound Banks -> New Sound Bank. Итогом этих операций станет открытие двух пустых окон в рабочей области Microsoft Cross-Platform Audio Creation Tool с одноименными названиями выполняемых команд (рис. 13.4). Эти два пустых файла, входящие в один проект, позволяют в пару кликов мыши конвертировать ваши звуковые данные для XACT-проекта.
5. Нажмите правой кнопкой мыши на пустом месте файла Wave Bank. Появится контекстное меню, где необходимо избрать команду Insert Wave File(s), как показано на рис. 13.5. Выполнение этой команды откроет простое диалоговое окно выбора файлов. Проследуйте в этом окне в каталог вашего рабочего проекта в папку Sound и добавьте два звуковых файла – boom и platform. Оба этих файла моментально добавятся в файл Wave Bank (рис. 13.6). Этой операцией вы присоединили два звуковых файла к проекту XACT. Всегда сначала явно добавляйте звуковые файлы в каталог своего проекта и только потом уже из этого каталога добавляйте в Wave Bank все звуковые файлы, иначе в дальнейшем у вас может возникать масса непонятных ошибок во время компиляции и сборки проекта!
Рис. 13.3. Рабочее окно Microsoft Cross/Platform Audio Creation Tool
Рис. 13.4. Создание двух пустых файлов Wave Bank и Sound Bank
Рис. 13.5. Команда добавления звуковых данных
6. Затем выделите курсором мыши два добавленных файла в Wave Bank и перетащите файлы в окно Sound Bank, непосредственно в часть окна под названием Cue Name (рис. 13.7). После перетаскивания файлов их названия появятся в Cue Name. Клик мыши на любом из файлов откроет более подробную информацию о звуковом файле во всех текстовых частях окна Sound Bank. Убедитесь, что все добавленные файлы при клике на них мышью отображают свои свойства во всех текстовых частях окна Sound Bank. Если какой-то из файлов не представлен в текстовых областях окна Sound Bank, то нужно обязательно удалить этот файл из окна Sound Bank (щелчок правой кнопкой мыши на названии файла и выбор команды Delete). Затем необходимо вновь выполнить операцию по перетаскиванию данного файла из окна Wave Bank в окно Sound Bank. Но обычно все работает с первого раза.
7. Последний штрих к созданию кросс-платформенного проекта XACT – это сохранение всего того, что вы сейчас имеете, в открытом окне программы Microsoft Cross-Platform Audio Creation Tool. Выберите в меню команды File -> Save Project As и в появившемся диалоговом окне Save Project As
Рис. 13.6. Добавление звуковых файлов в Wave Bank
(рис. 13.8) проследуйте в каталог вашего проекта в папку Sound. В поле этого окна Имя файла задайте любое название проекту и нажмите кнопку Сохранить. Всегда сохраняйте проект XACT в той папке вашего проекта, где хранятся звуковые файлы! На этом все, вы создали и сохранили проект XACT в рабочем каталоге вашего проекта (рис. 13.9), и теперь можно спокойно приступать к работе над исходным кодом программы. Итогом всех этих незамысловатых действий стало создание проектного файла Sound.xap. Что нам это дало? В момент компиляции проекта в работу вступит Content Pipeline, который своими внутренними сервисами на основе проектного файла Sound.xap произведет преобразование всех звуковых файлов проекта в три специфических файла Wave Bank.xwb, Sound Bank.xsb и Sound.xgs. Именно из этих трех файлов в дальнейшем будет происходить воспроизведение звука в игре. То есть после того как вы откомпилируете проект, в рабочем каталоге проекта в папках Debug или Release -> Content-> Sound появятся новые файлы Wave Bank.xwb, Sound Bank.xsb и Sound.xgs и вся работа программы со звуковыми данными будет происходить именно с этими файлами.
Рис. 13.7. Перетаскиваем файлы в окно Sound Bank
13.2. Класс Sound Для работы со звуковыми данными в игре лучше создать отдельный класс, который вы всегда можете со временем усовершенствовать, а также использовать в любых других своих проектах. Так поступим и мы, но свой класс писать не будем, а возьмем уже готовое решение, поставляемое в комплекте с Spacewar Xbox 360 Starter Kit. В частности, нас интересует класс Sound, который реализован достаточно просто и элегантно. Немного переделав его под свой проект, мы получаем функциональный класс. Давайте посмотрим сначала на весь исходный код класса Sound, представленный в листинге 13.1, а затем перейдем к его анализу.
Рис. 13.8. Сохранение проекта XACT в каталоге создаваемого приложения
Рис. 13.9. Рабочий каталог проекта с добавленными в него звуковыми файлами и проектом XACT
//========================================================================= #region Dependencies using System; using System.Collections.Generic; using System.Text; using System.IO; using Microsoft.Xna.Framework.Audio; #endregion namespace Audio { public enum soundList { /// <summary>
/// Прикосновение к ковру-самолету /// </summary> Platform, /// <summary> /// Падение в пропасть /// </summary> Boom, } public static class Sound { private static AudioEngine engine; private static SoundBank soundbank; private static WaveBank wavebank; /// <summary> /// Инициализация /// </summary> public static void Initialize() { engine = new AudioEngine(«Content\\Sound\\Sound.xgs»); soundbank =new SoundBank(engine, «Content\\Sound\\Sound Bank.xsb»); wavebank = new WaveBank(engine, «Content\\Sound\\Wave Bank.xwb»); } private static string[] cueNames = new string[] { «platform», «boom», }; /// <summary> /// Воспроизведение звука после остановки /// </summary> public static CuePlay(soundList sound) { Cue returnValue = soundbank.GetCue(cueNames[(int)sound]); returnValue.Play(); return returnValue; } /// <summary> /// Воспроизведение /// </summary> public static void PlayCue(soundList sound) { soundbank.PlayCue(cueNames[(int)sound]); } /// <summary> /// Обновляем звук /// </summary> public static void Update() {
Ключевыми фигурами в звуковых проектах, основанных на использовании XACT, являются три класса: AudioEngine, SoundBank и WaveBank. Объявление и создание объектов этих классов, а также загрузка звука в игру всегда происходят в одном и том же ключе. Посмотрите на нижеприведенные строки исходного кода.
private static AudioEngine engine; private static SoundBank soundbank; private static WaveBank wavebank; … public static void Initialize() { engine = new AudioEngine(«Content\\Sound\\Sound.xgs»); soundbank = new SoundBank(engine, «Content\\Sound\\Sound Bank.xsb»); wavebank = new WaveBank(engine, «Content\\Sound\\Wave Bank.xwb»); }
В трех первых строках этого блока кода происходит объявление трех объектов. Затем создание или инициализация объектов в данном случае делается в методе Initialize(), но это можно сделать в любом месте на этапе загрузки игры. Создание выше перечисленных объектов всегда происходит именно таким способом, как показано в коде. Это их специфика, или черный ящик, как хотите. Главное, что нужно четко понимать, – это какие пути прописывать в параметрах конструкторов классов AudioEngine, SoundBank и WaveBank. Как вы помните, все звуковые файлы на основе проектных данных файла XACT (Sound.xap), добавленные в рабочий каталог проекта, в момент компиляции будут преобразованы к файлам с названиями Wave Bank.xwb, Sound Bank.xsb и Sound.xgs. И именно к этим файлам вам необходимо прописывать путь в параметрах конструкторов классов AudioEngine, SoundBank и WaveBank. Если у вас после компиляции появляются ошибки, связанные с тем, что программа не может найти файлы Wave Bank.xwb, Sound Bank.xsb и Sound.xgs, то вы точно указали неверно либо путь к файлам, либо названия самих файлов. В этом случае после компиляции откройте рабочий каталог проекта и посмотрите, где у вас точно располагаются эти файлы, какое название они имеют, и, что немаловажно, обратите внимание на расширение файлов, у всех файлов оно разное!
Эта стандартная процедура по созданию объектов классов AudioEngine, SoundBank и WaveBank подгружает в игру имеющиеся звуковые данные. Теперь, чтобы воспроизвести любой файл, необходимо просто вызвать метод PlayCue(), в параметрах которого указать название файла для воспроизведения.
soundbank.PlayCue(“boom”);
В классе Sound реализация воспроизведения звуковых файлов несколько отличается от того, что я вам показал, но отличается в лучшую сторону. Смысл здесь следующий. Вместо того чтобы в методе PlayCue() явно прописывать название воспроизводимого файла, используется более элегантная конструкция, состоящая из структуры soundList и массива данных cueNames.
public enum soundList { /// <summary> /// Прикосновение к ковру-самолету /// </summary> Platform, /// <summary> /// Падение в пропасть /// </summary> Boom, } …. private static string[] cueNames = new string[] { «platform», «boom», };
Любые элементы структуры или массива данных всегда ведут свое числовое исчисление от нуля. В структуре soundList присутствуют два названия: Platform и Boom. Для читабельности исходного кода и дальнейшего использования звуковых данных такая запись просто прекрасна, но на самом деле в любой структуре первый элемент (Platform) всегда равен нулю, второй элемент (Boom) – единице и т. д. Аналогичные явления присутствуют и в массиве данных, поэтому если взять метод PlayCue() класса Sound, то получится, что обращение к элементам структуры идет по имени.
public static void PlayCue(soundList sound) { soundbank.PlayCue(cueNames[(int)sound]); }
В классе Sound, кроме метода PlayCue(), есть еще три метода. Первый метод Stop() позволяет останавливать воспроизведение определенного звукового файла.
В свою очередь, второй метод CuePlay() позволяет воспроизводить файл после его остановки. В параметре этого метода необходимо просто передать название структурной переменной, которая отвечает за один из звуковых файлов.
/// Воспроизведение звука после остановки /// </summary> public static CuePlay(soundList sound) { Cue returnValue = soundbank.GetCue(cueNames[(int)sound]); returnValue.Play(); return returnValue; }
Последний и очень важный метод занимается тем, что постоянно обновляет работу звукового движка. Вызов этого метода в дальнейшем должен производиться непосредственно в игровом цикле в методе Update(). Сейчас давайте перейдем к классу Game1 и добавим звуковые эффекты в игру.
13.3. Воспроизведение звука в игре Посмотрите на исходный код класса Game1 проекта Audio, приведенный в листинге 13.2. Сначала давайте посмотрим исходный код примера, а затем перейдем к его анализу. В некоторых больших методах этого класса исходный код был вырезан, поскольку не изменился с предыдущего проекта. Полный код проекта Audio вы найдете на диске в папке Code\Chapter13\Audio.
//========================================================================= /// <summary> /// Листинг 13.2 /// Исходный код к книге: /// «Программирование игр для приставки Xbox 360 в XNA Game Studio Express» /// Глава 13 /// Проект: Audio /// Класс: Game1 /// Звуковые эффекты /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace MenuCursor { public class Game1 : Microsoft.Xna.Framework.Game { … /// <summary> /// Конструктор /// <summary> public Game1() { …. } /// <summary> /// Инициализация /// protected override void Initialize() { Sound.Initialize(); base.Initialize(); } /// <summary> /// Загрузка компонентов игры /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { … } /// <summary> /// Освобождаем ресурсы /// protected override void UnloadGraphicsContent(bool unloadAllContent) { … } /// <summary> /// Обновляем состояние игры
/// <summary> protected override void Update(GameTime gameTime) { … Sound.Update(); base.Update(gameTime); } /// <summary> /// Движение спрайта по вертикали /// <summary> public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite.spritePosition += sprite.speedSprite; if (sprite.spritePosition.Y > screenHeight) { sprite.spritePosition = new Vector2(rand.Next(50, screenWidth - sprite.spriteTexture.Width / 12 - 50), -500); Sound.PlayCue(soundList.Boom); } } } /// <summary> /// Движение платформы по экрану /// <summary> public void MovePlatform() { … } /// <summary> /// Пауза в игре /// <summary> public void Pause() { … } /// <summary> /// Столкновения /// <summary> public void Collisions() { bbplatform.Min = new Vector3(platform.spritePosition.X, platform.spritePosition.Y + 45, 0); bbplatform.Max = new Vector3(platform.spritePosition.X + platform.spriteTexture.Width, platform.spritePosition.Y + 45 + 2, 0); for (int i = 0; bb.Length > i; i++) {
bb.Min = new Vector3(sprite.spritePosition.X, sprite.spritePosition.Y, 0); bb.Max = new Vector3(sprite.spritePosition.X + sprite.spriteTexture.Width / 12, sprite.spritePosition.Y + sprite.spriteTexture.Height, 0); } if (bbplatform.Intersects(bb[0])) { sprite[0].spritePosition = new Vector2(rand.Next(50, screenWidth - sprite[0].spriteTexture.Width / 12 - 50), -500); score0 += 1; Sound.PlayCue(soundList.Platform); } if (bbplatform.Intersects(bb[1])) { sprite[1].spritePosition = new Vector2(rand.Next(50, screenWidth - sprite[1].spriteTexture.Width / 12 - 50), -500); score1 += 1; Sound.PlayCue(soundList.Platform); } if (bbplatform.Intersects(bb[2])) { sprite[2].spritePosition = new Vector2(rand.Next(50, screenWidth - sprite[2].spriteTexture.Width / 12 - 50), -500); score2 += 1; Sound.PlayCue(soundList.Platform); } if (bbplatform.Intersects(bb[3])) { sprite[3].spritePosition = new Vector2(rand.Next(50, screenWidth - sprite[3].spriteTexture.Width / 12 - 50), -500); score3 += 1; Sound.PlayCue(soundList.Platform); } if (bbplatform.Intersects(bb[4])) { sprite[4].spritePosition = new Vector2(rand.Next(50, screenWidth - sprite[4].spriteTexture.Width / 12 - 50), -500); score4 += 1; Sound.PlayCue(soundList.Platform); } } /// <summary> /// Новая игра /// <summary> public void NewGame() { …
} /// <summary> /// Рисуем на экране /// <summary> protected override void Draw(GameTime gameTime) { … } } Первым делом в классе Game1 необходимо воспользоваться методом Sound.Initialize() и создать объекты AudioEngine, SoundBank и WaveBank. protected override void Initialize() { Sound.Initialize(); base.Initialize(); }
Затем мы переходим к игровому циклу и вызываем в нем метод Sound. Update(). Этот метод, как вы помните, обновляет работу звукового движка. На этом все подготовительные действия выполнены, и можно заняться воспроизведением звука в игре или использовать любые другие методы класса Sound. У нас в игре имеются два звуковых файла, которые определены для событий, связанных со столкновением платформы и объектов, а также падением объектов в пропасть. Соответственно и вызовы метода Sound.PlayCue() происходят в методах, обрабатывающих столкновения с ковром-самолетом и окончанием экрана. Здесь все просто. Придерживаясь в дальнейшем аналогичной схемы работы со звуковыми данными, можно добавить в игру любое количество звуковых эффектов или больших звуковых дорожек. Техника воспроизведения всех звуковых файлов, а также создание проектов XACT для Windows и Xbox 360 одинаковы.
13.4. Цикличное воспроизведение музыки В своих играх вам обязательно захочется воспроизводить одну мелодию или более в цикличном режиме. В XNA Game Studio Express такая возможность, несомненно, имеется. При этом цикличное воспроизведение одного из файлов задается еще на этапе формирования проекта XACT. Посмотрите на рис. 13.7, где представлен этап добавления в проект XACT новых звуковых файлов. Так вот, если в окне Sound Bank выделить курсором название файла, а затем в правой части этого окна выделить ветку структуры древовидной иерархии с названием Play Wave (рис. 13.10), то в левой части главного окна программы Microsoft Cross-Platform Audio Creation Tool откроется дополнительная панель со свойствами выбранного звукового файла.
В перечислениях свойств файла имеется параметр LoopEvent. В данный момент значение этого параметра выставлено в No, что означает: цикличное воспроизведение для этого файла не задано (рис. 13.11). Для того чтобы установить цикличность для этого параметра, достаточно выбрать из списка значение Infinite и продолжать работу с проектом по рассмотренной в этой главе схеме. Затем когда вы запустите проект, то для того файла, для которого назначили цикличное проигрывание, программа автоматически выберет заданный режим воспроизведения.