netlib.narod.ru | < Назад | Оглавление | Далее > |
Теперь в вашем распоряжении достаточно знаний, чтобы вы смогли добавить звук в свои игры. Но в своем нынешнем состоянии код недостаточно гибок и его сложно интегрировать в проекты. Для того, чтобы упростить интеграцию и использование кода необходим класс объекта звуковой системы. В этом разделе я покажу вам как создать класс звуковой системы, выполняющий следующие действия:
Сперва я опишу структуру класса звуковой системы, которая показана на рис. 7.8.
Рис. 7.8. Структура класса звуковой системы
Обратите внимание, что на рисунку показаны три главных метода класса и два главных члена данных. В классе также есть обычные конструктор и деструктор, но на рисунке они не показаны, поскольку присутствуют в любом классе.
Три метода называются hrInitSoundSystem(), hrLoadSound() и hrPlaySound(). Достаточно прямолинейно, правда? Функция инициализации вызывается один раз для каждого экземпляра игры. Поскольку у вас должен быть только один экземпляр, это означает, что вы один раз вызываете функцию и она делает всю необходимую работу. Функция загрузки звука должна вызываться один раз для каждого звукового файла. Нет никакой необходимости загружать один и тот же звук несколько раз, если только вы действительно не хотите этого по каким-то причинам. Функция воспроизведения звука может и, возможно, будет, вызываться несколько раз для одного и того же звука. Нет никаких ограничений того, сколько раз можно воспроизводить звук.
Два основных члена данных, m_pLoader (IDirectMusicLoader8) и m_pPerformance (IDirectMusicPerformance8), предоставляют классу необходимые интерфейсы объекта. Как вы, возможно, помните, загрузчик отвечает за загрузку звуковых файлов, а объект исполнителя осуществляет воспроизведение звука.
Итак, вы познакомились со структурой класса; теперь пришло время посмотреть на реальный код. А вот и он во всей славе:
class SoundSystem { private: public: HWND m_hWnd; // Звуковая система IDirectMusicLoader8 *m_pLoader; IDirectMusicPerformance8 *m_pPerformance; // Функции SoundSystem(); ~SoundSystem(); HRESULT hrInitSoundSystem(void); HRESULT hrLoadSound(char *szname,GameSound *gs); HRESULT hrPlaySound(GameSound *gs); };
Код определения класса достаточно короток, но не слишком, если его сравнивать с самим классом. Ваше внимание может привлечь одна вещь — тип данных GameSound. Что же это такое?
Я создал класс GameSound потому, что вам требуется только один объект исполнителя и один объект загрузчика, но несколько сегментов. Данный класс содержит реальные данные звукового сегмента для отдельного звукового фрагмента. Этот класс ни что иное, как простое хранилище звуковой информации. Вот как выглядит заголовок класса:
class GameSound { public: IDirectMusicSegment8 *m_pSound; IDirectMusicPerformance8 *m_pPerformance; ~GameSound(); GameSound(); };
Не беспокойтесь по поводу указателя m_pPerformance. Он всего лишь указывает на интерфейс исполнителя в классе звуковой системы. Реально используется только один член данных, m_pSound, в котором сразу после загрузки сохраняются звуковые данные.
Взаимосвязь двух классов показана на рис. 7.9.
Рис. 7.9. Взаимодействие класса звуковой системы и класса звукового фрагмента
Обратите внимание, что интерфейс загрузчика в классе звуковой системы используется для загрузки данных в интерфейс сегмента в классе звукового фрагмента. Кроме того, вы можете заметить, что интерфейс исполнителя совместно используют оба класса.
Обзор закончен, так как насчет нескольких фрагментов кода реализации класса, чтобы поддержать разговор? Ниже представлен код конструктора класса:
SoundSystem::SoundSystem() { m_pLoader = NULL; m_pPerformance = NULL; }
Как видите, в конструкторе я присваиваю значение NULL указателям на интерфейсы загрузчика и исполнителя. Это делается для того чтобы иметь возможность проверить существуют ли объекты загрузчика и исполнителя. Если значение указателя равно NULL, объект не готов. Если же значение отличается от NULL, я знаю, что могу использовать данный объект. Если хотите, можете назвать это санитарной проверкой.
В следующем блоке кода представлен деструктор класса:
SoundSystem::~SoundSystem() { SAFE_RELEASE(m_pLoader); SAFE_RELEASE(m_pPerformance); }
Деструктор освобождает объекты загрузчика и исполнителя. Я использую вспомогательный макрос DirectX с именем SAFE_RELEASE. Он проверяет равно ли значение указателя NULL и, если нет, освобождает объект. Поэтому данная операция и называется безопасным освобождением. Раз в классе создаются объекты загрузчика и исполнителя, в деструкторе их следует освободить.
Первая действительно важная функция — это функция инициализации. Вот ее код:
HRESULT SoundSystem::hrInitSoundSystem(void) { HRESULT hResult; IDirectMusicAudioPath8 *path; // Инициализация COM CoInitialize(NULL); // Создание загрузчика if(FAILED(hResult = CoCreateInstance( CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&m_pLoader))) { return(SOUNDERROR_MUSICLOADER); } // Создание исполнителя if(FAILED(hResult = CoCreateInstance( CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**)&m_pPerformance))) { return(SOUNDERROR_MUSICPERFORMANCE); } // Инициализация аудиосистемы if(FAILED(hResult = m_pPerformance->InitAudio( NULL, NULL, m_hWnd, DMUS_APATH_DYNAMIC_STEREO, 4, DMUS_AUDIOF_ALL, NULL ))) { return(SOUNDERROR_INITAUDIO); } // Получение пути по умолчанию if(FAILED(m_pPerformance->GetDefaultAudioPath(&path))) return(SOUNDERROR_PATH); // Установка уровня громкости if(FAILED(path->SetVolume(0, 0))) return(SOUNDERROR_VOLUME); return(S_OK); }
При виде кода функции у вас в голове должен прозвонить звонок. Ранее в этой главе я уже описывал аналогичную функцию. Если говорить кратко, функция инициализирует COM, создает интерфейс загрузчика и интерфейс исполнителя, инициализирует аудиосистему, получает аудиопуть по умолчанию и устанавливает уровень громкости.
Следующая функция в списке загружает звуковые данные. Вот как выглядит ее код:
HRESULT SoundSystem::hrLoadSound(char *szname,GameSound *gs) { WCHAR szWideFileName[512]; // Проверяем инициализирована ли аудиосистема if(!m_pLoader) return(SOUNDERROR_MUSICLOADER); if(!m_pPerformance) return(SOUNDERROR_MUSICPERFORMANCE); // Очищаем звуковые данные, если они существуют if(gs->m_pSound) { gs->m_pSound->Unload(m_pPerformance); gs->m_pSound->Release(); gs->m_pSound = NULL; } // Копируем имя файла DXUtil_ConvertGenericStringToWideCch( szWideFileName, szname, 512); // Загружаем звуковые данные из файла if (FAILED(m_pLoader->LoadObjectFromFile ( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8, szWideFileName, (LPVOID*) &gs->m_pSound ))) { return(SOUNDERROR_LOAD); } // Устанавливаем указатель на исполнителя в объекте звукового фрагмента gs->m_pPerformance = m_pPerformance; // Загружаем данные if (FAILED (gs->m_pSound->Download(m_pPerformance))) { return(SOUNDERROR_DOWNLOAD); } return(S_OK); }
В первой части кода функции выполняется проверка того, равны ли значения указателей на интерфейсы загрузчика и исполнителя NULL или нет. Если хотя бы один из указателей равен NULL, функция возвращает соответствующий код ошибки, сообщая о возникщей проблеме.
В следующей части функции проверяется содержит ли объект звукового фрагмента, указатель на который передан функции, ранее загруженные данные. Если это так, звуковые данные должны быть сперва выгружены и удалены. Это достигается путем вызова методов выгрузки и освобождения через указатель на интерфейс сегмента, являющийся членом объекта звукового фрагмента.
Санитарные проверки завершены и пора заняться чем-нибудь более существенным. Далее в коде вызывается вспомогательная функция DirectX с именем DXUtil_ConvertGenericStringToWideCch(). Поскольку выполняющие загрузку звуковых данных функции DirectX требуют, чтобы имя файлов было представлено строкой 16-разрядных символов, необходимо преобразовать переданную в параметре строку с именем файла в строку 16-разрядных символов. Вызов этой функции прямолинеен, так что я полагаю, что здесь код говорит сам за себя.
После того, как имя файла преобразовано, можно спокойно переходить к функции загрузки. Чтобы загрузить реальные звуковые данные я вызываю функцию LoadObjectFromFile(). Как помните, ранее в этой главе я говорил, что данная функция загружает звуковые данные из файла и сохраняет их в объекте звукового сегмента. В данном случае я сохраняю данные сегмента в сегменте на который указывает член данных объекта звукового фрагмента m_pSound.
Следующая строка кода устанавливает внутренний указатель исполнителя в объекте звукового фрагмента, чтобы он указывал на интерфейс исполнителя класса звуковой системы. Это необходимо потому, что объекту звукового фрагмента требуется указатель на исполнителя для загрузки и выгрузки данных.
И, наконец, я загружаю данные в только что установленный объект исполнителя. Теперь звуковые данные готовы для воспроизведения.
Раз уж мы заговорили о воспроизведении, пришла пора показать вам код функции воспроизведения звука.
HRESULT SoundSystem::hrPlaySound(GameSound *gs) { // Проверяем наличие объекта исполнителя if(!m_pPerformance) return(SOUNDERROR_MUSICPERFORMANCE); // Проверяем наличие звукового сегмента if(!gs->m_pSound) return(SOUNDERROR_NOSEGMENT); // Воспроизводим звуковой сегмент if(FAILED (m_pPerformance->PlaySegmentEx( gs->m_pSound, NULL, NULL, DMUS_SEGF_DEFAULT | DMUS_SEGF_SECONDARY, 0, NULL, NULL, NULL ))) return(SOUNDERROR_PLAYFAIL); return(S_OK); }
И снова я выполняю санитарные проверки, чтобы убедиться, что используемые интерфейсы доступны. Если они не существуют, вызывающей функции возвращается код ошибки.
Затем я вызываю функцию PlaySegmentEx(), которая выполняет всю черновую работу по воспроизведению звуковых данных. Поскольку функция требует, чтобы ей указали воспроизводимый звуковой сегмент, я передаю ей указатель на сегмент из объекта звукового фрагмента.
Класс звукового фрагмента является очень простым и содержит только конструктор и деструктор. Вот как выглядит реализация данного класса:
// Конструктор GameSound::GameSound() { m_pSound = NULL; m_pPerformance = NULL; } // Деструктор GameSound::~GameSound() { if(m_pSound) { if(m_pPerformance) m_pSound->Unload(m_pPerformance); } SAFE_RELEASE(m_pSound); }
Конструктор присваивает внутренним указателям значение NULL, чтобы последующие проверки могли сообщить инициализированы эти указатели или нет. Деструктор освобожает выделенные ресурсы, проверяя требуется ли выгрузка звуковых данных во время работы деструктора.
Взгляните на рис. 7.10, чтобы увидеть схему описанного к данному моменту кода.
Рис. 7.10. Взаимодействие объектов классов звуковой системы и звукового фрагмента
На рис. 7.10 показано взаимодействие объектов и взаимоотношения между классом звуковой системы и классом звукового фрагмента. В верхней части рисунка показано как интерфейсы зхагрузчика и исполнителя используются в функции инициализации. В центре рисунка видно как интерфейс загрузчика применяется в функции загрузки звука. В функцию загрузки передается объект звукового фрагмента и там вызывается его метод загрузки данных. В нижней части рисунка показано как объект звукового фрагмента передается в функцию возпроизведения, чтобы началось воспроизведение звука. Чтобы воспроизвести реальный звук объект исполнителя из класса звуковой системы использует данные сегмента из объекта звукового фрагмента. У-ух. Понятно?
Как насчет примера программы, которая использует рассмотренный только что класс? Загрузите с компакт-диска проект с именем DSound_SoundSystem и следуйте за мной дальше.
Рассматриваемый пример состоит из пяти основных файлов: main.cpp, main.h, SoundSystem.cpp, SoundSystem.h и dxutil.cpp. Файлы main.cpp и main.h содержат основной код программы, а в файлах SoundSystem.cpp и SoundSystem.h находится код класса звуковой системы. Файл dxutil.cpp содержит код полезных вспомогательных функций DirectX.
Для компиляции приложения потребуются несколько библиотек: dxguid.lib, comctl32.lib, winmm.lib и dsound.lib. Список должен выглядеть знакомо, поскольку те же самые библиотеки использовались в первом примере программы из этой главы.
На рис. 7.11 показано окно, выводимое данной программой.
Рис. 7.11. Окно программы DSound_SoundSystem
Полагаю, что окно на рис. 7.11 нельзя сравнивать с экранами из игры Warcraft II, но тем не менее это настоящая программа! Фактически в программе нет никаких изображений для разглядывания. Но тем не менее, тут есть что послушать! Запустите программу и нажмите на левую кнопку мыши, а затем на правую. Вы услышите воспроизведение двух разных звуков. Более того, вы можете нажимать на кнопки мыши снова и снова и звук будет воспроизодиться несколько раз.
Не будем тратить бумагу и рубить больше деревьев, чем требуется, так что без лишних проволочек перейдем к коду функции WinMain() в файле main.cpp. Там вы увидите вызов функции с именем bInitializeSoundSystem(), которая написана специально для данного примера. Вот ее код:
bool bInitializeSoundSystem(void) { HRESULT hr; // Выделение памяти для звуковых фрагментов g_sndButton = new GameSound; g_sndButtonOver = new GameSound; // Инициализация звуковой системы g_SoundSys.hrInitSoundSystem(); // Загрузка звуковых фрагментов hr = g_SoundSys.hrLoadSound("button.wav", g_sndButton); if(hr == SOUNDERROR_LOAD) { return(0); } hr = g_SoundSys.hrLoadSound( "button_over.wav", g_sndButtonOver); if(hr == SOUNDERROR_LOAD) { return(0); } // Успешное завершение return(1); }
В программе выполняется загрузка двух звуковых файлов: button_over.wav и button.wav. Поскольку у нас два звуковых файла, нам необходимо два объекта GameSound. Они присутствуют в виде переменных g_sndButton и g_sndButtonOver. Каждая из них является объектом GameSound и объявлена в заголовочном файле main.h. Первым действием является выделение памяти для двух объектов звуковых фрагментов. Я делаю это с помощью оператора new.
Объекты звуковых фрагментов не слишком полезны без звуковой системы, поэтому я объявляю в заголовочном файле main.h указатель на объект звуковой системы с именем g_SoundSys. Чтобы этим объектом можно было воспользоваться, его следует инициализировать, для чего я вызываю метод hrInitSoundSystem(). Данный вызов инициализирует DirectSound и подготавливает систему к воспроизведению звука.
Затем я дважды вызываю метод класса звуковой системы hrLoadSound(). В параметрах этих вызовов я передаю два объекта звуковых фрагментов, чтобы они были заполнены данными из указанных файлов WAV. Вы можете спокойно заменить загружаемые здесь WAV-файлы на свои собственные.
Как только работа функций загрузки звуковых данных успешно завершена, программе возвращается значение 1, свидетельствующее об успешном завершении инициализации. Если в ходе инициализации произошел сбой, возвращается 0, что говорит о наличии ошибки.
Итак, мы разобрали этапы успешной инициализации звуковой системы.
Вернемся к циклу обработки сообщений Windows. В нем находится код, ожидающий пока не будет нажата левая или правая кнопки мыши. Как только произойдет одно из указанных событий, будет воспроизведен соответствующий звук. Вот как выглядит код:
switch(msg) { case WM_LBUTTONDOWN: // Воспроизведение звука g_SoundSys.hrPlaySound(g_sndButtonOver); break; case WM_RBUTTONDOWN: // Воспроизведение другого звука g_SoundSys.hrPlaySound(g_sndButton); break; case WM_DESTROY: PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, msg, wParam, lParam);
В блоке логики оператора switch видно, как система реагирует на события кнопок мыши вызывая функцию класса звуковой системы hrPlaySound(). Если нажата левая кнопка мыши, воспроизводится звук из объекта g_sndButtonOver. Если нажата правая кнопка мыши, воспроизводится звук из объекта g_sndButton.
На рис. 7.12 показан ход выполнения программы с момента запуска до завершения.
Рис. 7.12. Работа программы, использующей класс звуковой системы
На рис. 7.12 видно, что первой вызывается функция инициализации звуковой системы. Внутри этой функции программа вызывает функцию инициализации из класса SoundSystem. Затем для загрузки двух звуковых файлов вызывается функция загрузки звукового фрагмента из того же класса. После того, как инициализация завершена, программа обрабатывает сообщения, пока не получит сигнал о том, что нажата левая или правая кнопка мыши. Как только это произойдет, программа вызывает метод воспроизведения звука из класса звуковой системы и воспроизводит соответствующий звуковой фрагмент.
Вы помните программу с меню игры Battle Armor, которую я описывал в 6 главе? Пришло время добавить к ней звуки и музыку. Откройте находящийся на компакт-диске проект с именем D3D_MenuSounds и следуйте за мной. Вместо того, чтобы вываливать на вас новые курганы кода, я просто проведу обзор внесенных изменений с высоты птичьего полета.
В файл Main.h я добавил директиву включения заголовочного файла класса звуковой системы. Кроме того, я создал глобальный объект класса звуковой системы и несколько объектов звуковых фрагментов. Все это показано на рис. 7.13.
Рис. 7.13. Структура реализации звуковой системы в заголовочном файле проекта D3D_MenuSounds
В файл Main.cpp были добавлены вызовы функций для инициализации звуковой системы, загрузки звуковых файлов и их воспроизведения. Данные изменения иллюстрирует рис. 7.14.
Рис. 7.14. Структура реализации звуковой системы в главном файле проекта D3D_MenuSoundsSound
Как видно на рис. 7.14 при инициализации звуковой системы происходит обращение к методу инициализации объекта звуковой системы. Затем этот же объект используется для загрузки звуковых файлов. Как только эти задачи выполнены, загруженные звуковые фрагменты можно воспроизводить, когда это потребуется.
Чтобы добавить воспроизведение файлов MP3 я просто скопировал в программу работы с меню функции bPlayTitleMusic(), vStopTitleMusic() и vCheckMusicStatus(). Эти действия и добавление вызова, начинающего воспроизведение музыки в код инициализации и составляют весь секрет трюка.
Если вы еще не сделали это, запустите программу D3D_MenuSounds и пощелкайте по разным кнопкам меню. Музыка MP3 воспроизводится в фоновом режиме, а при щелчке по некоторым кнопкам меню воспроизводится WAV-файл. Обратите внимание, что звук в WAV-файле достаточно тихий и вам, возможно, придется прислушаться, чтобы расслышать его на фоне музыки. Я советую вам попробовать поместить в программу свои собственные музыку и звуки (ха, вы можете добавить даже несколько звуков, чтобы закрепить полученные навыки).
Вот и все, что я собирался рассказать вам о реализации звукового сопровождения и воспроизведении MP3-файлов в программах. Конечно, есть еще масса вещей, которые придется учесть, например, определить какой звук воспроизводить и когда. Но об этом мы поговорим в другой раз.
netlib.narod.ru | < Назад | Оглавление | Далее > |