Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.12 Добавляем поддержку звука
Программируем 3D-шутер от первого лица (FPS) (Win32, C++, DirectX9)

1.12 Добавляем поддержку звука


Здесь мы внедрим в наш движок систему поддержки звука, способную проигрывать любые звуковые эффекты и музыку. Она даже будет поддерживать 3D-звук (3D-sound), который будет исходить из источника в 3D-пространстве.
Ни одна игра не обходится без звуков, будь то звуки выстрелов, шагов, музыка либо просто рекплики персонажей.

DirectMusic

DirectX до настоящего момента было всего 2 компонента, имеющих возможность проигрывать звуки:

  • DirectSound
  • DirectMusic.
Закрыть
noteПримечание

В последних версиях DirectX компоненты DirectSound и DirectMusic объединены в один компонент XAudio, который в данном курсе не рассматривается.

Несмотря на свой название, компонент DirectMusic способен проигрывать не только музыку. У него почти такие же возможности по работе со звуком, что и у DirectSound. Да, DirectMusic специально разрабатывался для проигрывания фонового музыкального оформления в играх. Но со временем его функционал был существенно расширен. Фактически DirectMusic рассматривается как полнофункциональный интерфейс, особенно если его рассматривать с точки зрения его возможностей динамического проигрывания звуков.
В DirectX9 оба компонента перешли с минимальными изменениями. Они были в очередной раз оптимизированы и немного расширены для повышения производительности.
Так в чём же различия между DirectMusic и DirectSound? В двух словах они отличаются по уровню юзабилити и степени контроля обработки звуков на низком уровне (low level control). Если отбросить все экстра-фичи, поддерживаемые DirectMusic (связанные с его возможностями по работе с музыкой), то реальная разница между этими двумя компонентами заключается в том, что DirectMusic предоставляет дружественную пользователю среду (user friendly enviroment), в то время как DirectSound даёт больший контроль над звуком.
В самом деле, DirectSound наиболее подходит тем, кто хочет получить абсолютный контроль над каждым аспектом проигрывания звука, причём на низком уровне (например, управление аппаратными ресурсами по работе со звуком). В качестве примера можно привести разработчиков программ синтезирования звука, или тех, кто разрабатывает свой собственный продвинутый звуковой движок. Обратной стороной DirectSound является его достаточно высокая сложность освоения. Если тебе не требуется делать со звуком что-либо экстраординарное и ты просто хочешь включить звуки и музыку в свою игру, используй DirectMusic. Жертвуя низкоуровневым контролем над выводом звука, взамен ты получаешь невероятно простой в использовании интерфейс, который будет готов к выводу звука уже через ннесколько минут. А когда дело дойдёт до проигрывания музыки, ты будешь приятно удивлён набором всевозможных фич, которые поддерживает DirectMusic.
Не забывай, что, несмотря на то, что DirectMusic и DirectSound являются двумя разными компонентами, тем не менее они очень взаимосвязаны друг с другом. И вскоре ты обнаружишь, что мы используем различные интерфейсы из обоих компонентов, особенно когда будем рассматривать 3D-звук. В связи с этим чуть позднее мы всё-таки рассмотрим некоторые интерфейсы DirectSound, по мере их использования в нашем движке. А сейчас мы более подробно остановимся на DirectMusic и на том, как именно мы будем его применять для проигрывания звуков.

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

  • Загружает и проигрывает звуки в форматах MIDI и WAV.
  • Использует динамическое проигрывание звуков посредством специального интерфейса DirectMusic producer.
  • Контролирует уровень громкости (volume), высоту (pitch) и панорамное распределение по обоим каналам (pan) путём использования компонента DirectSound.
  • Определяет местоположение звука в 3D-пространстве путём использования компонента DirectSound.
  • Применяет спецэффекты к звукам, использует стили для проигрывания музыки и ообладает множеством других продвинутых возможностей.

Судя по данному списку, некоторые аспекты работы DirectMusic зависят от DirectSound (например, позиционирование звука в 3D-пространстве). Теперь, когда мы знаем, на что способен DirectMusic, рассмотрим подробнее принципы его работы.
Для проигрывания музыки DirectMusic использует несколько интерфейсов. Первый из них - loader (загрузчик).

Интерфейс Loader

Звуки являются ресурсами. Подобно скриптам, текстурам и 3D-мешам, звуки хранятся (чаще всего) на жётском диске компьютера, и перед использованием должны быть обязательно загружены. В Главе 1.4 мы уже разработали и внедрили систему менеджмента ресурсов, но в случае со звуками мы не будем её применять. DirectMusic использует (свой собственный) loader для загрузки и управления звуковыми ресурсами. Другими словами, он способен делать всё, что и наша система менеджмента ресурсов. И даже намного больше. К сожалению, loader заточен именно под звуковые ресурсы. Поэтому мы не сможем применить его для ресурсов других типов.

Интерфейс Perfomance

Как только звуковой ресурс был успешно загружен, за дело берётся т.н. Perfomance (не думаю, что переводится на русский...) для управления потоком звуковых данных в синтезаторе (который может быть либо программным, либо встроенным в хорошую дискретную звуковую карту). Perfomance является наиболее важным интерфейсом, используемым в компоненте DirectMusic. При инициализации движка и запуске игры он создаётся всего один раз и отвечает за проигрывание абсолютно всех звуков в данном приложении. Perfomance также отвечает за освобождение более неиспользуемых ресурсов и за менеджмент ресурсов вцелом, так или иначе связанные с проигрыванием звуков. Perfomance способен управлять проигрыванием нескольких звуков одновременно, наиболее эффективно смешивать их для гармоничного и качественного вывода на внешние колонки, подключенные к ПК. Удивительно, но, несмотря на то, что интерфейс Perfomance без сомнения станет твоим лучшим другом, ты будешь очень редко обращаться к нему. Будучи однажды созданным, он будет послушно выполнять свою работу и у тебя реально не будет необходимости в неё вмешиваться. Вот почему DirectMusic так прост в использовании, в чём ты очень скоро убедишься, когда мы начнём разрабатывать нашу систему поддержки звука.

Рис.1 Процесс воспроизв. звука через DirectMusic и DirectSound
Рис.1 Процесс воспроизв. звука через DirectMusic и DirectSound

Интерфейсы Segments и Audio paths

  • Идут "рука об руку" друг с другом.

Как только звуковой ресурс был загружен, он сохраняется в сегменте (участке звукового буфера), который по сути является блоком звуковых данных, который моментально может быть передан в DirectMusic для дальнейшего проигрывания.
Audio path (аудиопуть) можно сравнить с обычной тропинкой в лесу, по которой идёт звуковой сегмент. Эта воображаемая тропинка начинается в Perfomance и идёт дальше к т.н. главному буферу (primary buffer). На своём пути он встречает несколько "интересных турпунктов", которые, без сомнения, любят посещать все звуковые сегменты. Таковыми являются, к примеру, синтезатор (synthesizer) и буферы DirectSound (DirectSound buffers). DirectSound использует буферы для управления проигрыванием определённых звуков в определённое время. К примеру, у тебя может быть буфер для звука выстрела, и ещё один для звука шагов. У каждого из этих буферов есть свои параметры, которые влияют на проигрывание отдельных звуков и которые могут быть отредактированы (например, уровень громкости или частота звука).
Дополнительно ты можешь назначить (aquire) буфер 3D-звука (3D-sound buffer) для данного звука, что позволит настраивать для него дополнительные параметры, как например позиция звука в 3D-пространстве, его скорость и направление движения. Главный звуковой буфер (primary sound buffer) стоит особняком и используется DirectSound для смешивания всех звуков, расположенных во вторичных (secondary) звуковых буферах, в один комбинированый звук, который затем отправляется на выход звуковой карты. Главный звуковой буфер также применяется для контролирования глобальных (= общих для всех) параметров 3D-звука.
Как видишь, всё не так уж сложно. К счастью, звуковые интерфейсы очень легко применять и большинство всех этих внутренних процессов полностью прозрачны. То есть тебе никогда не придётся их видеть, либо разбираться в том, как они работают.
Ты, должно быть, заметил, что ближе к концу описания сегментов и аудиопутей мы вновь стали говорить о DirectSound. Причина кроется в том, что DirectSound на низком уровне управляет проигрыванием звуков именно с помощью звуковых буферов. DirectMusic, в свою очередь, "сидит" на вершине всего этого действа и просто передаёт необходимую информацию в звуковые буферы (См. Рис.1)

===
Вот мы и рассмотрели систему воспроизведения звука через DirectMusic и DirectSound. Конечно, данное описание затрагивает лишь основные принципы работы данных компонентов и "за кадром" осталось множество фишек и дополнительных возможностей. Более подробно некоторые из них мы рассмотрим при разработке нашей системы поддержки звука.

Сейчас в Проекте Engine нашего движка всего 16 файлов: Engine.h, Engine.cpp, DeviceEnumeration.h, DeviceEnumeration.cpp, Font.h, Font.cpp, Input.h, Input.cpp, LinkedList.h, Resource.h, ResourceManagement.h, Geometry.h, Scripting.h, Scripting.h, State.h, State.cpp, которые мы создали в предыдущих главах (см. Рис. 1).
Для того, чтобы вдохнуть жизнь в нашу систему поддержки звука мы создадим всего 3 класса:

  • SoundSystem
  • Sound
  • AudioPath3D

...и напишем их реализации.

Создаём SoundSystem.h (Проект Engine)

ОК, приступаем.

  • Стартуй MSVC++ 2010 и открывай Решение GameProject01 (если не сделал это раньше).
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы" Проекта Engine.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "SoundSystem.h".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле SoundSystem.h набираем следующий код:

SoundSystem.h (Проект Engine)
//-----------------------------------------------------------------------------
// Класс-обёртка для воспроизведения аудиофайлов через интерфейсы DirectMusic.
// Класс AudioPath3D предназначен для размещения источника звука в 3D-пространстве.
//
// Original Sourcecode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#ifndef SOUND_SYSTEM_H
#define SOUND_SYSTEM_H

//-----------------------------------------------------------------------------
// Sound System Class
//-----------------------------------------------------------------------------
class SoundSystem
{
public:
	SoundSystem( float scale = 1.0f );
	virtual ~SoundSystem();

	void UpdateListener( D3DXVECTOR3 forward, D3DXVECTOR3 position, D3DXVECTOR3 velocity );

	void GarbageCollection();

	void SetVolume( long volume );

	IDirectMusicLoader8 *GetLoader();
	IDirectMusicPerformance8 *GetPerformance();

private:
	float m_scale; // Масштаб в метрах/юнитах.
	IDirectMusicLoader8 *m_loader; // DirectMusic loader.
	IDirectMusicPerformance8 *m_performance; // DirectMusic performance.
	IDirectSound3DListener8 *m_listener; // DirectSound 3D-слушатель (3D listener).
};

//-----------------------------------------------------------------------------
// Sound Class
//-----------------------------------------------------------------------------
class Sound
{
public:
	Sound( char *filename );
	virtual ~Sound();

	void Play( bool loop = false, unsigned long flags = DMUS_SEGF_AUTOTRANSITION );

	IDirectMusicSegment8 *GetSegment();

private:
	IDirectMusicSegment8 *m_segment; // DirectMusic segment for the sound.
};


//-----------------------------------------------------------------------------
// Audio Path 3D Class
//-----------------------------------------------------------------------------
class AudioPath3D
{
public:
	AudioPath3D();
	virtual ~AudioPath3D();

	void SetPosition( D3DXVECTOR3 position );
	void SetVelocity( D3DXVECTOR3 velocity );
	void SetMode( unsigned long mode );

	void Play( IDirectMusicSegment8 *segment, bool loop = false, unsigned long flags = DMUS_SEGF_SECONDARY );

private:
	IDirectMusicAudioPath8 *m_audioPath; // DirectMusic audio path for 3D playback.
	IDirectSound3DBuffer8 *m_soundBuffer; // Указатель на звуковой буфер аудиопути (audiopath's sound buffer).
};

#endif

  • Сохрани Решение (Файл->Сохранить все).

Исследуем код SoundSystem.h

Объявление класса SoundSystem

Первым из классов представлен SoundSystem, предназначенный для общего управления системой поддержки звука. Кроме того, в нём создаются и обрабатываются загрузчик звуковых ресурсов (loader) и Perfomance. Если изучил теоретическую часть, изложенную выше, то проблем с пониманием данного класса быть не должно. Едиснтвенное, с чем ты пока не сталкивался, это интерфейс IDirectSound3DListener8, который используется DirectSound где данный 3D-звук может быть услышан, а где нет (и с какой громкостью). Если может, то дополнительно определяются (основываясь на текущей позиции в сцене) с какой громкостью данный звук должен быть воспроизведён в каждой из двух колонок (левой и правой) для симуляции эффекта 3D-окружения.

Закрыть
noteЛюбопытно, но факт!

Напомним, что легендарная игра Half-Life (1998, Valve) уже тогда вовсю щеголяла подобной фичей. Когда персонаж поворачивал голову, стоя перед говорящим учёным, звук голоса персонажа плавно "перетекал" из левого канала в правый (или наоборот), значительно усиливая эффект присутствия и общее впечатление от игры.

Объявление класса Sound

  • Служит для хранения отдельных звуков.
  • Является классом-обёрткой (wrapper class) интерфейса IDirectMusicSegment8, экспонированного DirectX.

На деле можно вообще напрямую использовать интерфейс IDirectMusicSegment8 для хранения и проигрывания всех наших звуков. Тем не менее мы реализуем наш собственный класс Sound в целях завершённости общего вида программного кода и для улучшения юзабилити движка. Данный класс предоставит нам интерффейс более высокого уровня, который сделает процесс управления звуками ещё проще. Рассмотрим объявление класса Sound, размещённое в SoundSystem.h:

Фрагмент SoundSystem.h (Проект Engine)
...
//-----------------------------------------------------------------------------
// Sound Class
//-----------------------------------------------------------------------------
class Sound
{
public:
	Sound( char *filename );
	virtual ~Sound();

	void Play( bool loop = false, unsigned long flags = DMUS_SEGF_AUTOTRANSITION );

	IDirectMusicSegment8 *GetSegment();

private:
	IDirectMusicSegment8 *m_segment; // DirectMusic segment for the sound.
};
...


Из данного фрагмента видно, что класс Sound не наследуется от класса Resource! А всё потому, что, как ты помнишь, все наши звуковые ресурсы загружаются и управляются напрямую через загрузчик DirectMusic. Следовательно им не нужна поддержка со стороны нашей системы управления ресурсами. Остальной код понятен без объяснений.

Объявление класса AudioPath3D

Фрагмент SoundSystem.h (Проект Engine)
...
//-----------------------------------------------------------------------------
// Audio Path 3D Class
//-----------------------------------------------------------------------------
class AudioPath3D
{
public:
	AudioPath3D();
	virtual ~AudioPath3D();

	void SetPosition( D3DXVECTOR3 position );
	void SetVelocity( D3DXVECTOR3 velocity );
	void SetMode( unsigned long mode );

	void Play( IDirectMusicSegment8 *segment, bool loop = false, unsigned long flags = DMUS_SEGF_SECONDARY );

private:
	IDirectMusicAudioPath8 *m_audioPath; // DirectMusic audio path for 3D playback.
	IDirectSound3DBuffer8 *m_soundBuffer; // Указатель на звуковой буфер аудиопути (audiopath's sound buffer).
};
...

Первое, что видим - это конструктор и деструктор класса. У конструктора нет ни одного параметра, что делает нашу жизнь несколько проще.
Класс также экспонирует несколько функций, которые позволяют установить для выбранного аудиопути ряд дополнительных параметров (например, положение источника звука в 3D-пространстве и скорость его движения). В данный момент это ещё не значит, что аудиопуть размещён в 3D-пространстве и перемещается. Т.к. в данный момент мы лишь даём команду DirectSound выполнять все вычисления над любыми воспроизводимыми звуками исключительно через данный аудиопуть, основываясь на параметры положения и скорости звука, предварительно установленных в нём. Созданный аудиопуть не является внутренним интерфейсом IDirectMusicAudioPath3D, который хранит данные о положении и скорости данного звука. Вместо этого наш аудиопуть создан на базе интерфейса IDirectSoundBuffer8. Мы захватываем (aquire) данный звуковой буфер (sound buffer) сразу после создания нашего аудиопути (это видно в исходном коде реализации конструктора класса AudioPath3D, размещённом в SoundSystem.cpp, который мы создадим буквально через пару абзацев).
Но у нас есть ещё одна функция, которая используется для воспроизведения звуков через созданный аудиопуть.
Мы говорили, что аудиопуть может использоваться для воспроизведения любых звуков. Обычно аудиопуть создаётся для каждого объекта воспроизводимого звука вместо того, что бы его создавать для каждого звука. Например, если в игре у тебя есть оружие, которое может воспроизводить один из трёх различных звуков выстрела, то, вместо того, чтобы создавать аудиопуть для каждого из этих звуков, рациональнее будет создать один аудиопуть для всех звуков выстрела оружия. Точно также ты можешь установить положение аудиопути в 3D-пространстве и скорость его передвижения, основываясь на тех же параметрах 3D-модели оружия. Затем ты просто воспроизводишь соответствующий звук на аудиопути. В данном случае это означает, что одновременно на созданном аудиопути может воспроизводится один из трёх возможных звуков. При этом неважно, какой из звуков воспроизводится в данный момент, т.к. все они будут иметь координаты позиции и скорость, установленные в их аудиопути.

Создаём SoundSystem.cpp (Проект Engine)

В файле исходного кода SoundSystem.cpp будут размещаться реализации функций, объявленных в SoundSystem.h.
ОК, приступаем.

  • Стартуй MSVC++ 2010 и открывай Решение GameProject01 (если не сделал это раньше).
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Engine.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи "SoundSystem.cpp".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле SoundSystem.cpp набираем следующий код:

SoundSystem.cpp (Проект Engine)
//-----------------------------------------------------------------------------
// Файл: SoundSystem.cpp
//	Реализация фцнкций, объявленных в SoundSystem.h.
//
// Original SourceCode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#include "Engine.h"

//-----------------------------------------------------------------------------
// The sound system class constructor.
//-----------------------------------------------------------------------------
SoundSystem::SoundSystem( float scale )
{
	// Создаём загрузчик DirectMusic loader.
	CoCreateInstance( CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&m_loader );

	// Создаём и инициализируем DirectMusic Performance.
	CoCreateInstance( CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**)&m_performance );
	m_performance->InitAudio( NULL, NULL, NULL, DMUS_APATH_SHARED_STEREOPLUSREVERB, 8, DMUS_AUDIOF_ALL, NULL );

	// Получаем 3D-слушателя (3D listener) путём создания 3D аудиопути и затем запрашивая
	// слушателя из этого аудиопути. Аудиопуть (audio path) затем может быть освобождён (released).
	IDirectMusicAudioPath8 *audioPath3D;
	m_performance->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_3D, 1, true, &audioPath3D );
	audioPath3D->GetObjectInPath( 0, DMUS_PATH_PRIMARY_BUFFER, 0, GUID_All_Objects, 0, IID_IDirectSound3DListener, (void**)&m_listener );
	SAFE_RELEASE( audioPath3D );

	// Устанавливаем масштаб и фактор удаления (distance factor).
	m_scale = scale;
	m_listener->SetDistanceFactor( m_scale, DS3D_IMMEDIATE );
}

//-----------------------------------------------------------------------------
// The sound system class destructor.
//-----------------------------------------------------------------------------
SoundSystem::~SoundSystem()
{
	// Закрываем и освобождаем DirectMusic Performance.
	m_performance->CloseDown();
	SAFE_RELEASE( m_performance );

	// Освобождаем закгрузчик (loader) DirectMusic.
	SAFE_RELEASE( m_loader );
}

//-----------------------------------------------------------------------------
// Обновляем 3D-слушателя (3D listener) звуковой системы.
//-----------------------------------------------------------------------------
void SoundSystem::UpdateListener( D3DXVECTOR3 forward, D3DXVECTOR3 position, D3DXVECTOR3 velocity )
{
	// Устанавливаем ориентацию слушателя.
	m_listener->SetOrientation( forward.x, forward.y, forward.z, 0.0f, 1.0f, 0.0f, DS3D_DEFERRED );

	// Масштабируем и устанавливаем позицию слушателя.
	position *= m_scale;
	m_listener->SetPosition( position.x, position.y, position.z, DS3D_DEFERRED );

	// Масштабируем и устанавливаем скорость слушателя.
	velocity *= m_scale;
	m_listener->SetVelocity( velocity.x, velocity.y, velocity.z, DS3D_DEFERRED );

	// Подтверждаем выбранные установки.
	m_listener->CommitDeferredSettings();
}

//-----------------------------------------------------------------------------
// Позволяем системе поддержки звука удалять неиспользуемые звуковые объекты.
//-----------------------------------------------------------------------------
void SoundSystem::GarbageCollection()
{
	m_loader->CollectGarbage();
}

//-----------------------------------------------------------------------------
// Устанавливаем общий уровень громкости (master volume) для всех звуков, воспроизводимых через данную звуковую систему.
//-----------------------------------------------------------------------------
void SoundSystem::SetVolume( long volume )
{
	m_performance->SetGlobalParam( GUID_PerfMasterVolume, &volume, sizeof( long ) );
}

//-----------------------------------------------------------------------------
// Возвращает указатель на текущий объект загрузчика звуковой системы.
//-----------------------------------------------------------------------------
IDirectMusicLoader8 *SoundSystem::GetLoader()
{
	return m_loader;
}

//-----------------------------------------------------------------------------
// Возвращает указатель на текущий объект Perfomance-а звуковой системы.
//-----------------------------------------------------------------------------
IDirectMusicPerformance8 *SoundSystem::GetPerformance()
{
	return m_performance;
}

//-----------------------------------------------------------------------------
// The sound class constructor.
//-----------------------------------------------------------------------------
Sound::Sound( char *filename )
{
	// Конвертируем имя файла в формат wide character string.
	WCHAR *wideFilename = new WCHAR[strlen( filename ) + 1];
	MultiByteToWideChar( CP_ACP, 0, filename, -1, wideFilename, strlen( filename ) + 1 );
	wideFilename[strlen( filename )] = 0;

	// Загружаем звуковой файл, затем уничтожаем имя файла.
	g_engine->GetSoundSystem()->GetLoader()->LoadObjectFromFile( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8, wideFilename, (void**)&m_segment );
	SAFE_DELETE( wideFilename );

	// Загружаем данные сегмента (segment's data) в Perfromance.
	m_segment->Download( g_engine->GetSoundSystem()->GetPerformance() );
}

//-----------------------------------------------------------------------------
// The sound class destructor.
//-----------------------------------------------------------------------------
Sound::~Sound()
{
	// Выгружаем данные сегмента (segment's data) из Perfromance-а.
	m_segment->Unload( g_engine->GetSoundSystem()->GetPerformance() );
	
	// Убираем сегмент из загрузчика DirectMusic, затем освобождаем (удаляем) его.
	g_engine->GetSoundSystem()->GetLoader()->ReleaseObjectByUnknown( m_segment );
	SAFE_RELEASE( m_segment );
}

//-----------------------------------------------------------------------------
// Воспроизводим звук на стандартном аудиопути с зацикливанием (бесконечным повторением) или без него.
// Внимание: Звуковые сегменты могут обслуживать тольго один стейт зацикливания (looping state).
// Во всех случаях смена флага зацикливания повлияет на все инстансы данного звука.
//-----------------------------------------------------------------------------
void Sound::Play( bool loop, unsigned long flags )
{
	if( loop == true )
		m_segment->SetRepeats( DMUS_SEG_REPEAT_INFINITE );
	else
		m_segment->SetRepeats( 0 );

	g_engine->GetSoundSystem()->GetPerformance()->PlaySegment( m_segment, flags, 0, NULL );
}

//-----------------------------------------------------------------------------
// Возвращает звуковой сегмент.
//-----------------------------------------------------------------------------
IDirectMusicSegment8 *Sound::GetSegment()
{
	return m_segment;
}

//-----------------------------------------------------------------------------
// The audio path 3D class constructor.
//-----------------------------------------------------------------------------
AudioPath3D::AudioPath3D()
{
	// Создаём аудиопуть. После этого можно индивидуально устанавливать параметры 3D-звучания.
	g_engine->GetSoundSystem()->GetPerformance()->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_3D, 1, true, &m_audioPath );

	// Получаем звуковой буфер аудиопути.
	m_audioPath->GetObjectInPath( DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER, 0, GUID_NULL, 0, IID_IDirectSound3DBuffer, (void**)&m_soundBuffer );
}

//-----------------------------------------------------------------------------
// The audio path 3D class destructor.
//-----------------------------------------------------------------------------
AudioPath3D::~AudioPath3D()
{
	SAFE_RELEASE( m_soundBuffer );
	SAFE_RELEASE( m_audioPath );
}

//-----------------------------------------------------------------------------
// Устанавливаем позицию звука в 3D-пространстве.
//-----------------------------------------------------------------------------
void AudioPath3D::SetPosition( D3DXVECTOR3 position )
{
	position *= g_engine->GetScale();
	m_soundBuffer->SetPosition( position.x, position.y, position.z, DS3D_IMMEDIATE );
}

//-----------------------------------------------------------------------------
// Устанавливаем скорость движения (источника) звука в 3D-пространстве.
//-----------------------------------------------------------------------------
void AudioPath3D::SetVelocity( D3DXVECTOR3 velocity )
{
	velocity *= g_engine->GetScale();
	m_soundBuffer->SetVelocity( velocity.x, velocity.y, velocity.z, DS3D_IMMEDIATE );
}

//-----------------------------------------------------------------------------
// Устанавливаем режим, в котором аудиопуть будет воспроизводить звук.
//-----------------------------------------------------------------------------
void AudioPath3D::SetMode( unsigned long mode )
{
	m_soundBuffer->SetMode( mode, DS3D_IMMEDIATE );
}

//-----------------------------------------------------------------------------
// Воспроизводим звук на 3D-аудиопути с зацикливанием (бесконечным повторением) или без него.
// Внимание: Звуковые сегменты могут обслуживать тольго один стейт зацикливания (looping state).
// Во всех случаях смена флага зацикливания повлияет на все инстансы данного звука.
//-----------------------------------------------------------------------------
void AudioPath3D::Play( IDirectMusicSegment8 *segment, bool loop, unsigned long flags )
{
	if( loop == true )
		segment->SetRepeats( DMUS_SEG_REPEAT_INFINITE );
	else
		segment->SetRepeats( 0 );

	g_engine->GetSoundSystem()->GetPerformance()->PlaySegmentEx( segment, NULL, NULL, flags, 0, NULL, NULL, m_audioPath );
}

  • Сохрани Решение (Файл->Сохранить все).

Исследуем код SoundSystem.cpp

Реализация класса SoundSystem

Конструктор класса SoundSystem принимает качестве единственного параметра масштаб, с которым в данный момент работает движок. Напомним, что это то самое значение, которое выставляется при заполнении структуры EngineSetup (Engine.h) и обозначает количество метров в одном условном юните.

Внутри конструктора первым делом создаём загрузчик (loader) DirectMusic, который является COM-объектом. Поэтому для его создания используем функцию CoCreateInstance. Здесь в первом параметре передаём идентификатор класса, от которого создаётся объект (в нашем случае - CLSID_DirectMusicLoader). В четвёртом параметре мы указываем, какой интерфейс будет использовать данный объект, путём передачи его имени (в нашем случае - IID_IDirectMusicLoader8). В последнем параметре мы передаём адрес указателя, в котором будет сохранён запрошенный указатель интерфейса ((void**)&m_loader).

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The sound system class constructor.
//-----------------------------------------------------------------------------
SoundSystem::SoundSystem( float scale )
{
	// Создаём загрузчик DirectMusic loader.
	CoCreateInstance( CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&m_loader );

	// Создаём и инициализируем DirectMusic Performance.
	CoCreateInstance( CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**)&m_performance );
	m_performance->InitAudio( NULL, NULL, NULL, DMUS_APATH_SHARED_STEREOPLUSREVERB, 8, DMUS_AUDIOF_ALL, NULL );

	// Получаем 3D-слушателя (3D listener) путём создания 3D аудиопути и затем запрашивая
	// слушателя из этого аудиопути. Аудиопуть (audio path) затем может быть освобождён (released).
	IDirectMusicAudioPath8 *audioPath3D;
	m_performance->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_3D, 1, true, &audioPath3D );
	audioPath3D->GetObjectInPath( 0, DMUS_PATH_PRIMARY_BUFFER, 0, GUID_All_Objects, 0, IID_IDirectSound3DListener, (void**)&m_listener );
	SAFE_RELEASE( audioPath3D );

	// Устанавливаем масштаб и фактор удаления (distance factor).
	m_scale = scale;
	m_listener->SetDistanceFactor( m_scale, DS3D_IMMEDIATE );
}
...


Сразу после создания объекта загрузчика, таким же образом создаём объект Perfomance. Для этого снова вызываем функцию CoCreateInstance для создания объекта COM-интерфейса. Разве что теперь мы указываем параметры, необходимые для создания Perfomance, а не загрузчика. В качестве идентификатора класса, на базе которого создаётся объект используем CLSID_DirectMusicPerfomance, а в качестве ссылки на интерфейс - IID_IDirectMusicLoader8. В последнем параметре передаём указатель m_perfomance для сохранения в нём указателя на только что созданный объект Perfomance.

Теперь, когда мы создали 2 ключевых объекта, можем безболезненно начинать процесс инициализации:

Фрагмент файла SoundSystem.cpp (Проект Engine)
...
	m_performance->InitAudio( NULL, NULL, NULL, DMUS_APATH_SHARED_STEREOPLUSREVERB, 8, DMUS_AUDIOF_ALL, NULL );
...

Основная задача функции InitAudio - это инициализация Perfomance-a и его подготовка к воспроизведению звуков. Данная функция экспонирована нашим объектом perfomance. Вот её прототип:

Прототип функции InitAudio
HRESULT InitAudio(
	IDirectMusic** ppDirectMusic, // Адрес указателя на объект интерфейса DirectMusic.
	IDirectSound** ppDirectSound, // Адрес указателя на объект интерфейса DirectSound.
	HWND hWnd,   // Дескриптор окна, используемого DirectSound.
	DWORD dwDefaultPathType,  // Аудиопуть по умолчанию.
	DWORD dwPChannelCount,  // Число каналов perfomance-a, которое будет выделено.
	DWORD dwFlags,   // Флаги запроса специальных фич.
	DMUS_AUDIOPARAMS *pParams // Адрес структуры DMUS_AUDIOPARAMS *pParams.
);

Выглядит громоздко. Но на деле тут всё просто. Ведь в ншем случае первый, второй, третий и последний параметр мы установили в NULL, что даёт т.н. "поведение по умолчанию", что нам и требуется.
Первые два параметра позволяют указать объекты DirectMusic и DirectSound, с которыми будет работать наш perfomance. Оба установлены в NULL. В данном случае это означает, что perfomance создаст оба этих объекта автоматически, только лишь для частного использования в своих целях. Это облегчает нам задачу, т.к. нет необходимости получать доступ к каждому из этих объектов.
Мы также устанавливаем в NULL третий параметр, что заставляет perfomance использовать в качестве рабочего окна дескриптор текущего активного, расположенного поверх остальных (foreground), окна для присоединения к нему объекта DirectSound. Раз уж у нашего приложения всего одно окно и оно всегда расположено поверх остальных, то при создании perfomance-a нам также не требуется указывать дескриптор окна.

Perfomance может также содержать собственный аудиопуть по умолчанию, что позволяет быстро воспроизвести звук без необходимости предварительного создания отдельного аудиопути. В нашем случае мы как раз и используем этот "встроенный" (internal) в perfomance аудиопуть по умолчанию, указав DMUS_APATH_SHARED_STEREOPLUSREVERB в четвёртом параметре, где указывается тип аудиопути, используемого perfomance-ом. Если его установить в NULL, то perfomance не будет использовать встроенный аудиопуть. В таблице 1 указаны другие возможные значения параметра DWORD dwDefaultPathType функции InitAudio:

Таблица 1. Возможные значения параметра dwDefaultPathType функции InitAudio

Значение Описание
DMUS_APATH_DYNAMIC_3D Использует встроенный в perfomance 3D-аудиопуть для воспроизведения 3D-звука.
DMUS_APATH_DYNAMIC_MONO Использует встроенный в perfomance аудиопуть для воспроизведения одноканального звука в режиме моно.
DMUS_APATH_DYNAMIC_STEREO Использует встроенный в perfomance аудиопуть для воспроизведения двухканального звука в режиме стерео.
DMUS_APATH_DYNAMIC_STEREOPLUSREVERB Использует встроенный в perfomance аудиопуть для воспроизведения двухканального звука в режиме стерео c эффектом ревербации.


Пятый параметр dwPChannelCount указывает на количество каналов perfomance-a, которое необходимо выделить. Каждый аудиопуть обязательно использует один из этих каналов при воспроизведении звука. Это означает, что чем больше каналов perfomance-a будет выделено, тем большее число звуков может быть воспроизведено одновременно. Если количество этих каналов превысит пределы разумного, это может негативно сказаться на быстродействии и расходе лперативной памяти. Поэтому хорошей идеей будет позволить игроку самостоятельно указывать в настройках число каналов, в зависимости от быстродействия его компьютера. По умолчанию мы установим у данного параметра значение равное 8, что должно быть достаточно в большинстве случаев.

Шестой параметр dwFlags позволяет устанавливать специальные возможности (=фичи, техники, команды обработки), которые будет использовать perfomance. В нашем случае мы передаём в данном параметре специальный флаг DMUS_AUDIOF_ALL, означающий, что мы будем использовать все доступные фичи. Это наиболее распространённая опция, подходящая для большинства ситуаций.

В последнем параметре *pParams передаём указатель на созданную ранее структуру DMUS_AUDIOPARAMS, в которой хранятся пользовательские настройки работы синтезатора. Данный указатель также позволяет получать текущие установки синтезатора уже после инициализации звуковой системы. В данный момент нам не требуются данные настройки, поэтому выставляем здесь NULL.

На этом шаге инициализация perfomance-a окончена и он полностью готов к воспроизведению звуков.
Тем не менее, для использования режима проигрывания 3D-звука нам необходимо получить доступ к объекту DirectSound 3D-listener (3D-слушатель), что мы и делаем дальше:

Фрагмент SoundSystem.cpp (Проект Engine)
...
	// Получаем 3D-слушателя (3D listener) путём создания 3D аудиопути и затем запрашивая
	// слушателя из этого аудиопути. Аудиопуть (audio path) затем может быть освобождён (released).
	IDirectMusicAudioPath8 *audioPath3D;
	m_performance->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_3D, 1, true, &audioPath3D );
	audioPath3D->GetObjectInPath( 0, DMUS_PATH_PRIMARY_BUFFER, 0, GUID_All_Objects, 0, IID_IDirectSound3DListener, (void**)&m_listener );
	SAFE_RELEASE( audioPath3D );
...


Объект DirectSound 3D-listener представлен интерфейсом IDirectSound3DListener8. Проще всего представить этого самого виртуального слушателя в виде микрофона, размещённого в 3D-пространстве. Как и к обычным объектам, к данному микрофону можно применять различные преобразования: устанавливать позицию, ориентацию и скорость движения. DirectSound будет использовать этот микрофон-слушатель для корректного просчёта всех параметров, которые влияют на параметры проигрывания 3D-звуков в сцене, учитывая его текущее положение (позицию), ориентацию и скорость (причём, как микрофона, так и звука!). Самый яркий пример - когда DirectSound использует скорости движения микрофона и звука для воссоздания реального физического феномена т.н. эффекта Допплера(external link), когда частота звука сигнала грузовика, проехавшего мимо слушателя, постепенно снижается по мере удаления.
Так вот. Для создания 3D-слушателя необходимо сперва создать 3D-аудиопуть (audiopath3D). А уже из этого 3D-аудиопути мы получаем указатель слушателя, использующего его. Вообще этот тот же самый слушатель, который используется всеми аудиопутями. Это связано с тем, что он изначально связан с объектом (инстансом) DirectSound, созданным нашим perfomance-ом. У созданного объекта DirectSound может быть всего 1 слушатель. Поэтому неважно, какой из аудиопутей мы используем для получения указателя на него. По этой причине мы создаём временный 3D-аудиопуть (audioPath3D) и запрашиваем у него указатель слушателя. После этого данный аудиопуть нам становится больше не нужен и мы удаляем его, вызвав макрос SAFE_RELEASE( audioPath3D ). В результате данной операции мы получили указатель на слушателя, который валиден в течение всей жизни нашей системы поддержки звука.
Сейчас мы не будем вдаваться в подробности создания аудиопути. Но обязательно рассмотрим эту тему позднее. Вообще, судя по коду, данная операция совсем не трудная.
Как только мы создали 3D-аудиопуть, мы тут же запрашиваем у него слушателя путём вызова функции GetObjectInPath, экспонированной интерфейсом IDirectMusicAudioPath8. Эта полезная функция позволяет получить доступ ко многим компонентам аудиопути, причём не только к тем, которые связаны с аудиопутём. Сегменты (segments) и звуковые буферы (sound buffer) также экспонируют свои собственные версии функции GetObjectInPath, позволяющие получить доступ к внутренним компонентам данных объектов. Вот её прототип:

Прототип функции GetObjectInPath
HRESULT IDirectMusicAudioPath8::GetObjectInPath(
     DWORD   dwPChannel,   // Каналы perfomance-a (в нашем случае DMUS_PCHANNEL_ALL) для поиска
     DWORD   dwStage,      // Stage in audiopath (в нашем случае DMUS_PATH_PRIMARY_BUFFER) - этап аудиопути
     DWORD   dwBuffer,     // 0 (индекс в цепочке буферов)
     REFGUID guidObject,   // GUID_All_Objects. Класс запрашиваемого объекта.
     DWORD   dwIndex,      // 0 (индекс объекта в буфере при множественном совпадении)
     REFGUID iidInterface, // Идентификатор интерфейса желаемого объекта
     void    **ppObject);  // Указатель, в который сохраняем указатель интерфейса.

Первые 4 параметра нам мало интересны, поэтому в них мы выставляем значения по умолчанию. Также эти параметры по умолчанию означают, что мы ищем все объекты на первом канале perfomance-a первичного буфера, используемого аудиопутём. Последние два параметра куда интереснее.
В пятом параметре мы указываем IID_IDirectSound3DListener что бы искать именно интерфейс 3D-слушателя.
В последнем параметре мы передаём адрес нашего слушателя m_listener, в котором будет храниться указатель на интерфейс слушателя сразу после завершения выполнения функции.

Теперь, когда у нас есть слушатель, нам больше не нужен 3D-аудиопуть и мы его спокойно удаляем:

Фрагмент SoundSystem.cpp (Проект Engine)
...
	SAFE_RELEASE( audioPath3D );
...


Завершающий шаг - установить масштаб (scale), с которым будет работать наш слушатель и который часто называют фактором дистанции (Distance factor):

Фрагмент SoundSystem.cpp (Проект Engine)
...
	// Устанавливаем масштаб и фактор удаления (distance factor).
	m_scale = scale;
	m_listener->SetDistanceFactor( m_scale, DS3D_IMMEDIATE );
...

Сперва мы присваиваем глобальное значение масштаба движка (scale) нашему переменному члену (m_scale).
Затем вызываем функцию SetDistanceFactor, где указываем применить выбранный масштаб к текущему слушателю. Второй её параметр DS3D_IMMEDIATE даёт команду применить данные настройки немедленно.

Закрыть
noteОбрати внимание

Альтернативно во втором параметре функции SetDistanceFactor можно указать флаг DS3D_DEFERRED, указывающий на приостановку внесения любых изменений, вплоть до вызова специальной функции CommitDeferredSettings (указав в параметре нашего слушателя). Флаг DS3D_DEFERRED часто применяется для применения различных установок, оказывающих влияние на быстродействие движка (т.е. критичных по времени своего выполнения, например при рендеринге сцены в реальном времени). Например, при одновременном изменении положения (position), ориентации (orientation) и скорости движения (velocity) слушателя должен обязательно применяться флаг DS3D_DEFERRED. Иначе возможно заметное замедление работы игры, лаги. Указание флага DS3D_DEFERRED должно обязательно сопровождаться вызовом функции CommitDeferredSettings, подтверждающей внесение изменений. Её обычно вызывают на экране загрузки, при показе видеофрагмента, заставки и т.д. То есть, там, где замедление работы игры наименее заметно.


Вот и весь конструктор)). С этого момента система поддержки звука полностью настроена и готова к работе.
Прежде чем продолжить, рассмотрим деструктор класса SoundSystem. В нём система поддержки звука закрывается, освобождая память.

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The sound system class destructor.
//-----------------------------------------------------------------------------
SoundSystem::~SoundSystem()
{
	// Закрываем и освобождаем DirectMusic Performance.
	m_performance->CloseDown();
	SAFE_RELEASE( m_performance );

	// Освобождаем закгрузчик (loader) DirectMusic.
	SAFE_RELEASE( m_loader );
}
...

Для закрытия perfomance-a достаточно вызвать функцию CloseDown. После этого мы безопасно уничтожаем объекты perfomance-a и загрузчика (loader).

Реализация класса Sound

Сперва рассмотрим конструктор класса Sound, размещённый в SoundSystem.cpp:

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The sound class constructor.
//-----------------------------------------------------------------------------
Sound::Sound( char *filename )
{
	// Конвертируем имя файла в формат wide character string.
	WCHAR *wideFilename = new WCHAR[strlen( filename ) + 1];
	MultiByteToWideChar( CP_ACP, 0, filename, -1, wideFilename, strlen( filename ) + 1 );
	wideFilename[strlen( filename )] = 0;

	// Загружаем звуковой файл, затем уничтожаем имя файла.
	g_engine->GetSoundSystem()->GetLoader()->LoadObjectFromFile( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8, wideFilename, (void**)&m_segment );
	SAFE_DELETE( wideFilename );

	// Загружаем данные сегмента (segment's data) в Perfromance.
	m_segment->Download( g_engine->GetSoundSystem()->GetPerformance() );
}
...


В качестве вводного параметра конструктор принимает имя файла. Причём это обязательно должно быть полное имя файла, включающее в себя путь к нему.
Первым делом имя файла преобразуется в строку с многобайтовой (widechar, Unicode) кодировкой, так как только такие строки принимает функция LoadObjectFromFile, расположенная чуть ниже. Вот её прототип:

Прототип (шаблон) функции LoadObjectFromFile
HRESULT LoadObjectFromFile
{
  REFGUID rguidClassID,  // Идентификатор класса объекта
  REFIID iidInterfaceID,  // Идентификатор интерфейса
  WCHAR *pwzFiePath,  // Имя файла и полный путь к нему
  void ** ppObject  // Адрес,  который будет сохранён указатель интерфейса
}


В нашем случае в первом и втором параметрах данной функции мы передаём уникальные идентификаторы класса и интерфейса соответственно, которые мы создали. В нашем случае это идентификаторы класса и интерфейса звукового сегмента (segment). Поэтому в качестве первого параметра указываем CLSID_DirectMusicSegment в качестве класса и IID_IDirectMusicSegment8 в качестве имени интерфейса. В третьем параметре указываем полное имя (имя файла + путь) звукового ресурса, который будет загружен в звуковой сегмент. В последнем параметре указывается адрес переменной, в которой будет храниться указатель на только что созданный интерфейс (в нашем случае - m_segment).

Завершающий шаг - загрузка данных сегмента в перфоманс для последующего проигрывания. Здесь применяется функция Download, в которую передаётся указатель на перфоманс, в который будут загружены данные.

В деструкторе класса Sound выполняются функции по уничтожению более неиспользуемого звукового ресурса и выгрузке (функция Unload) данных сегмента из перфоманса:

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The sound class destructor.
//-----------------------------------------------------------------------------
Sound::~Sound()
{
	// Выгружаем данные сегмента (segment's data) из Perfromance-а.
	m_segment->Unload( g_engine->GetSoundSystem()->GetPerformance() );
	
	// Убираем сегмент из загрузчика DirectMusic, затем освобождаем (удаляем) его.
	g_engine->GetSoundSystem()->GetLoader()->ReleaseObjectByUnknown( m_segment );
	SAFE_RELEASE( m_segment );
}
...

Сегмент также удаляется из загрузчика (loader) и бережно удаляется через макрос SAFE_RELEASE.

Функция Sound::Play

  • Позволяет проигрывать звуковой сегмент на аудиопути по умолчанию, который мы предварительно создали для перфоманса. Вот её реализация, размещённая в SoundSystem.cpp:

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Воспроизводим звук на стандартном аудиопути с зацикливанием (бесконечным повторением) или без него.
// Внимание: Звуковые сегменты могут обслуживать тольго один стейт зацикливания (looping state).
// Во всех случаях смена флага зацикливания повлияет на все инстансы данного звука.
//-----------------------------------------------------------------------------
void Sound::Play( bool loop, unsigned long flags )
{
	if( loop == true )
		m_segment->SetRepeats( DMUS_SEG_REPEAT_INFINITE );
	else
		m_segment->SetRepeats( 0 );

	g_engine->GetSoundSystem()->GetPerformance()->PlaySegment( m_segment, flags, 0, NULL );
}
...


В качестве первого вводного параметра функция Play принимает указатель на проигрываемый звуковой сегмент.
Во втором параметре указываются различные опциональные флаги, изменяющие различные параметры воспроизводимого звука. Все они содержатся в перечислении DMUS_SEGF_FLAGS:

Перечисление (энумерация) DMUS_SEGF_FLAGS
typedef enum enumDMUS_SEGF_FLAGS {
  DMUS_SEGF_REFTIME = 64,
  DMUS_SEGF_SECONDARY = 128,
  DMUS_SEGF_QUEUE = 256,
  DMUS_SEGF_CONTROL = 512
  DMUS_SEGF_AFTERPREPARETIME = 1<<10,
  DMUS_SEGF_GRID = 1<<11,
  DMUS_SEGF_BEAT = 1<<12,
  DMUS_SEGF_MEASURE = 1<<13,
  DMUS_SEGF_DEFAULT = 1<<14,
  DMUS_SEGF_NOINVALIDATE = 1<<15,
} DMUS_SEGF_FLAGS;

Вот их описание:
Таблица 2. Описание элементов перечиления (энумерации) DMUS_SEGF_FLAGS

Значение Описание
DMUS_SEGF_REFTIME Время проигрывания.
DMUS_SEGF_SECONDARY Указатель на второй сегмент.
DMUS_SEGF_QUEUE Устанавливается в конце главной (primary) очереди сегментов.
DMUS_SEGF_CONTROL Играет роль контрольного сегмента (control segment). Только для вторичных (secondary) сегментов. По умолчанию первичный сегмент всегда является контрольным. В одно и то же время контрольным может быть только один сегмент. По умолчанию только контрольный сегмент может посылать сообщения о темпе произведения.
DMUS_SEGF_AFTERPREPARETIME Воспроизвести сегмент только после т.н. "времени приготовления".
DMUS_SEGF_GRID Воспроизвести сегмент в пределах "сетки".
DMUS_SEGF_BEAT Воспроизвести сегмент в пределах двух соседних звуков ударных инструментов (битов).
DMUS_SEGF_MEASURE Воспроизвести сегмент в пределах "линейки" (выделенной области).
DMUS_SEGF_DEFAULT Воспроизвести сегмент в пределах временного отрезка звучания самого сегмента.
DMUS_SEGF_NOINVALIDATE Указание данного флага в функции IDirectMusicPerformance::PlaySegment для основного (primary) или контрольного (control) сегментов приведёт к тому, что у нового сегмента будет отсутствать встроенная процедура инвалидации (отмены) по завершении проигрывания. Без данного флага (по умолчанию) по окончании проигрывания происходит инвалидация, включающая себя удаление сегмента и всех его т.н. "пометок" (notes). Данный флаг обычно комбинируют с флагом DMUS_SEGF_AFTERPREPARETIME, чтобы пометки нового сегмента не накладывались на пометки предыдущего.


Внутри функции Play мы проверяем наличие/отсутствие флага loop. Если он установлен в TRUE, то устанавливаем количество повторов воспроизведения в бесконечность (флаг DMUS_SEG_REPEAT_INFINITE). Если FALSE - устаналиваем в 0 (NULL) для проигрывания сегмента всего 1 раз. Здесь на помощь приходит функция SetRepeats.
В конце тела фнукции Play мы вызываем функцию PlaySegment, экспонированную перфомансом в классе SoundSystem. Вот её прототип:

Прототип функции PlaySegment
HRESULT PlaySegment(
  IDirectMusicSegment* pSegment,  // Воспроизводимый сегмент
  DWORD dwFlags,  // Управляющие флаги
  _int64  i64StartTime,  // Время начала воспроизведения
  IDirectMusicSgmentState** ppSegmentState  // Сохранённое состояние сегмента
);

Вот описание её параметров:

Параметр Описание
pSegment Время проигрывания. В нашем случае передаём m_segment для воспроизведения данного сегмента целиком.
dwFlags Управляющие флаги. Полный список см. в Таблице 2, чуть выше. Передаём flags, как и в функции Sound::Play.
i64StartTime Время начала воспроизведения. Указываем 0 для воспроизведения сегмента с самого начала.
ppSegmentState Указатель на сохранённое состояние (state) данного экземпляра воспроизводимого сегмента. Это позволяет изменять параметры данного экземпляра сегмента прямо во время воспроизведения. Нам это сейчас не нужно, поэтому выставляем в NULL.


///
Вот мы и рассмотрели класс Sound.
В нём есть ещё одна функция, на которой следует остановиться более подробно - GetSegment.

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Возвращает звуковой сегмент.
//-----------------------------------------------------------------------------
IDirectMusicSegment8 *Sound::GetSegment()
{
	return m_segment;
}
...


Всё, что она делает - так это возвращает указатель m_segment, который позднее пригодится для проигрывания звуков через 3D аудиопути.

На данном этапе мы почти подготовили к работе систему поддержки звука. Осталось рассмотреть всего 1 класс - AudioPath3D, который позволит нам создавать 3D-аудиопути для проигрывания звуков в трёхмерном пространстве, что на самом деле очень круто.

Реализация класса AudioPath3D

К настоящему времени у тебя должно быть чёткое понимание того, как работают аудиопути и для чего нужны 3D-аудиопути. Если это не совсем так, то просто изучи класс AudioPath3D - там всё предельно ясно. Напомним, что никакая теория не заменит практическое изучение реального исходного кода. В данном курсе теоретическая часть сведена к минимуму и мы всё внимание сосредотачиваем на практических навыках по работе с программным кодом.
При беглом осмотре содержимого SoundSystem.cpp, можно увидеть, что вся реализация класса AudioPath3D разбита на несколько обособленных функций.

Начнём с реализации конструктора класса AudioPath3D:

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The audio path 3D class constructor.
//-----------------------------------------------------------------------------
AudioPath3D::AudioPath3D()
{
	// Создаём аудиопуть. После этого можно индивидуально устанавливать параметры 3D-звучания.
	g_engine->GetSoundSystem()->GetPerformance()->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_3D, 1, true, &m_audioPath );

	// Получаем звуковой буфер аудиопути.
	m_audioPath->GetObjectInPath( DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER, 0, GUID_NULL, 0, IID_IDirectSound3DBuffer, (void**)&m_soundBuffer );
}
...

Конструктор состоит всего из двух строк кода, но для подготовки к работе 3D-аудиопути больше и не требуется.
Первым делом вызываем функцию CreateStandardAudioPath, экспонированную перфомансом нашего класса SoundSystem. Ты уже видел применение данной функции в конструкторе класса SoundSystem. Рассмотрим её прототип:

Прототип функции CreateStandardAudioPath
HRESULT CreateStandardAudioPath(
  DWORD dwType,  // Тип создаваемого аудиопути
  DWORD dwChannelCount,  // Количество каналов
  BOOL fActivate, // Активировать сразу после создания
  IDirectMusicAudioPath **ppNewPath  // Адрес переменной, в которой будет храниться указатель на интерфейс  создаваемого аудиопути
);

Вот описание её параметров:
Параметры функции CreateStandardAudioPath

Параметр Описание
dwType Указываем тип создаваемого аудиопути. Возможные значения: DMUS_APATH_DYNAMIC_3D, DMUS_APATH_DYNAMIC_MONO, DMUS_APATH_DYNAMIC_STEREO, DMUS_APATH_DYNAMIC_STEREOPLUSREVERB. См. Таблицу 1 в этой Главе. В нашем случае указываем DMUS_APATH_DYNAMIC_3D.
dwChannelCount Число каналов перфоманса. В нашем случае указываем 1 для воспроизведения через аудиопуть лишь одного звука за раз.
fActivate Указывает, должен ли аудиопуть быть активным сразу после своего создания. В нашем случае устанавливаем в true, т.к. аудиопуть чаще всего создаётся для немедленного применения.
**ppNewPath Адрес указателя, в который будет сохраняться указатель интерфейса создаваемого пути. В нашем случае указываем заранее подготовленную переменную m_audioPath.

Теперь, когда 3D-аудиопуть создан, нам необходимо выделить из него 3D звуковой буфер (3D sound buffer) для установки различных свойств, как например положение в 3D-пространстве и скорость перемещения. Выделение 3D-буфера осуществляется путём вызова функции GetObjectInPath и передачи в неё указателя вновь созданного аудиопути. Ты также мог видеть данную функцию в деле при конструкторе класса SoundSystem. В тот раз мы применяли её для получения указателя глобального 3D-слушателя (global 3D-listener). В этот раз мы используем её для получения указателя 3D звукового буфера, ассоциированного с данным аудиопутём. Мы используем данную функцию практически в неизменном виде, за исключением того, что мы изменили в ней некоторые параметры. Вот прототип функции GetObjectInPath:

Прототип функции GetObjectInPath
HRESULT IDirectMusicAudioPath8::GetObjectInPath(
     DWORD   dwPChannel,   // Каналы для поиска
     DWORD   dwStage,      // Сцена, дорожка (stage) аудиопути
     DWORD   dwBuffer,     // Индекс в цепочке буферов
     REFGUID guidObject,   // Класс объекта
     DWORD   dwIndex,      // Индекс объекта в буфере
     REFGUID iidInterface, // GUID желаемого объекта
     void    **ppObject);  // Указатель на создаваемый объект

Вот описание её параметров:
Параметры функции GetObjectInPath

Параметр Описание
dwPChannel Каналы для поиска. В нашем случае указываем DMUS_PCHANNEL_ALL для поиска во всех возможных каналах перфоманса. Напомним, что наш аудиопуть имеет всего 1 канал.
dwStage Сцена, дорожка (stage) аудиопути, где будем искать объект. В нашем случае указываем DMUS_PATH_BUFFER для поиска в звуковом буфере.
dwBuffer Индекс искомого объекта в цепочке буферов. Указываем 0, т.к. нас не интересует поиск всего объекта целиком, а нужен лишь его интерфейс.
guidObject Класс искомого объекта. Указываем GUID_NULL, т.к. нас не интересует поиск всего объекта целиком, а нужен лишь его интерфейс.
dwIndex Индекс искомого объекта в буфере. Указываем 0, т.к. нас не интересует поиск всего объекта целиком, а нужен лишь его интерфейс.
iidInterface GUID искомого объекта. В нашем случае указываем идентификатор интерфейса IID_IDirectSound3DBuffer т.к. ищем интерфейс 3D звукового буфера, который будет использоваться данным аудиопутём.
**ppObject Указатель, который будет хранить указатель интерфейса 3D звукового буфера. В нашем случае - (void**)&m_soundBuffer.


Деструктор класса AudioPath3D
Как и во всех остальных случаях, мы бережно удаляем более неиспользуемые объекты путём вызова деструктора. При уничтожении аудиопути вызывается деструктор класса AudioPath3D, который просто "освобождает" интерфейс звукового буфера и текущий аудиопуть. Вот его реализация в SoundSystem.cpp:

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The audio path 3D class destructor.
//-----------------------------------------------------------------------------
AudioPath3D::~AudioPath3D()
{
	SAFE_RELEASE( m_soundBuffer );
	SAFE_RELEASE( m_audioPath );
}
...

Другие функции

В листинге SoundSystem.cpp представлено ещё несколько функций, экспонированных классом SoundSystem. И принцип их работы довольно прозрачен:

SetVolume Позволяет изменить общую громкость звуковой системы, которая влияет на все воспроизводимые через неё звуки.
GetLoader Возвращает указатель на объект загрузчик (loader object).
GetPerfomance Возвращает указатель на объект perfomance-a.
GarbageCollection "Сборщик мусора". По умолчанию объект загрузчика использует т.н. автоматическое кэширование (automatic caching). Это означает, что любой объект, загруженный загрузчиком, а также любые другие объекты, которые на него ссылаются, обязательно кэшируются загрузчиком для более быстрого и простого доступа к ним. При наличии множества объектов объём кэша может здорово вырасти и превысить допустимые лимиты. При этом далеко не все объекты в нём используются в данное время. Для снижения давления на кэш загрузчика можно применять функцию GarbageCollection, которая в свою очередь вызывает внутреннюю функцию загрузчика CollectGarbage. Это позволяет загрузчику удалять из своего кэша неиспользуемые объекты. Так как это довольно ресурсоёмкая операция, её не рекомендуется выполнять в коде, критичном по времени выполнения (например, при 3D-рендеринге). В нашем движке мы установим вызов функции GarbageCollection при каждой смене стейта, т.к. это наиболее вероятный момент, когда многие объекты перестают использоваться.
UpdateListener Данную функцию рассмотрим более подробно чуть ниже...

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Обновляем 3D-слушателя (3D listener) звуковой системы.
//-----------------------------------------------------------------------------
void SoundSystem::UpdateListener( D3DXVECTOR3 forward, D3DXVECTOR3 position, D3DXVECTOR3 velocity )
{
	// Устанавливаем ориентацию слушателя.
	m_listener->SetOrientation( forward.x, forward.y, forward.z, 0.0f, 1.0f, 0.0f, DS3D_DEFERRED );

	// Масштабируем и устанавливаем позицию слушателя.
	position *= m_scale;
	m_listener->SetPosition( position.x, position.y, position.z, DS3D_DEFERRED );

	// Масштабируем и устанавливаем скорость слушателя.
	velocity *= m_scale;
	m_listener->SetVelocity( velocity.x, velocity.y, velocity.z, DS3D_DEFERRED );

	// Подтверждаем выбранные установки.
	m_listener->CommitDeferredSettings();
}
...


В качестве параметров функция UpdateListener принимает 3 вектора, которые означают направление взгляда слушателя (forward), его положение (position) и скорость движения (velocity) в 3D-пространстве. Если не знаком с понятием "вектор", чуть ниже будеть теоретическая вставка на эту тему.
К веторам положения и скорости мы применяем текущий масштаб, установленный для нашей системы поддержки звука (m_scale). Ты также заметил флаги DS3D_DEFERRED, которые указаны при установке новых значений векторов. После этого, как и положено, мы подтверждаем внесённые изменения путём вызова функции CommitDeferredSettings.

Функция AudioPath3D::SetPosition

  • Устанавливает позицию (положение) звука в 3D-пространстве.
  • В качестве вводного параметра принимает вектор позиции (position vector).
  • Вызывается из 3D звукового буфера аудиопути.
  • Позиция (вектор позиции) масштабируется с учётом действующего масштаба путём запроса текущего значения параметра scale.
Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Устанавливаем позицию звука в 3D-пространстве.
//-----------------------------------------------------------------------------
void AudioPath3D::SetPosition( D3DXVECTOR3 position )
{
	position *= g_engine->GetScale();
	m_soundBuffer->SetPosition( position.x, position.y, position.z, DS3D_IMMEDIATE );
}
...


Функция AudioPath3D::SetVelocity

  • Устанавливает скорость движения звука в 3D-пространстве.
  • В качестве вводного параметра принимает вектор скорости (velocity vector).
  • Вызывается из 3D звукового буфера аудиопути.
  • Скорость (вектор скорости) масштабируется с учётом действующего масштаба путём запроса текущего значения параметра scale.
Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Устанавливаем скорость движения (источника) звука в 3D-пространстве.
//-----------------------------------------------------------------------------
void AudioPath3D::SetVelocity( D3DXVECTOR3 velocity )
{
	velocity *= g_engine->GetScale();
	m_soundBuffer->SetVelocity( velocity.x, velocity.y, velocity.z, DS3D_IMMEDIATE );
}
...

Закрыть
noteОбрати внимание

Обе вышеперечисленные функции для своего выполнения используют параметр (макрос) DS3D_IMMEDIATE (="выполнить немедленно") вместо DS3D_DEFFERED (="внести изменения позднее, путём вызова спец. функции"). Флаг DS3D_DEFFERED в основном используется для внесения изменений в несколько свойств одновременно. Т.к. функции SetPosition и SetVelocity изменяют всего одно свойство и нет гарантии, что они не будут вызваны одновременно, то лучшим решением будет внести изменения немедленно (флаг DS3D_IMMEDIATE).


Функция AudioPath3D::SetMode

  • Совсем небольшая, но вызывающая большой интерес, т.к. будет использоваться неоднократно при создании игры во второй части данного курса.
  • Позволяет указывать, каким образом звуковой буфер аудиопути будет обрабатывать 3D-звук.
  • Обычно вызывается из звукового буфера.
Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Устанавливаем режим, в котором аудиопуть будет воспроизводить звук.
//-----------------------------------------------------------------------------
void AudioPath3D::SetMode( unsigned long mode )
{
	m_soundBuffer->SetMode( mode, DS3D_IMMEDIATE );
}
...
  • Во втором параметре также указывается флаг DS3D_IMMEDIATE для внесения изменений немедленно.

Возможные значения первого параметра mode:

Значение параметра Описание
DS3DMODE_DISABLE Обработка 3D-звука отключена. Звуки, воспроизводимые через данный звуковой буфер не имеют каких-либо 3D-возможностей.
DS3DMODE_HEADRELATIVE Свойства 3D-звука, как например положение в пространстве и скорость перемещения, задаются относительно 3D-слушателя (3D-listener; т.н. "виртуальный микрофон"). Другими словами, указав позицию (5.0, 0.0, 0.0) наш звук будет расположен на расстоянии 5 единиц от слушателя по оси X. Причём вне зависимости от того, где расположен сам слушатель. Звук будет неподвижен относительно слушателя, куда бы тот не переместился.
DS3DMODE_NORMAL Нормальная обработка 3D-звука. Здесь позиция (5.0, 0.0, 0.0) означает, что звук воспроизводится, будучи расположен по данным координатам в абсолютных координатах 3D-пространства. Звук будет неподвижен относительно текущих координат пространства, в независимости от перемещений слушателя. Используется по умолчанию.

Функция AudioPath3D::Play

  • Позволяет проигрывать звуковой сегмент на текущем аудиопути. Вот её реализация, размещённая в самом конце файла SoundSystem.cpp:

Фрагмент SoundSystem.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Воспроизводим звук на 3D-аудиопути с зацикливанием (бесконечным повторением) или без него.
// Внимание: Звуковые сегменты могут обслуживать тольго один стейт зацикливания (looping state).
// Во всех случаях смена флага зацикливания повлияет на все инстансы данного звука.
//-----------------------------------------------------------------------------
void AudioPath3D::Play( IDirectMusicSegment8 *segment, bool loop, unsigned long flags )
{
	if( loop == true )
		segment->SetRepeats( DMUS_SEG_REPEAT_INFINITE );
	else
		segment->SetRepeats( 0 );

	g_engine->GetSoundSystem()->GetPerformance()->PlaySegmentEx( segment, NULL, NULL, flags, 0, NULL, NULL, m_audioPath );
}


В качестве первого вводного параметра функция Play принимает указатель на проигрываемый звуковой сегмент. Здесь также пригодится служебная функция GetSegment класса SoundSystem. Теоретически мы могли бы просто передать здесь указатель на инстанс класса Sound, но в этом случае мы не смогли бы использовать класс AudioPath3D с его сегментами. Это сильно снизило бы гибкость (flexibility) класса AudioPath3D.
Во втором параметре указываются различные опциональные флаги, изменяющие различные параметры воспроизводимого звука. Все они содержатся в перечислении DMUS_SEGF_FLAGS:

Перечисление (энумерация) DMUS_SEGF_FLAGS
typedef enum enumDMUS_SEGF_FLAGS {
  DMUS_SEGF_REFTIME = 64,
  DMUS_SEGF_SECONDARY = 128,
  DMUS_SEGF_QUEUE = 256,
  DMUS_SEGF_CONTROL = 512
  DMUS_SEGF_AFTERPREPARETIME = 1<<10,
  DMUS_SEGF_GRID = 1<<11,
  DMUS_SEGF_BEAT = 1<<12,
  DMUS_SEGF_MEASURE = 1<<13,
  DMUS_SEGF_DEFAULT = 1<<14,
  DMUS_SEGF_NOINVALIDATE = 1<<15,
} DMUS_SEGF_FLAGS;

Их описание представлено в Таблице 2 данной Главы.
Внутри функции Play мы проверяем наличие/отсутствие флага loop. Если он установлен в TRUE, то устанавливаем количество повторов воспроизведения в бесконечность (флаг DMUS_SEG_REPEAT_INFINITE). Если FALSE - устаналиваем в 0 (NULL) для проигрывания сегмента всего 1 раз. Здесь на помощь приходит функция SetRepeats.
В конце тела фнукции Play мы вызываем функцию PlaySegmentEx, экспонированную перфомансом в классе SoundSystem. Вот её прототип:

Прототип функции PlaySegmentEx
HRESULT IDirectMusicPerformance8::PlaySegmentEx(
    IUnknown*        pSource,  // Воспроизводимый сегмент
    WCHAR*           pwzSegmentName,  // NULL - не используется
    IUnknown*        pTransition,   // Сегмент перехода - используйте NULL
    DWORD            dwFlags,  // Флаги изменения поведения
    __int64          i64StartTime,  // Когда начинать воспроизведение (0 - немедленно)
    IDirectMusicSegmentState**   ppSegmentState,  // Указатель на объект, получающий объект состояния сегмента
    IUnknown*        pFrom,  // NULL
    IUnknown*        pAudioPath);  // Используемый аудио-путь или NULL для аудио-пути по умолчанию


В функции Play класса Sound для проигрывания сегмента мы использовали другую функцию - PlaySegment, которая имеет всего 4 параметра. Здесь же нам необходима именно функция PlaySegmentEx, т.к. только в неё в качестве последнего параметра можно передать указатель желаемого аудиопути. При использовании функции PlaySegment все звуки воспроизводятся через аудиопуть по умолчанию текущего перфоманса.

Векторы в 3D-пространстве (теоретическая вставка)

Вектор является фундаментальным понятием для 3D-математики, повсеместно применяемой в программировании трёхмерной графики. Данная подглава:

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

3D-векторы часто рассматриваются как некие величины, имеющие размер (длину) и направление (направление силы, скорости, ускорения или простого смещения). Например, ты смотришь на автомобиль, едущий строго на север со скоростью 60 км/ч и можешь сказать, что данное авто имеет вектор скорости "60 км/ч север". Если бы этот автомобиль был в игре, то про него в этом случае можно сказать, что он имеет вектор скорости 60 км/ч, направленный по оси Z (предположим, что ось Z указывает на север).
Векторы, с которыми ты чаще всего будешь иметь дело, - 3D-векторы, которые имеют компоненты x, y и z, соответствующие их координатам в пространстве. Библиотека D3DX предоставляет базовую структуру (тип) D3DXVECTOR3, которую мы и будем применять в течение всего курса. Возвращаясь к нашему примеру с автомобилем, можем записать наш вектор скорости как (0.0, 0.0, 60.0), что означает движение автомобиля вдоль положительного луча оси Z в степени 60 единиц. Как часто автомобиль проделывает данное движение (например, каждый кадр, каждую секунду или час) - решает игрокодер, но это значение сильно зависит от выбранного масштаба (scale).

Рис.2 Типы векторов в трёхмерном пространстве
Рис.2 Типы векторов в трёхмерном пространстве

Но не все векторы обязательно имеют величину и направление. Многие из них просто интерпретируются как координаты в 3D-пространстве. Если вновь обратиться к примеру с автомобилем, мы можем сказать, что он расположен по координатам (10.0, 0.0, -32,5). То есть автомобиль располагается на 10-й отметке положительной ветки оси X, на нулевой отметке по оси Y и на отметке -32,5 по оси Z.
Всегда очень важно давать векторам осмысленные имена, согласно тем значениям, которые они содержат. То есть, вектор, который содержит только размер (величину, magnitude = 3D-координаты) назовём как-нибудь наподобие "position", в то время как вектор, имеющий и величину и направление лучше назвать "velocity" (скорость).
Для тех, кто думает, что на этом всё, спешим разочаровать.
Существует третий тип векторов, который мы также будем применять. Вообще ближе всего к этому типу относятся векторы, обсуждаемые в одной из предыдущих глав данного курса, при создании вспомогательного заголовка с базовой геометрией и освещением (Geometry.h) - вектор нормали (normal vector), или как часто его называют единичный вектор (unit vector), так как он далеко не всегда используется в качестве вектора нормали вершины или грани. Более того, вектор нормали совсем не обязательно должен иметь размер, равный одной единице, как у единичного вектора.
В итоге имеем:

  • тип векторов, которые имеют размер (magnitude) и направление (direction). Мы условно назвали данный тип силой (force).
  • тип векторов, которые имеют только размер. Данный тип векторов мы назвали позицией (position) или трансляцией (translation) в 3D-пространстве.
  • Наш новый тип векторов, который имеет только направление. Данный тип векторов называют направленным либо указательным (facing, т.е. указывающим направление).

Лучший способ, чтобы разобраться с данным типом векторов - это рассмотреть следующий пример. Если у нас есть единичный (unit) вектор или направленный вектор с координатами (0.0, 0.0, 1.0), то можно сказать, что вектор указывает на положительную ветку оси Z. Раз этот вектор направленный, то нам вообще нет необходимости выяснять его величину, как это было бы с вектором силы. Нам также не требуется указывать на то, что он имеет трансляцию (т.е. позиционирован) в начале координат осей X и Y и имеет координату 1 по оси Z.
Есть ещё один аспект при работе с единичными векторами, о котором следует помнить. Любой вектор может стать единичным вектором, пройдя т.н. процесс нормализации (normalization). В общих чертах нормализацией называют процесс удаления размерной величины (magnitude) из вектора. Другими словами, каждый компонент вектора в этом случае пропорционально масштабируется (scaled), принимая новое значение размера в пределах от 0.0 до 1.0, причём с игнорированием знака. Это достигается путём простого деления каждой координаты вектора на его длину, что является обычным делом в 3D-математике и позволяет быстро определить, на что указывает вектор, либо направление применение силы. К примеру, возьмём автомобиль с вектором скорости (20.0, 0.0, -10.0). Мы можем подсчитать ориентацию авто (cars' facing, т.е. куда указывает вектор, направленный из середины авто в сторону его радиаторной решётки), вычислив его единичный вектор. Другими словами мы нормализуем вектор скорости автомобиля. После нормализации вектор скорости будет равен приблизительно (0.89, 0.0, -0.44). Это означает что в результате мы получили вектор, который направлен также, как и вектор движения автомобиля, но имеет длину в 1 единицу.
Нормализацию векторов производят по следующим причинам:

  • Нормализованные векторы намного проще сравнивать между собой. К примеру, можно без труда вычислить угол между двумя нормализованными векторами.
  • К единичному вектору можно легко применить силу. Сразу после нормализации вектора, ты можешь умножить его на скалярную (числовую) величину (например на 2, 3 или 7), после чего к данному вектору будет применено новое значение его размера (magnitude).

Рис.2 даёт наглядное представление обо всех трёх типах векторов, описанных выше.

Интегрируем систему поддержки звука в движок

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

Изменения в SoundSystem.cpp (Проект Engine)

  • Добавь инструкцию #include "Engine.h" в самом начале файла SoundSystem.cpp (проверь её наличие).

Изменения в Engine.h (Проект Engine)

  • Добавь инструкцию #include "SoundSystem.h" в файл Engine.h, сразу после инструкции #include "Input.h":
Фрагмент Engine.h (Проект Engine)
...
//-----------------------------------------------------------------------------
// Engine Includes
//-----------------------------------------------------------------------------
#include "Resource.h"
#include "LinkedList.h"
#include "ResourceManagement.h"
#include "Geometry.h"
#include "Font.h"
#include "Scripting.h"
#include "DeviceEnumeration.h"
#include "Input.h"
#include "SoundSystem.h"
#include "State.h"
...
  • Добавь инструкцию #include <dmusici.h> в файл Engine.h, сразу после инструкции #include <dinput.h>:
Фрагмент Engine.h (Проект Engine)
...
//-----------------------------------------------------------------------------
// DirectX Includes
//-----------------------------------------------------------------------------
#include <d3dx9.h>
#include <dinput.h>
#include <dmusici.h>
...


Добавь новый объект класса SoundSystem в объявление класса Engine. Для этого:

  • Добавь в секцию private объявления класса Engine строку:

SoundSystem *m_soundSystem;

  • Добавь в секцию public объявления класса Engine определение функции GetSoundSystem():

SoundSystem *GetSoundSystem();

Фрагмент Engine.h (Проект Engine)
...
//-----------------------------------------------------------------------------
// Engine Class
//-----------------------------------------------------------------------------
class Engine
{
public:
	Engine( EngineSetup *setup = NULL );
	virtual ~Engine();

	void Run();

	HWND GetWindow();
	void SetDeactiveFlag( bool deactive );

	float GetScale(); 
	IDirect3DDevice9 *GetDevice(); 
	D3DDISPLAYMODE *GetDisplayMode(); 
	ID3DXSprite *GetSprite();

	void AddState( State *state, bool change = true );
	void RemoveState( State *state );
	void ChangeState( unsigned long id );
	State *GetCurrentState();

	ResourceManager< Script > *GetScriptManager();

	Input *GetInput();
	SoundSystem *GetSoundSystem();

private:
	bool m_loaded; // Флаг показывает, загружен ли движок или нет.
	HWND m_window; // Дескриптор главного окна.
	bool m_deactive; // Флаг показывает, активно приложение или нет.

	EngineSetup *m_setup; // Копия (инстанс) структуры EngineSetup.

	IDirect3DDevice9 *m_device; 
	D3DDISPLAYMODE m_displayMode; 
	ID3DXSprite *m_sprite; 
	unsigned char m_currentBackBuffer;

	LinkedList< State > *m_states; // Связный список (Linked list) стейтов.
	State *m_currentState; // Указатель на текущий стейт.
	bool m_stateChanged; // Флаг показывает, изменён ли стейт в текущем кадре.

	ResourceManager< Script > *m_scriptManager;

	Input *m_input; // Объект класса Input.
	SoundSystem *m_soundSystem; // Объект класса SoundSystem.
};
...


Функция GetSoundSystem позволяет получить доступ к системе поддержки звука из других частей Проекта Engine.

Изменения в Engine.cpp (Проект Engine)

В конструкторе класса Engine необходимо создать объект системы SoundSystem. Для этого:

  • Добавь следующую строку в конструктор класса Engine, сразу после строки m_input = new Input( m_window ); :

m_soundSystem = new SoundSystem( m_setup->scale );

Фрагмент Engine.cpp (Проект Engine)
...
	// Создаём интерфейс спрайта.
	D3DXCreateSprite( m_device, &m_sprite );

	// Создаём связный список стейтов.
	m_states = new LinkedList< State >;
	m_currentState = NULL;

	// Создаём менеджеры ресурсов.
	m_scriptManager = new ResourceManager< Script >;

	// Создаём экземпляр класса Input
	m_input = new Input( m_window );

	// Создаём экземпляр (объект) класса SoundSytem
	m_soundSystem = new SoundSystem( m_setup->scale );

	// Создаём генератор случайных чисел на основе текущего времени.
	srand( timeGetTime() );
...

  • Добавь следующую строку в деструктор класса Engine, сразу перед строкой SAFE_DELETE( m_input ); :

SAFE_DELETE( m_soundSystem );

Фрагмент Engine.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Деструктор класса Engine.
//-----------------------------------------------------------------------------
Engine::~Engine()
{
	// Проверяем, что движок загружен.
	if( m_loaded == true )
	{
		// Всё, что здесь, будет уничтожаться (например, более неиспользуемые DirectX компоненты).

		// Уничтожаем связные списки со стейтами.
		if( m_currentState != NULL )
			m_currentState->Close();
		SAFE_DELETE( m_states );

		// Уничтожаем ранее созданные объекты.
		SAFE_DELETE( m_soundSystem );
		SAFE_DELETE( m_input );
		SAFE_DELETE( m_scriptManager );

		// Уничтожаем интерфейс спрайта.
		SAFE_RELEASE( m_sprite );

		// Уничтожаем объект устройства.
		SAFE_RELEASE( m_device );
	}

	// Деинициализация COM.
	CoUninitialize();

	// Unregister the window class.
	UnregisterClass( "WindowClass", m_setup->instance );

	// Уничтожение структуры engine setup.
	SAFE_DELETE( m_setup );
}
...

  • Добавь следующую строку в реализации функции ChangeState класса Engine, сразу перед строкой if( m_currentState != NULL ) m_currentState->Close(); :

m_soundSystem->GarbageCollection();

Фрагмент Engine.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Сменить текущий стейт на стейт с указанным ID.
//-----------------------------------------------------------------------------
void Engine::ChangeState( unsigned long id )
{
	// Итерировать через список стейтов и найти новый стейт, на который надо сменить.
	m_states->Iterate( true );
	while( m_states->Iterate() != NULL )
	{
		if( m_states->GetCurrent()->GetID() == id )
		{
			// Закрываем предыдущий стейт
			if( m_currentState != NULL )
				m_currentState->Close();

			// Пусть система поддержки звука сама выполняет сбор мусора
			m_soundSystem->GarbageCollection();

			// Устанавливаем новый стейт в качестве текущего и загружаем его
			m_currentState = m_states->GetCurrent();
			m_currentState->Load();

			// Сменяем бэк-буферы до тех пор, пока первый бэк-буфер не станет фронтальным
			while( m_currentBackBuffer != 0 )
			{
				m_device->Present( NULL, NULL, NULL, NULL );

				if( ++m_currentBackBuffer == m_setup->totalBackBuffers + 1 )
					m_currentBackBuffer = 0;
			}

			// Указываем флаг, что стейт был изменён в данном кадре.
			m_stateChanged = true;

			break;
		}
	}
}
...

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

  • Добавь реализацию функции GetSoundSystem в самом конце файла Engine.cpp:
Фрагмент Engine.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// Возвращает указатель на объект input.
//-----------------------------------------------------------------------------
Input *Engine::GetInput()
{
	return m_input;
}

//-----------------------------------------------------------------------------
// Возвращает указатель на объект SoundSystem.
//-----------------------------------------------------------------------------
SoundSystem *Engine::GetSoundSystem()
{
	return m_soundSystem;
}

Тестовая перекомпиляция Engine.lib

Итак, система поддержки звука полностью интегрирована в движок.

Закрыть
warningПеред компиляцией

В последних версиях DirectX SDK отсутствуют файлы dmusici.h и dmplugin.h, необходимые для следующей компиляции библиотеки Engine.lib. Их можно без труда найти в Интернете или в DirectX SDK 8.0. После скачивания, данные файлы нужно скопировать в папку include установленного DirectX SDK.

Для проверки работосопособности исходного кода, добавленного в этой Главе, перекомпилируем исходный код Проекта Engine:

  • В Обозревателе решений щёлкаем правой кнопкой мыши по значку Проекта Engine. Во всплывающем меню выбираем "Перестроить" (применяется в случае, когда код уже был успешно скомпилирован ранее).

Image
По окончании компиляции (обычно, при создании небольших проектов, она занимает менее 1 секунды) в панели "Вывод" (в нижней части главного окна IDE) будет представлен отчёт (лог) об успешной (либо неуспешной) компиляции.
В нашем случае, несмотря на многочисленные предупреждения, компиляция прошла успешно.

Закрыть
noteОбрати внимание...

Напомним о том, что следует отличать ошибки (errors) от предупреждений (warnings). Компиляция может завершится успешно даже при выводе сотен всевозможных предупреждений. Чаще всего они связаны с использованием устаревших (deprecated) либо небезопасных (unsafe), по мнению Microsoft, функций. За основу учебного курса была взята книга Young V. "Programming Multiplayer FPS in DirectX", вышедшая в свет в 2005 году, где все примеры написаны под классический C++ в IDE MS Visual C++ 6.0 аж 1998 года выпуска. С тех пор очень многое изменилось. Microsoft развивала C++, Windows API и свои IDE семимильными шагами. В результате многие функции, успешно применявшиеся тогда, сегодня признаны Microsoft устаревшими и не рекомендованными к использованию. К предупреждениям можно прислушиваться, а можно и нет. Для простоты изложения мы будем их игнорить. А чтобы они не выводились совсем, пропиши строку _CRT_SECURE_NO_WARNINGS в свойствах Проекта Engine (Свойства -> Свойства конфигурации -> С/С++ -> Препроцессор -> Определения препроцессора).

Полученная в результате компиляции двоичная библиотека Engine.lib перезаписывается по тому же пути (там же её будет искать тестовое приложение из Проекта Test).

Модифицируем тестовое приложение (Проект Test)

В Главе 1.6(external link) для проверки работоспособности движка в нашем Решении GameProject01 мы создали второй Проект Test, после компиляции которого получили исполняемое приложение (файл .EXE), показывающее окно. В последующих главах его функционал изменялся. Применив полученные в текущей Главе знания на практике, снова дополним исходный код тестового приложения, оснастив его новым "функционалом". Если по каким-то причинам Проект Test отсутствует в Решении GameProject01, создай его, следуя инструкциям Главы 1.6(external link) .
ОК, приступаем.

  • Открой для редактирования файл исходного кода Main.cpp (Проект Test) и замени содержащийся в нём код на следующий:
Main.cpp (Проект Test)
//-----------------------------------------------------------------------------
// System Includes
//-----------------------------------------------------------------------------
#include <windows.h>

//-----------------------------------------------------------------------------
// Engine Includes
//-----------------------------------------------------------------------------
#include "..\GameProject01\Engine.h"

//-----------------------------------------------------------------------------
// Test State Class
//-----------------------------------------------------------------------------
class TestState : public State
{
public:
	//-------------------------------------------------------------------------
	// Выполняем необходимые операции перед созданием стейта.
	//-------------------------------------------------------------------------
	virtual void Load()
	{
		m_font = new Font;

		m_sound = new Sound( "./Assets/Sound.wav" );
	}

	//-------------------------------------------------------------------------
	// Выполняем необходимые операции при уничтожении стейта.
	//-------------------------------------------------------------------------
	virtual void Close()
	{
		SAFE_DELETE( m_font );

		SAFE_DELETE( m_sound );
	}

	//-------------------------------------------------------------------------
	// Возвращает сведения о видеонастройках данного кадра.
	//-------------------------------------------------------------------------
	virtual void RequestViewer( ViewerSetup *viewer )
	{
		viewer->viewClearFlags = D3DCLEAR_TARGET;
	}

	//-------------------------------------------------------------------------
	// Updates the state.
	//-------------------------------------------------------------------------
	virtual void Update( float elapsed )
	{
		if( g_engine->GetInput()->GetKeyPress( DIK_SPACE ) )
			m_sound->Play();
	}

	//-------------------------------------------------------------------------
	// Renders the state.
	//-------------------------------------------------------------------------
	virtual void Render()
	{
		m_font->Render( "Press the space bar to test the sound system.", 10, 10 );
	}

private:
	Font *m_font; // Шрифт, используемый для рендеринга текста.
	Sound *m_sound; // Тестовый звук.
};

//-----------------------------------------------------------------------------
// Установки стейта, специфичные для приложения.
//-----------------------------------------------------------------------------
void StateSetup()
{
	g_engine->AddState( new TestState, true );
}

//-----------------------------------------------------------------------------
// Точка входа в приложение.
//-----------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE instance, HINSTANCE prev, LPSTR cmdLine, int cmdShow )
{
	// Создаём структуру EngineSetup.
	EngineSetup setup;
	setup.instance = instance;
	setup.name = "Sound Test";
	setup.StateSetup = StateSetup;

	// Создаём движок (используя только что созданную структуру EngineSetup) и запускаем его.
	new Engine( &setup );
	g_engine->Run();

	return true;
}


И вновь в нашем Проекте Test всего 1 файл Main.cpp, который содержит весь исходный код тестового приложения. Перед нами готовая система поддержки звука (в виде стейта) в действии.

Исследуем код

Первым делом в классе TestState видим новую переменную m_sound, которая на самом деле является указателем на объект класса Sound. Мы будем использовать его для создания и воспроизведения звука.
В самом начале загружаем звуковой ресурс (заранее заготовленный файл Sound.wav) через функцию Load класса TestState:

Фрагмент файла Main.cpp (Проект Test)
...
//-----------------------------------------------------------------------------
// Test State Class
//-----------------------------------------------------------------------------
class TestState : public State
{
public:
	//-------------------------------------------------------------------------
	// Выполняем необходимые операции перед созданием стейта.
	//-------------------------------------------------------------------------
	virtual void Load()
	{
		m_font = new Font;

		m_sound = new Sound( "./Assets/Sound.wav" );
	}
...

Напомним, что имя файла также должно включать в себя полный путь до него.

Закрыть
noteОбрати внимание...

Каталог Assets должен быть расположен в одной папке с исполняемым файлом Test.exe . В нашем случае это: C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\GameProject01\Debug\


Уничтожаем звук путём вызова функции Close и использования макроса SAFE_DELETE на указателе m_sound.

Наконец, нам нужно воспроизвести звук. Чтобы сделать весь процесс чуточку интереснее, мы привяжем воспроизведение звука к нажатию клавиши Пробел:

Фрагмент файла Main.cpp (Проект Test)
...
	//-------------------------------------------------------------------------
	// Updates the state.
	//-------------------------------------------------------------------------
	virtual void Update( float elapsed )
	{
		if( g_engine->GetInput()->GetKeyPress( DIK_SPACE ) )
			m_sound->Play();
	}
...

Теперь каждый раз при нажатии Пробела будет воспроизводиться звук.
Отметим, что т.к. мы не используем класс SoundSystem, то звук будет воспроизведён на аудиопути по умолчанию (Default Audiopath), предоставляемом перфомансом. При создании аудиопути по умолчанию, мы указали, что не хотим использовать 3D-возможности звука.

Подготовка Проекта Test к перекомпиляции

Напомним, что для успешной компиляции библиотека Engine должна быть добавлена в Проект Test (см. Главу 1.6).
Помимо этого, MS Visual C++ 2010 при компиляции нашего обновлённого Проекта Test активно юзает внешние библиотеки и требует их принудительного указания в настройках компоновщика (= линковщика, линкера) Проекта. Некоторые из них мы уже добавляли ранее (winmm.lib, dxguid.lib и dinput8.lib).
Проверим наличие всех необходимых библиотек:

  • В Обозревателе решений щёлкни правой кнопкой мыши по названию Проекта Test.
  • Во всплывающем контекстном меню выбираем пункт "Свойства".
  • В появившемся окне: Свойства конфигурации -> Компоновщик -> Ввод. Во всплывающем списке напротив пункта "Дополнительные зависимости" выбираем "Изменить...".
  • В появившемся окне "Дополнительные зависимости" в поле ввода должны быть указаны (обязательно в столбик и без запятых!) 7 библиотек:

d3dx9d.lib, d3d9.lib, dinput8.lib, dxguid.lib, winmm.lib, odbc32.lib, odbccp32.lib

  • Жмём ОК, ОК.
  • Сохрани изменения в Решении (Файл->Сохранить все).


Сразу после этого компилируем Проект Test:

  • В Обозревателе решений щёлкни правой кнопкой мыши по названию Проекта Test.
  • Во всплывающем контекстном меню выбираем: Перестроить.

Закрыть
warningerror LNK2019

При компиляции Проекта Test с использованием последних версий DirectX SDK часто возникает несколько ошибок вида "error LNK2019: ссылка на неразрешенный внешний символ...".
Причина: DirectSound-у не нравится библиотека dxguid.lib из последних версий DirectX SDK. Интерфейсы DirectSound и DirectMusic давно не поддерживаются и признаны устаревшими. Для корректной компиляции необходимо найти dxguid.lib из Microsoft DirectX SDK 8.0 и скопировать в каталог lib установленного DirectX SDK, согласившись на замену.


Компиляция завершилась успешно. Итоговый исполняемый двоичный файл (Test.exe) расположен на жёстком диске ПК в той же директории, что и библиотека движка.
В нашем случае, это: С:\Users\<Имя пользователя>\documents\visual studio 2010\Projects\GameProject01\Debug. (В разных ОС путь к файлу может отличаться от представленного).
*Найди и запусти получившееся приложение.

Итоги главы

Постепенно наш движок начинает обретать форму. В этой Главе мы рассмотрели работу DirectMusic и DirectSound (лишь в той части, где речь шла о 3D-звуке). Затем мы подробно обсудили и разработали 3 класса, которые и представляют собой нашу систему поддержки звука: SoundSystem, Sound и AudioPath3D. Сразу после интегрирования системы поддержки звука в наш движок, мы создали ещё одно тестовое приложение, воспроизводящее короткий звуковой фрагмент по нажатию клавиши Пробел. Во второй части данного курса мы будем активно применять класс AudioPath3D для создания 3D-звуков.

Исходные коды Решения GameProject01 (для MS Visual C++ 2010), с которым работали в данной Главе, забираем здесь(external link).


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.12 Добавляем поддержку звука

Contributors to this page: slymentat .
Последнее изменение страницы Суббота 20 / Май, 2017 13:16:26 MSK автор slymentat.

Последние комментарии

No records to display

Хостинг