Программирование стратегических игр с DirectX 9.0

       

Создание класса для представления блоков


Простейший метод хранения блочной карты уже определен, так что настало время погрузиться в объектно-ориентированный мир и создать класс для представления блоков.



Создание наброска


Теперь у вас есть все эти идеи. Что дальше? Лучший из придуманных мной методов состоит в том, чтобы чтобы создать на основе идей набросок игры. Взгляните на следующий набросок:

Исходные данные

Вторая Мировая Война

Сражения Паттона

Тщательное моделирование реальных событий

Сражения

Уровень бригад

Основное внимание механизированным подразделениям

Команды передаются вниз до уровня подразделений

Реализм

Туман войны

Проблемы с коммуникациями

Отсутствует вид со спутника

Возможные маршруты

Необходимость маршрутов снабжения

Игроки

Один игрок

Игровая кампания

Многопользовательский режим

Стычки между отрядами

События однопользовательской кампании

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

Читая мой набросок, вы представляете основную идею игры — реалистичная игра, посвященная танковым сражениям Второй Мировой войны. Несомненно, есть сотни деталей, которые еще следует определить, но это только первая стадия разработки программы.



Создание объекта Direct3D

Функция Direct3DCreate9() является самой важной в Direct3D, поскольку без нее ничего не сможет произойти. Это самая первая функция, которую вы вызываете в процессе настройки среды визуализации. Главной задачей функции является создание объекта IDirect3D9. Вот как выглядит ее прототип:

IDirect3D9 *Direct3DCreate9( UINT SDKVersion );

Вы должны полюбить его за простоту. Единственный параметр, являющийся беззнаковым целым числом, указывает используемую вами версию DirectX SDK. Для этого параметра всегда следует использовать значение D3D_SDK_VERSION.

Параметр предназначен для проверки кода программы на отсутствие тривиальных ошибок. Фактически, выполняется проверка переданного номера версии и его сравнение с номером версии, хранящимся в заголовочных файлах DirectX. Если номера версий не совпадают, код знает, что что-то в установленном DirectX вызывает подозрения. Не заморачивайте себе этим голову; лучше всего оставьте эту функцию в покое и используйте рекомендованное значение.

ПРИМЕЧАНИЕ Если вы добавите к вашей системе новые видеокарты или мониторы после вызова функции Direct3DCreate9(), объект IDirect3D9 не будет знать об этих устройствах. Чтобы новые устройства были перечислены в списке, необходимо заново создать объект.

Создание устройства трехмерной визуализации


Вы уже установили параметры отображения, так что настало время создать объект устройства IDirect3DDevice9. Он является основой для всех относящихся к визуализации вызовов и поэтому исключительно важен. Без этого маленького объекта вы ничего не сможете отобразить на экране. Для его создания применяется функция IDirect3D9::CreateDevice(). Вот как выглядит ее прототип:

HRESULT CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 **ppReturnedDeviceInterface );

Первый параметр, Adapter, содержит порядковый номер используемой видеокарты. Для большинства систем с одним монитором можно использовать значение D3DADAPTER_DEFAULT. Этот параметр работает точно так же как и первый параметр функции GetAdapterDisplayMode(), который я описал ранее.

Второй параметр, DeviceType, является перечислением, задающим тип устройства. Доступные типы перечислены в таблице 6.5. В данной программе я использую значение D3DDEVTYPE_HAL. Из-за этого программа может не работать с видеокартами, не поддерживающими аппаратное ускорение. В этом случае для того, чтобы программа запустилась попробуйте изменить значение этого параметра на D3DDEVTYPE_REF.

Таблица 6.5. Типы устройств

Значение Константа Описание
1 D3DDEVTYPE_HAL Визуализация выполняется аппаратурой видеокарты. Этот метод позволяет использовать преимущества доступных методов аппаратного ускорения.
2 D3DDEVTYPE_REF Direct3D всю визуализацию выполняет программно. Это плохой выбор, если видеокарта поддерживает аппаратное ускорение.
3 D3DDEVTYPE_SW Используется программное устройство визуализации, эмулирующее аппаратуру.

Третий параметр, hFocusWindow, задает окно, которому будет принадлежать фокус системы визуализации DirectX. Для полноэкранного режима это должно быть окно самого верхнего уровня. В рассматриваемом примере я использую дескриптор окна, созданного в функции WinMain(). Это общепринятая практика для приложений, работающих в оконном режиме.

Четвертый параметр, BehaviorFlags, задает один или несколько флагов, определяющих параметры создаваемого устройства отображения. Доступные флаги перечислены в таблице 6.6.

Таблица 6.6. Флаги, задающие поведение устройства

Значение Описание
D3DCREATE_FPU_PRESERVE Приложение требует вычислений с плавающей точкой двойной точности.
D3DCREATE_MULTITHREADED Direct3D будет работать в безопасном многопоточном режиме. Этот режим приводит к значительному падению производительности и должен применяться только в случае крайней необходимости.
D3DCREATE_PUREDEVICE Указывает, что устройство не поддерживает вызовы Get для использования в блоках состояния. Кроме того, устройство не будет поддерживать обработку вершин. Лично я никогда не использую этот флаг.
D3DCREATE_HARDWARE_VERTEXPROCESSING Обработка вершин производится аппаратурой видеокарты. Этот флаг используется в рассматриваемой программе. Если приложение на вашем компьютере не работает, попробуйте заменить его на следующее значение из спсика.
D3DCREATE_SOFTWARE_VERTEXPROCESSING Обработка вершин производится программно. Это значительно медленнее, чем аппаратная обработка.
D3DCREATE_MIXED_VERTEXPROCESSING Обработка вершин производится как аппаратурой, так и программно. Этот вариант может работать медленнее, чем чисто аппаратная обработка.
D3DCREATE_DISABLE_DRIVER_MANAGEMENT Устраняет драйвер видеокарты от управления ресурсами. Если флаг установлен, всеми ресурсами управляет Direct3D.
D3DCREATE_ADAPTERGROUP_DEVICE Используется, если одновременно работают несколько видеокарт.
D3DCREATE_MANAGED Передает вопросы управления памятью в ведение устройства.

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

Последний параметр, ppReturnedDeviceInterface, содержит адрес указателя, который после завершения работы функции будет представлять созданное устройство. В этом параметре я передаю глобальный указатель на интерфейс IDirect3DDevice9 с именем g_pd3dDevice.

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



Специальные повреждения


Специальными повреждениями называются повреждения, наносимые вне обычного хода событий. Очевидный пример — область действия эффектов применения оружия. Оружие, наносящее ущерб посредством взрыва, может также вызывать контузию, которая может рассматриваться как специальное повреждение, поскольку ошеломляет врагов и травмирует их. Обладая большим творческим потенциалом вы сможете придумать сотни видов специальных повреждений. Для простоты специальные повреждения называют также дополнительными повреждениями.



Способ передвижения


Как перемещается подразделение? Летает ли оно, плавает, перемещается в космосе или может использовать все эти три способа? Это очень важный вопрос ответ на который влияет на стратегию использования данного подразделения в ходе игры. Взгляните на список наиболее распространенных способов передвижения:

По земле По воздуху По воде В космосе



StarCraft от Blizzard


Другой популярной игрой, выпущенной Blizzard Entertainment является StarCraft. StarCraft чем-то похожа на Warcraft: Orcs & Humans, но ее действие разворачивается в будущем. В игре имеются три различные расы, каждая со своими сильными и слабыми сторонами. Эта уникальная особенность выделяет StarCraft среди современников. До StarCraft, большинство стратегий реального времени предлагали противостояние примерно одинаковых рас или сторон. StarCraft впервые предложил настолько различных соперников. На Рисунок 1.13 показана атака расы, называемой зерги на людей.



Стартовый экран игры Battle Armor





На Рисунок 6.1 показан стартовый экран моей игры Battle Armor. Он не слишком сложен, но здесь присутствуют несколько элементов, образующих интерфейс. На Рисунок 6.2 приведена схема кадра со стартовым экраном.



Стоимость боевых единиц

Все боевые единицы должны иметь какую-то стоимость для игрока, выражающуюся в затрачиваемых на их создание ресурсах. Если боевые единицы слишком дешевы, то игроки будут приобретать их сотнями и использовать стратегию, известную под названием «лавина». По моему мнению, это наиболее скучная форма сражения, но она срабатывает во многих случаях и поэтому широко используется. Вы должны удержать точный баланс между стоимостью боевой единицы и ее мощью. Обычно хорошей идеей является сделать так, чтобы для создания каждой боевой единицы требовалось несколько видов ресурсов. Это не даст игрокам возможности концентрироваться на одном виде ресурсов для содержания армии. Кроме того, это добавит игре тактической ценности.

СОВЕТ Термин «лавина» происходит от тактики, когда игрок строит множество дешевых боевых единиц, формирует из них армию, и обрушивает ее на противника. Это настоящая форма блтцкрига, причем весьма опасная. Проблема в том, что данная тактика почти ничего не стоит в плане ресурсов, и большинство игр заканчиваются победой того игрока, который быстрее сформирует армию и обрушит ее на противника. Другая отрицательная сторона заключается в том, что большинство стратегий реального времени, страдающих от такого рода атак не имеют никакой защиты против подобной тактики. Помните, любая стратегия в игре нуждается в существовании противостратегии. Если это не так, игра превратится в постоянное скучное повторение одной и той же тактики.

Стоимость технологий


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

Выбирая стоимость технологий для вашей игры следует помнить о нескольких ключевых пунктах:

Убедитесь, что разработка технологии занимает какое-то время. Чеб более ценна технология, тем больше времени должно требоваться для ее открытия. Убедитесь, что технология требует ресурсы, которые имеют отношение к ней. Другими словами, не делайте так, чтобы технология требовала ресурсы, которые не имеют с ней ничего общего. Убедитесь, что стоимость технологии находится в разумных пределах. Если технология очень полезна, ее стоимость должна быть высокой. Однако не стоит выходить за разумные границы. Если технология стоит больше ресурсов, чем с ее помощью можно сэкономить, то она является бесполезной.

На Рисунок 3.9 показано обновленное дерево технологий, где указаны затраты ресурсов, связанные с каждым узлом технологий.



Строительство ландшафта


Другой уникальной особенностью игры Populous является возможность изменять ландшафт территории, на которой проживают ваши люди. Инструменты в игре позволяют вам создавать равнины или возвышенности. Создавая большие пространства ровной земли вы обеспечиваете свое население пригодным для строительства ландшафтом. Если вы оставляете свою землю неровной, ваши люди вынуждены жить в небольших домах. Если же вы создадите широкие равнины, они смогут строить замки и большие здания. Проектируя свою игру помните об этой уникальной возможности. Иногда даже самые простые идеи делают игру гораздо более увлекательной.

Как я упоминал, было выпущено несколько вариантов игры Populous. Чтобы получить самые последние новости, относящиеся к этой игре, посетите сайт http://www.populous.net.



Структура данных настраиваемого формата вершин (FVF)


Перейдем дальше к структуре данных настраиваемого формата вершин. Она объявляет формат геометрических данных используемых в примере для трехмерной визуализации. Код выглядит следующим образом:

struct CUSTOMVERTEX { D3DXVECTOR3 position; // Местоположение D3DXVECTOR3 vecNorm; // Нормаль FLOAT tu, tv; // Координаты текстуры FLOAT tu2, tv2; // Координаты текстуры }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2)

DirectX использует структуру данных вершины при описании геометрии визуализируемых трехмерных объектов. Без такой структуры вы ничего не сможете отобразить на экране. Это очень важный аспект DirectX и краеугольный камень визуализации. Как видите, в описанном мной формате вершин, присутствует несколько ключевых элементов: сведения о местоположении, данные нормали и координаты текстуры.



Структура данных stHotSpot


Первый заслуживающий внимания элемент заголовочного файла класса — структура данных stHotSpot. Вот как выглядит ее код:

struct stHotSpot { short m_shZoneXPos; short m_shZoneYPos; short m_shZoneWidth; short m_shZoneHeight; bool m_bActive; short m_shClickType; char *m_szZoneName; };

Структура данных горячей точки представляет активную зону в экранных координатах. Поскольку зоны прямоугольные, указываются координаты угла, высота и ширина. Взаимосвязь между членами структуры данных и параметрами активной зоны показана на Рисунок 6.27.



Структура данных WNDCLASSEX


Если в прошлом вы уже занимались программированием для Windows, то могли использовать структуру WNDCLASS. Между ней и объектом WNDCLASSEX есть только пара отличий. Во-первых добавлен апраметр, задающий размер структуры в байтах. Во-вторых, добавлен параметр задающий дескриптор маленького значка для окна.

Ниже приведен прототип структуры WNDCLASSEX:

typedef struct _WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX;

Нравится ли вам встеча со сложной структурой, назначение членов которой надо запомнить? Нет! Хорошо хоть Microsoft позаботилась, чтобы имена этих переменных было просто читать. Первая переменная типа UINT называется cbSize. Она используется для указания размера структуры данных. Обычно для инициализации этого члена структуры используется выражение sizeof(WNDCLASSEX). Инициализацию этого члена данных вы можете увидеть взглянув на приведенный выше листинг.

ПРИМЕЧАНИЕ Если вам непонятно ключевое слово UINT — оно обозначает целое число без знака. Это означает, что переменная может принимать только положительные значения.

Второй элемент структуры также имеет тип UINT и называется style. Как указывает имя, член style используется для задания стиля создаваемого окна. Удобство данного члена данных в том, что вы можете указывать комбинации одних стилей с другими, используя несколько флагов, объединеных поразрядной операцией ИЛИ (|). Доступные стили перечислены в таблице 2.2.

Таблица 2.2. Стили окон

Значение Действие
CS_BYTEALIGNCLIENT Выравнивает клиентскую область окна (область с содержимым) по границе байта в направлении оси X. Оказывает влияние на позицию окна по горизонтали и на его ширину. Лично я никогда не использовал этот стиль.
CS_BYTEALIGNWINDOW Выравнивает окно по границе байта в направлении оси X. Оказывает влияние на границу окна по горизонтали и на его ширину. Этот стиль я также не использую.
CS_CLASSDC Размещает в структуре класса единый контекст устройства для использования всеми окнами. Я пока не рассказывал о контекстах устройств, но сделаю это позже, так что нет причин для волнения. В Windows можно создать несколько классов окон одного и того же типа в разных потоках. Так как у вас может быть несколько копий класса, несколько потоков могут попытаться использовать контекст устройства одновременно. Это вызовет блокировку всех потоков, кроме одного, пока этот поток не завершит работу с контекстом.
CS_DBLCLKS Очень простой стиль. Когда он указан, Windows будет посылать вашему окну сообщение о двойном щелчке каждый раз, когда пользователь выполняет двойной щелчок кнопкой мыши в пределах области окна. Это может показаться глупым, но многие приложения запоминают время каждого щелчка кнопки мыши, чтобы определить был ли выполнен двойной щелчок. Я, чтобы добиться того же, просто использую данный стиль.
CS_GLOBALCLASS Этот стиль разрешает создание глобального класса окна. Чтобы получить дополнительную информацию, обратитесь к документации, поставляемой с Microsoft Visual C++. При разработке игр нет необходимости использовать данный стиль.
CS_HREDRAW Этот стиль заставляет перерисовывать все окно в случае изменения его ширины.
CS_NOCLOSE Запрещает выполнение закрытия окна через системное меню.
CS_OWNDC Выделяет уникальный контекст устройства для каждого окна, созданного с использованием класса.
CS_PARENTDC Устанавливается, когда дочернее окно использует ту же самую область отсечения, что и родительское. Это позволяет дочернему окну рисовать на родительском. Это не означает, что потомок использует контекст устройства родителя. В действительности потомок получает свой собственный контекст устройства из системного пула. Единственное, что делает этот флаг — увеличение производительности приложения.
CS_SAVEBITS Сохраняет в памяти растровое изображение находящейся под окном области экрана. В дальнейшем при перемещении окна скрытая область копируется на экран. Это предотвращает отправку сообщения WM_PAINT всем окнам, которые были скрыты данным.
CS_VREDRAW Заставляет перерисовывать все содержимое окна в случае изменения высоты окна.

Третий член структуры имеет тип WNDPROC, является указателем на функцию и называется lpfnWndProc. Помещаемый сюда указатель должен указывать на функцию обработки сообщений Windows, которую окно использует чтобы принимать сообщения. Это очень важно, и функция, на которую ссылаются здесь должна полностью соответствовать прототипу, приведенному в моем коде. Взгляните на Рисунок 2.9, чтобы увидеть взаимоотношения между классом и обработчиком сообщений.



Структура файлов программы демонстрирующей двухмерную блочную графику





Как видно на рисунке, проект включает собственные уникальные файлы, файлы каркаса приложения DirectX и библиотеки d3d9.lib, dxguid.lib, d3dx9dt.lib, d3dxof.lib, comctl32.lib и winmm.lib.



Структура класса звуковой системы





Обратите внимание, что на рисунку показаны три главных метода класса и два главных члена данных. В классе также есть обычные конструктор и деструктор, но на рисунке они не показаны, поскольку присутствуют в любом классе.

Три метода называются hrInitSoundSystem(), hrLoadSound() и hrPlaySound(). Достаточно прямолинейно, правда? Функция инициализации вызывается один раз для каждого экземпляра игры. Поскольку у вас должен быть только один экземпляр, это означает, что вы один раз вызываете функцию и она делает всю необходимую работу. Функция загрузки звука должна вызываться один раз для каждого звукового файла. Нет никакой необходимости загружать один и тот же звук несколько раз, если только вы действительно не хотите этого по каким-то причинам. Функция воспроизведения звука может и, возможно, будет, вызываться несколько раз для одного и того же звука. Нет никаких ограничений того, сколько раз можно воспроизводить звук.

Два основных члена данных, m_pLoader (IDirectMusicLoader8) и m_pPerformance (IDirectMusicPerformance8), предоставляют классу необходимые интерфейсы объекта. Как вы, возможно, помните, загрузчик отвечает за загрузку звуковых файлов, а объект исполнителя осуществляет воспроизведение звука.



Структура обработки сообщений Windows





На Рисунок 2.1 есть несколько областей, представляющих для нас интерес. Прежде всего обратите внимание на изображение окна программы. Под ним нарисована клавиатура, представляющая входные данные, которые поступают от пользователя.



Структура программы


Если назначение обработчика событий все еще кажется вам немного странным, не волнуйтесь. Я пока не показал как он вписывается в общую картину. Рисунок 2.1 поможет прояснить некоторые вещи.



Структура реализации звуковой системы в заголовочном файле проекта D3D_MenuSounds





В файл Main.cpp были добавлены вызовы функций для инициализации звуковой системы, загрузки звуковых файлов и их воспроизведения. Данные изменения иллюстрирует Рисунок 7.14.



Структура реализации звуковой системы в главном файле проекта D3D_MenuSoundsSound





Как видно на Рисунок 7.14 при инициализации звуковой системы происходит обращение к методу инициализации объекта звуковой системы. Затем этот же объект используется для загрузки звуковых файлов. Как только эти задачи выполнены, загруженные звуковые фрагменты можно воспроизводить, когда это потребуется.

Чтобы добавить воспроизведение файлов MP3 я просто скопировал в программу работы с меню функции bPlayTitleMusic(), vStopTitleMusic() и vCheckMusicStatus(). Эти действия и добавление вызова, начинающего воспроизведение музыки в код инициализации и составляют весь секрет трюка.

Если вы еще не сделали это, запустите программу D3D_MenuSounds и пощелкайте по разным кнопкам меню. Музыка MP3 воспроизводится в фоновом режиме, а при щелчке по некоторым кнопкам меню воспроизводится WAV-файл. Обратите внимание, что звук в WAV-файле достаточно тихий и вам, возможно, придется прислушаться, чтобы расслышать его на фоне музыки. Я советую вам попробовать поместить в программу свои собственные музыку и звуки (ха, вы можете добавить даже несколько звуков, чтобы закрепить полученные навыки).

Вот и все, что я собирался рассказать вам о реализации звукового сопровождения и воспроизведении MP3-файлов в программах. Конечно, есть еще масса вещей, которые придется учесть, например, определить какой звук воспроизводить и когда. Но об этом мы поговорим в другой раз.



Структура вызовов других функций в функции WinMain()





На Рисунок 6.12 показан список функций, которые я использую в WinMain(). Они приведены в порядке их исполнения. Обратите внимание, что большинство вызовов не относятся к Direct3D. Это объясняется тем, что функциия WinMain() содержит в основном код инициализации, а не код выполнения или визуализации.

Первым интересным фрагментом кода является вызов функции GetClientRect(). Вы можете недоумевать, почему она внесена в список. Для лучшего понимания позвольте мне рассказать о некоторых особенностях написания программ для работы в оконном режиме.



Свойства блоков


Ох, парни — ну и классеая игра America's Army. Я набрал 62 очка в миссии Pipeline и великолепно провел время. Ну, за работу!

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

Проходимость Возвышенность Яркость Смещение

Технологии для инфраструктуры


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

В качестве примера рассмотрим игру Age of Empires. В ней вы не можете создать кавалерию, пока не построите конюшни. Вы не можете построить конюшни, пока не достигнете второй эры. Таким образом технологии формируют основу инфраструктуры.



Технологии для модернизации


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

Вы можете модифицировать не только оружие, но также и инфраструктуру. Возьмем для примера мою игру Battle Armor. В ней со временем появляется возможность модернизировать гидропонную фабрику, чтобы она за то же самое время производила больше пищи. Модернизированная гидропонная фабрика показана на Рисунок 3.8.



Технологии для вооружений

«...И будут спущены с цепей псы войны...»

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

Я помню игру Alpha Centauri. Это замечательная игра, которая фактически является Civilization в космосе. Единственная проблема, с которой я столкнулся, заключалась в том, что дерево технологий было очень запутанным. Поскольку игра является научно-фантастической, у всех технологий были фантастические названия, никак не связанные существующей действительностью. Это приводит к проблемам, поскольку разработчики знают, что они подразумевают, но у игроков нет никакого ключа, чтобы понять, о чем думал разработчик! Тот факт, что ваша игра основана на научной фантастике не означает, что вы должны создать для нее полностью новый язык.

СОВЕТ Используйте простые для понимания деревья технологий. Даже если действие игры разворачивается в будущем, попытайтесь придумывать названия для технологий, основываясь на сегодняшней реальности.

Технология


Как и в большинстве современных стратегий реального времени, в Command & Conquer присутствуют две воюющие стороны. Каждая из них обладает собственными сильными и слабыми сторонами. В данном случае сильные и слабые стороны выражаются в форме подразделений и оружия.

У зловещего Братства NOD есть тяжелые и медленные подразделения, обладающие большой огневой мощью. Также у него есть исключительно сильная оборона и специальные средства для атак. Одним из таких специальных средсв являются ядерные ракеты. Когда игрок, управляющий NOD, построит Храм NOD, он получает возможность достаточно часто запускать ядерные ракеты. Этот зловещий храм изображен на Рисунок 1.9.



Тема сюжета


Первое, что вам следует сделать начиная писать сценарий — подумать о теме вашей игры. Вот несколько возможных тем:

Научная фантастика. Средневековье. Вестерн. Мир после катастрофы.

Есть множество других возмжных тем, которые можно выбрать или придумать; главное — выбрать одну и работать с ней. Возьмите, например, игру Star Wars: Galactic Battlegrounds, основанную на широко известной саге «Звездные войны», созданной Джорджем Лукасом. Она конечно попадает в категорию научной фантастики. Другим примером игры может служить Stronghold, где сюжет вращается вокруг захвата и строительства замков, и, следовательно, относится к средневековью. Я рекомендую выбирать тему, которая вам действительно нравится. К тому же, идеи для сюжета проще придумать, когда его тема интересна вам.



Тестирование отдельных частей


Я не могу достаточно подчеркнуть, насколько важно тестирование отдельных частей. Тестирование частей — это испытание разработчиком его собственного изделия. Перед тем, как отправить игру команде тестировщиков, разработчики, как предполагается, проверяют ее самостоятельно. Мой лучший совет — проверьте ваш код полностью! Вы не должны передавать код тестировшикам, если в нем есть любыне известные серьезные ошибки. Если это все-таки приходится делать, удостоверьтесь, что все известные ошибки вам задокументированы.

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

Код выполняется без сбоев 5000000 раз в одном потоке. Код выполняется без сбоев 5000000 раз одновременно в 32 потоках. Код работает без сбоев в течение одной недели. Код без сбоев проходит полное возвратное тестирование.

Многопоточная часть может не применяться в вашей разработке. В действительности все зависит от того, что вы пишете. Не следуйте перечисленным правилам буквально, а используйте их для идей, применимых в вашем собственном процессе.

Суть в следующем — код, передаваемый вами тестировщикам, должне быть надежен как скала. Если он не настолько надежен, зачем вы передаете его?



The Seven Cities of Gold


Если у вас был компьютер Commodore 64 или Atari 800, возможно вы играли в игру The Seven Cities of Gold. Это еще одна замечательная игра, созданная Ozark Softscape. Она была выпущена в 1987 году и имела оглушительный успех. В этой игре вы занимались исследованием Нового Света в роли Христофора Колумба или испанского конквистадора.

Игра очень интересная и захватывающая, поскольку вы исследуете Новый Свет постепенно. Вы начинаете с приобретения снаряжения и найма людей, которые будут помогать вам в путешествии. Как только вы подготовитесь, можно поднять паруса и плыть в Новый Cвет. Это путешествие интересно само по себе — если вы не найдете Новый Свет то умрете от голода! Если вы все-таки нашли Новый Свет, вам следует высадиться, основать миссию и наладить отношения с аборигенами. Здесь мы встречаемся еще с одной особенностью игры: вы можете встечать аборигенов мирно, либо можете обыскивать их деревни в поисках золота. Если вы пришли с миром, весть о вашей честности распространится, и другие деревни будут встречать вас тем же. Если вы убиваете аборигенов, весть о вашем предательстве распространится еще быстрее, и будущие встречи с аборигенами скорее всего закончатся кровопролитем.

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



Тип атаки


Вы должны не только учесть объем наносимого боевой единицей ущерба, но также и подумать о том, каким именно способом этот ущерб будет наноситься. Будет ли подразделение выпускать во врага ракеты, или оно будет стрелять пулями, воспользуется лазером, или каким-либо другим оружием?

Есть несколько различных методов назначения боевым подразделениям типов атаки. Можно либо назначать параметры каждому подразделению индивидуально, либо создать несколько глобальных наборов параметров, и назначать их различным подразделениям. Я предпочитаю использовать глобальные параметры, поскольку это упрощает назначение одного и того же типа атаки нескольким различным подразделениям. Многие игры на рынке также следуют этим путем, поскольку он более эффективен. Взгляните, например, на следующую таблицу:

Название подразделения

Легкий танк

Наносимый ущерб

100

Скорострельность

5

Косвенный ущерб от оружия

0

Изображение атаки

laser.bmp

Мощность брони

100

Скорость передвижения

50

В данном примере легкий танк может причинить вражескому подразделению до 100 единиц ущерба, стреляет пять раз в минуту, для изображения оружия используется файл laser.bmp, оборудован броней мощностью 100 единиц и за один ход может переместиться на 50 единиц. Что, если теперь вы захотите создать средний танк, оборудованный тем же вооружением, но имеющий броню мощьностью 150 единиц и способный передвинуться за один ход только на 40 единиц? Конечно, вы можете заново задать все значения параметров атаки. В результате ряд параметров будет дублироваться и вы получите настоящую головную боль, если решите внести глобальные изменения в параметры вооружений.

Гораздо лучше сгруппировать параметры нападения в типы атаки. Взгляните на следующий пример:

Тип атаки

лазер

Наносимый ущерб

100

Скорострельность

5

Косвенный ущерб от оружия

0

Изображение атаки

laser.bmp

Название подразделения

легкий танк

Тип атаки

лазер

Мощность брони

100

Скорость передвижения

50

Название подразделения

средний танк

Тип атаки

лазер

Мощность брони

150

Скорость передвижения

40

В данном примере я создаю тип атаки с названием «лазер». Затем я создаю два подразделения, которые используют один и тот же тип атаки, но отличаются параметрами защиты и скоростью передвижения. Эта ситуация показана на Рисунок 8.5.



Тип повреждений


Я уже касался этой темы при обсуждении брони. С каждым оружием должен быть связан тип наносимых повреждений. Упомянутая выше автоматическая винтовка стреляет пулями, в то время как обсуждавшийся ранее огнемет наности повреждения посредством пламени. Я рекомендую вам придумать различные типы наносимых повреждений, доступные вашим боевым единицам и поместить их в отдельную таблицу для последующих ссылок на нее.



Тип защиты


Каждому преступлению соответствует наказание. Так что для каждого типа атаки должен быть тип защиты, верно? Ладно, не всегда, но это утверждение можно принять в качестве отправной точки. Так или иначе, типы защиты работают точно так же как и типы атаки. Возьмем для примера рассматривавшиеся выше легкий и средний танки. Вместо того, чтобы в параметрах каждого из них задавать параметры брони, можно создать два типа защиты. Чтобы увидеть, как это действует, взгляните на Рисунок 8.6.



Титульный экран игры Command & Conquer ©2002 Electronic Arts All Rights Reserved





Воспоминания возвращаются — моя армия готова, БМП загружены, воздушные силы заправлены и готовы к вылету, ядерное оружие готово к использованию. Я отдаю приказ «вперед» отвлекающему подразделению БМП. Оно стремительно атакует защитные системы врага. Пока враг занят отвлекающим маневром БМП, я отправляю флот геликоптеров атаковать строительный центр, сердце действий врага. Тем временем мой 11-й танковый дивизион медленно продвигается, разрушая внешнюю стену вражеской базы. В это время подразделение геликоптеров прорвало передовую линию ПВО и атаковало нервный центр. Я даю зеленый свет БМП с инженерами, они направляются к вражеской базе. Мои геликоптеры были разрушены, но нанесли серьезный урон строительному центру — он уже дымится. Пришло время для ядерной атаки. Код введен и ядерные заряды отправляются в атмосферу, нацеленные на строительный центр. После попадания нервный центр врага прекращает существование. Он не выдержал комбинацию из атаки геликоптеров и атомного оружия. К этому времени танки глубоко вклинились на территорию вражеской базы. Инженеры прибыли к кратеру, находящемуся на месте бывшегго строительного центра и выгрузились из БМП. Они размещают заряды на оставшихся оборонительных сооружениях. Заряды взрываются и разрушают цели. После этого танки идут вперед и разрушают то, что осталось от базы. Подождите, часы действительно показывают пять утра?

Я не могу сосчитать, сколько часов потратил на игру C&C в моем первом офисе. Мой деловой партнер и я приглашали в офис пару друзей и играли до раннего утра. Мой первый офис располагался в крошечном помещении размерами десять на десять футов. Компьютеры Pentium-133 (последняя модель в то время) создавали столько тепла, что температура в помещении достигала 89 градусов по Фаренгейту. Ничто из этого нас не устрашало; мы набивались в офис и играли. Обычно собиралось более четырех человек, и шла игра на выбывание.



Точечный источник света


Переместимся ниже к функции CD3DFramework::RestoreDeviceObjects(), чтобы увидеть сделанные мной незначительные изменения ее кода. Отличий совсем немного; главное из них то, что в этом примере я использую новый тип источника света. Вот код для нового точечного источника света:

ZeroMemory(&d3dLight, sizeof(D3DLIGHT9)); d3dLight.Type = D3DLIGHT_POINT; d3dLight.Diffuse.r = 1.0f; d3dLight.Diffuse.g = 1.0f; d3dLight.Diffuse.b = 1.0f; d3dLight.Position.x = 0.0f; d3dLight.Position.y = -20.0f; d3dLight.Position.z = 20.0f; d3dLight.Attenuation0 = 1.0f; d3dLight.Attenuation1 = 0.0f; d3dLight.Range = 100.0f;

В предыдущих примерах программ использовался направленный источник света. DirectGraphics предлагает для использования и другие типы освещения, такие как зональное освещение (прожектор) и точечный источник света. В рассматриваемом примере я применяю точечный источник света.

Значение Type указывает системе визуализации тип источника света. В данном примере я задаю значение D3DLIGHT_POINT. Оно указывает, что система визуализации должна ожидать и использовать параметры для точечного источника света.

Структура Diffuse задает цвет источника света. Она содержит три компонента — красный, зеленый и синий. Значения каждого компонента должны находиться в диапазоне от 0.0 до 1.0. Значение 0.0 соответствует полному отсутствию данного цвета, а значение 1.0 — его максимальной интенсивности. Поскольку для каждого из цветов я указал значение 1.0, в результате будет получен белый свет максимальной интенсивности. Если вы не знакомы с освещением трехмерных сцен, я предлагаю вам поиграть с параметрами, чтобы увидеть, какое влияние они оказывают на сцену.

Структура Position содержит местоположение источника света в трехмерном пространстве. Я разместил его в точке с координатами (0.0, –20.0, 20.0).

Значение Attentuation0 определяет, как интенсивность света будет изменяться с увеличением расстояния. Это значение устанавливает константу, с которой начнется изменение интенсивности. Значение Attenuation1 устанавливает следующую константу, используемую для изменения интенсивности. Задав масштабирование в диапазоне от 1.0 до 0.0, я указываю, что с увеличением расстояния интенсивность света должна уменьшаться.

Значение Range указывает расстояние на котором источник света перестает оказывать влияние на объекты. В нашем примере источник света не освещает предметы, которые удалены от него более, чем на 100 единиц.



Трансляция сообщений функцией TranslateMessage()


Перед тем, как отправить сообщение в вашу очередь сообщений, вы должны транслировать его в символьные данные. Это делает функция TranslateMessage(). Для нее требуется единственный параметр — указатель на транслируемое сообщение. В примере я передаю функции адрес переменной, содержащей сообщение.

После того, как сообщение транслировано в символьные данные, вы можете поместить его в очередь сообщений с помощью функции DispatchMessage().



Треугольник образованный тремя точками в трехмерном пространстве





Как видно из Рисунок 6.9, местоположение каждой из вершин определяет геометрию образуемого в трехмерном пространстве объекта. Если изменить местоположение, то при визуализации изменится и геометрия объекта. Понятно, что для трехмерной визуализации это очень важно. Для хранения данных о местоположении я использую тип данных D3DXVECTOR3, поскольку он содержит элементы для хранения координат по осям X, Y и Z.



Три блочных карты с различным уровнем детализации





Если смотреть на Рисунок 5.14 слева направо, количество деталей на блочных картах постепенно увеличивается. Первая карта состоит из блоков только одного типа — с изображением травы. Это не слишком захватывающий ландшафт, поскольку смотреть практически не на что.

На второй карте добавлены камни. Это уже двухслойная карта. Первый слой содержит фоновые блоки с изображением травы, а во второй слой включены блоки с дополнительными деталями (камнями).

На третей карте помимо камней добавлены еще и деревья. Первый слой содержит обычные фоновые блоки, во второй слой включены блоки с дополнительными деталями фоновых блоков, а новый, третий, слой содержит блоки с изображениями деревьев.

Возможно, вы задаетесь вопросом, почему все эти блоки не помещены в один и тот же слой. Дело в том, что при использовании только одного слоя количество блоков увеличивается, и в результате у вас окажется больше блоков, чем действительно необходимо. Взгляните на блочные карты, изображенные на Рисунок 5.14. Если бы на средней карте использовался только один слой блоков, блок с изображением камней мог бы выглядеть так, как изображено на Рисунок 5.15.



Три блока трава песок и камни





С блоком с изображением камней, изображенным на Рисунок 5.16, связан альфа-канал, чтобы его изображение смешивалось с базовой текстурой. Это позволяет добавлять блок с изображением камней отдельным слоем поверх травы или песка, вместо того чтобы хранить отдельный блок с изображением камней для каждого базового блока. Я знаю, что в моем примере мы сэкономили только одни блок, но в законченной игре будут сотни, или даже тысячи блоков, и возможности для расточительства поистине безграничны, особенно если вы неблагоразумно используете блоки.



Трусливые пираты бороздят океан игры Utopia





Есть несколько погодных явлений: ливни, тропические штормы и ураганы. Ливни полезны, поскольку когда они проходят над полями, то приносят золото. Тропические штормы тоже приносят золото аналогичным образом, но они могут уничтожить поля, потопить корабли и, иногда, даже разрушить здания. Ураганы хуже всего — они уничтожают все на своем пути. Опасайтесь ураганов — если вы не будете действовать быстро, они могут уничтожить весь ваш рыбацкий флот.



Удобство и простота интерфейса


Удобство и простота использования интерфейса вашей игры безусловно одна из важнейших составляющих при выпуске высококачественного продукта. Если игроку не понравится интерфейс, он просто не будет играть в игру. Это и впрямь так просто. Я уверен, что в прошлом вам не раз встречались игры с плохим интерфейсом. Но я так же готов поспорить, что в такие игры вы играли не слишком долго. Проектируя свой интерфейс помните о следующих ключевых вопросах:

Сколько щелчков мышью должен сделать пользователь для выполнения обычных задач? Сколько уровней в вашей иерархии меню? Согласованы ли интерфейсы во всей игре?

Умные компьютеры


Посмотрим правде в глаза: управляемые компьютером противники исключительно тупы. Сегодня не существует стратегии реального времени, которая могла бы бросить настоящий вызов игроку не прибегая к мошенничеству. Чтобы увидеть жульничающий искуственный интеллект в действии, достаточно сыграть в Empire Earth. Игра дает управляемым компьютером противникам больше ресурсов и, я подозреваю, что цикл производства у них тоже короче. Конечно, сначала некоторые игры кажутся трудными, но сколько длится это ощущение? Совсем недолго. В будущем появятся достаточно мощные процессоры, позволяющие создать феноменальные алгоритмы искусственного интеллекта. Проблема, стоящая перед программистами сегодня, заключается в том, что алгоритмы искусственного интеллекта требуют значительных объемов вычислений. После формирования каждую секунду десятков кадров с различными спецэффектами, на долю искуственного интеллекта остается не так много времени процессора. Возможно, когда-нибудь нашим кремниевым врагам будет предоставлено достаточно тактов.



Управление аудиовизуальным потоком


В следующей строке кода заголовочного файла я создаю указатель на интерфейс IMediaControl с именем g_pMediaControl. Интерфейс управления аудиовизуальным потоком предназначен для контроля проходящих через граф фильтров данных. Этот интерфейс позволяет запустить, закончить и даже временно приостановить прохождение данных через граф. Вы можете представлять его как пульт дистанционного упроавления видеомагнитофона.

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

Таблица 7.7. Методы интерфейса IMediaControl

Метод Описание
GetState Возвращает состояние графа.
Pause Приостанавливает воспроизводимый в данный момент аудиовизуальный поток.
Run Запускает аудиовизуальный поток. Это аналог кнопки Play на пульте дистанционного управления видеомагнитофона.
Stop Завершает воспроизведение аудиовизуального потока.
StopWhenReady Более мягкая остановка.


Управление метками


Благодаря управлению метками вы можете получать преимущества от ясно помеченных ревизий исходного кода. Достигнув какой либо вехи в разработке, вы сохраняете весь ваш код и назначаете ему метку. Это позволяет вернуться назад и проверить все различные версии кода, требуемые для построения программы в какой-либо контрольной точке. Обратите внимание на следующие два файла:

Main.cpp

Версия 1.4

Версия 1.3

Версия 1.2

Версия 1.1

Версия 1.0

Main.h

Версия 1.2

Версия 1.1

Версия 1.0

Если теперь я попрошу вас выбрать код для бета-версии, какие файлы вы предложите? Вы можете взять самые последние версии файлов, но что, если они были написаны уже после выхода бета-версии? Без меток у вас нет никакого способа получить необходимую информацию, если только вы не решите создавать документ с номерами версий в каждой контрольной точке. Вы можете представить себе записывание версий нескольких тысяч файлов? Как можно предположить, это большая трудность. Теперь взгляните на те же самые два файла с метками:

Main.cpp

Версия 1.4

Версия 1.3 BETA

Версия 1.2

Версия 1.1

Версия 1.0

Main.h

Версия 1.2 BETA

Версия 1.1

Версия 1.0

Только взгляните на это! Возле тех файлов, которые использовались при создании бета-берсии стоит метка BETA. Все, что вам теперь остается сделать — выбрать исходный код с меткой BETA. К счастью программное обеспечение для контроля исходного кода позволяет снабжать код меткой или номером ревизии, так что для вас все автоматизировано. Вы должны также отметить насколько важны метки с точки зрения стабильности. Если бы для постройки бета-версии вы взяли бы самые последние файлы, у вас оказался бы выбран неправильный файл Main.cpp. Это могло бы вызвать огромные проблемы во всем проекте. Вышеупомянутый пример проиллюстрирован на Рисунок 4.2.



Управление ресурсами


Какая стратегия реального времени может считаться законченной без управления ресурсами? Держу пари, что вы не сможете привести более пары примеров. Управление ресурсами лежит в самом сердце большинства стратегий реального времени. Я говорю не о счетчике спрятанных в холодильнике консервированных бобов, а о материале, из которого сделаны легенды истории стратегий реального времени. У каждого лидера продаж прошлого было управление ресурсами. В Warcraft были древесина, золото и камни. В Command & Conquer был тибериум. В Age of Empires были древесина, золото, камень и пища. Этот список можно продолжать бесконечно.

Возможно, вы говорите себе «Большое дело! Управление ресурсами легко реализовать». Прежде чем сделать подобный вывод, минутку подумайте о том, насколько сложной может быть такая простая вещь, как управление ресурсами. Вернемся к моему предыдущему примеру с пищевыми ресурсами в игре Empire Earth. Описав только первые 15 минут игры, я уже отметил многие связанные с ним сложности и причины для беспокойства. В предыдущем разделе ресурсы рассматривались главным образом с точки зрения игрока. Теперь, когда мы подошли к вопросам реализации ресурсов, пришло время взглянуть на них с точки зрения разработчика.



Управление щелчками мыши


Внутри этого небольшого фрагмента кода находится вызов функции vCheckInput(). Возможно, вы недоумеваете для чего здесь нужен вызов функции timeGetTime(). Дело в том, что современные компьютеры настолько быстрые, что единственное нажатие на кнопку мыши длится десятки итераций цикла обработки сообщений. В результате одно нажатие на кнопку мыши активирует пункт меню десятки раз. Это приведет к проблемам, поскольку ваш интерфейс окажется слишком чувствительным к сделанным пользователем щелчкам мышью. Чтобы побороть проблему, я установил таймер, который разрешает обработку сообытия мыши не чаще чем раз в 50 милисекунд. Код проверяет текущее время и смотрит прошло ли 50 милисекунд с момента последнего вызова функции vCheckInput(). Если прошло достаточно времени, функция вызывается снова и таймер сбрасывается. Если время еще не истекло, выполнение кода продолжается, но никакой проверки введенных данных не происходит. Величина 50 милисекунд выбрана мной произвольным образом и вы можете изменить ее в соответствии с вашими вкусами. Если вам непонятно, какой эффект она оказывает, установите значение 0 и запустите пример (попробуйте щелкнуть в меню по кнопке Options).

Функция vCheckInput() проверяет текущую позицию указателя мыши и выполняет действия, зависящие от того, какое меню в данный момент показывается пользователю. Вот как выглядит код этой функции:

void vCheckInput(void) { bool bRet; char szZoneHit[64]; POINT Point; RECT rcWindowRect; int iMouseX; int iMouseY; // Проверка смещения окна GetWindowRect(g_hWnd, &rcWindowRect); // Обновление позиции указателя мыши GetCursorPos(&Point); // Вычисление реальных координат указателя мыши iMouseX = Point.x - g_iXOffset - rcWindowRect.left; iMouseY = Point.y - g_iYOffset - rcWindowRect.top; // Проверка попадания в активную зону bRet = MZones.bCheckZones( (short)iMouseX, (short)iMouseY, szZoneHit, g_bLeftButton, g_bRightButton); if(bRet) { // ЛОГИКА ДЛЯ ТИТУЛЬНОГО ЭКРАНА if(g_iCurrentScreen == 0) { // Переход к главному меню if(!stricmp(szZoneHit, "TITLE_SCREEN")) { // Делаем главное меню текущим g_iCurrentScreen = 1; // Устанавливаем активные зоны vSetupMouseZones(1); } // Переход к экрану завершения игры else if(!stricmp(szZoneHit, "EXIT_BUTTON")) { // Делаем завершающий экран текущим g_iCurrentScreen = 2; // Устанавливаем активные зоны vSetupMouseZones(2); } } // ЛОГИКА ГЛАВНОГО МЕНЮ else if(g_iCurrentScreen == 1) { // Переход к экрану завершения игры if(!stricmp(szZoneHit, "EXIT_BUTTON")) { // Делаем завершающий экран текущим g_iCurrentScreen = 2; // Устанавливаем активные зоны vSetupMouseZones(2); } else if(!stricmp(szZoneHit, "MAINMENU_NEWGAME")) { // Добавьте сюда код для начала новой игры } else if(!stricmp(szZoneHit, "MAINMENU_LOADGAME")) { // Добавьте сюда код для загрузки игры } else if(!stricmp(szZoneHit, "MAINMENU_SAVEGAME")) { // Добавьте сюда код для сохранения игры } else if(!stricmp(szZoneHit, "MAINMENU_OPTIONS")) { // Делаем меню параметров текущим g_iCurrentScreen = 7; // Устанавливаем активные зоны vSetupMouseZones(7); } } // ЛОГИКА ЭКРАНА ЗАВЕРШЕНИЯ ИГРЫ else if(g_iCurrentScreen == 2) { // Выходим из программы, если пользователь // нажмет любую кнопку мыши if(!stricmp(szZoneHit, "TITLE_SCREEN")) { // Сообщаем WinMain() о завершении программы g_iCurrentScreen = 3; } } // ЛОГИКА МЕНЮ ПАРАМЕТРОВ else if(g_iCurrentScreen == 7) { // Переход к экрану завершения игры if(!stricmp(szZoneHit, "EXIT_BUTTON")) { // Делаем экран завершения игры текущим g_iCurrentScreen = 2; // Устанавливаем активные зоны vSetupMouseZones(2); } // Возврат к главному меню else if(!stricmp(szZoneHit, "OPTIONS_BACK")) { // Делаем главное меню текущим g_iCurrentScreen = 1; // Устанавливаем активные зоны vSetupMouseZones(1); } } } }

Уровень энергии


Хотя тибериум — это самый важный материал в Command & Conquer, без энергии ваши постройки не смогут функционировать. Индикатор энергии в нижнем правом углу интерфейса отображает объем используемой энергии в сравнении с доступной. Когда игрок строит электростанции цвет полосы становится более зеленым, а сама она растет. Когда строятся другие здания цвет полосы становится более красным. Такой стиль индикации дает игроку визуальное представление энергоснабжения в сравнении с потребностями. Это весьма изобретательно, поскольку чтобы представить текущую ситуацию игроку не надо счтиывать различные числа. Здесь мы пришли к еще одному ключевому пункту: постарайтесь, чтобы ваша игра не выглядела похожей на электронную таблицу. Для этого вам придется использовать графические представления числовых значений.



Условнобесплатные программы


Ах, условно-бесплатные программы, западня для стремлений. Это простейший метод, который вы можете выбрать для распространения вашей игры. Существует много открытых сайтов, на которых любой желающий может разместить свою игру для бесплатного скачивания. Все, что вам необходимо сделать — это разместить на сайте установочный пакет для скачивания и указать адрес для людей, которые пожелают сделать пожертвование.

Единственная проблема с условно-бесплатными программами заключается в том, что ваши шансы заработать реальные деньги практически равны нулю. Честно говоря, гораздо больше шансов быть пораженным молнией. Если ваш основной мотив — заставить людей запускать вашу игру, то условно-бесплатные программы можно рассматривать как полезный метод. Только не ожидайте, что таким образом вы заработаете на жизнь.

Одно из возможных мест для размещения ваших файлов называется FilePlanet. Оно располагается по адресу www.fileplanet.com. Вы можете разместить файл размером до 200 Мбайт, и он будет доступен другим посетителям для скачивания. (Убедитесь, что указали адрес для тех людей, которые захотят сделать пожертвования!)

Альтернативный метод выпуска условно-бесплатных программ состоит в создании сокращенной версии вашей игры. Предположим, ваша игра посвящена сражениям Второй Мировой войны. Включите в свободно распространяемую версию всего лишь несколько миссий, и сообщите игроку, что за загрузку полной версии следует заплатить. Это замечательный способ продемонстрировать вашу игру и продать несколько копий.



Установка темпа воспроизведения


Давайте двигаться дальше. Следующий фрагмент кода задает темп песни. Темп музыки определяет насколько быстро (или медленно) она воспроизводится. Данный параметр может использоваться для того, чтобы голос актера звучал похоже на белку или на Дарта Вейдера. Все это делает функция IMediaSeeking::SetRate(). Она получает единственный параметр — темп воспроизведения. Если вы хотите, чтобы для воспроизведения песни использовался ее темп по умолчанию, укажите здесь значение 1. Чтобы услышать, как артист поет с удвоенной скоростью, укажите значение 2. Кроме того, вы можете использовать различные промежуточные значения. Например, я люблю воспроизводить музыку группы Metallica с темпом 1.25, чтобы получить чистый скоростной металл. Вы почти можете видеть, как руки Ларса дымятся от этого!



Utopia от Intellivision


В далекой, далекой местности, в двух милях вниз по грунтовой дороге мимо старой рыжей собаки, слева от большого дуба домашняя игровая система Intellivision населяла несколько гостинных в Соединенных Штатах.

(Отсюда начинается ретроспектива.) Жаркий летний полдень, я и мой брат Эдди участвуем в решающем сражении. Этому моменту предшествовали несколько недель планирования, маневров и пропаганды. Мой боевой флот вторгся в его водное пространство и разрушил рыболовные суда. Его люди голодают. Единственная надежда Эдди — сохранить свои посевы до следующего ливня. Но увы, я я подрядил армию симпатизирующих моим целям мятежников, чтобы они уничтожали поля. Бим, бам, бом! Ход закончен. Черт, теперь придется ждать следующего хода, чтобы установить мое марионеточное правительство на его острове.

Я провел много дней играя в Utopia с любой жертвой, — я имею в виду противника, — которую я мог уговорить сыграть. Utopia была весьма оригинальной игрой, в которой игрок действовал в режиме реального времени, а ходы использовались чтобы сообщать о произошедшем и подсчитать счет. Вы могли играть в однопользовательском режиме, но настоящее удовольствие было в игре с другим человеком. Utopia пердставляла собой смесь SimCity и Command & Conquer. Вы должны увеличивать благосостояние своих людей и в то же время уменьшать благосостояние вашего противника.



Визуализация трехмерных моделей

Готовы ли вы к визуализации трехмерных блоков? Я готов! Вот новый и улучшенный код визуализации трехмерных блоков:

HRESULT CD3DFramework::Render() { D3DXMATRIX matTranslation; int iX, iY; int iCurTile; float fXPos; float fYPos; // Очистка порта просмотра m_pd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0L); // Начало создания сцены if(SUCCEEDED(m_pd3dDevice->BeginScene())) { for(iY = 0; iY < 10; iY++) { // Горизонтали for(iX = 0; iX < 10; iX++) { // Вычисляем, какой блок отображать iCurTile = m_iTileMap[iX + (iY * m_shTileMapWidth)]; // Вычисляем местоположение блока fXPos = (-5.0f * iX) + 22.5f; fYPos = (-5.0f * iY) + 32.5f; // Устанавливаем позицию блока D3DXMatrixTranslation(&matTranslation, fXPos, fYPos, 0.0f); m_pd3dDevice->SetTransform(D3DTS_WORLD, &matTranslation); // Отображаем блок m_pObject[iCurTile]->Render(m_pd3dDevice); } } // Показываем частоту кадров m_pStatsFont->DrawText(2, 0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats); // Показываем сведения о видеокарте m_pStatsFont->DrawText(2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats); // Завершаем создание сцены m_pd3dDevice->EndScene(); } return S_OK; }

Обратите внимание, насколько этот код похож на предыдущие примеры. Визуализация трехмерных моделей на самом деле не так уж и сложна. В рассматриваемом примере присутствует обычный набор ключевых фрагментов. Есть внешний цикл для визуализации блоков вдоль оси Y и внутренний цикл для визуализации блоков вдоль оси X. Местоположение блока вычисляется обычным способом с небольшим изменением координат. Главное отличие — вызов функции D3DXMatrixTranslation().

Функция D3DXMatrixTranslation() создает матрицу перемещения, содержащую набор трехмерных координат. Перемещение — это всего лишь причудливое слово, обозначающее задание местоположения. Таким образом, матрица перемещения задает местоположение объекта в трехмерном пространстве. Как только местоположение объекта установлено, вызов функции SetTransform() вводит задающую местоположение матрицу в действие.

ВНИМАНИЕ Убедитесь, что значение первого параметра функции SetTransform() равно D3DTS_WORLD. В ином случае вы измените что угодно, но только не местоположение объекта!

Теперь объект находится в требуемой позиции, и для его отображения следует вызвать функцию Render(). Функция Render() принадлежит объекту CD3DMesh и выполняет за вас всю необходимую работу. Вам надо только передать указатель на трехмерное устройство, и объект сделает все остальное. Разве не круто?

Теперь вы знаете как отображать трехмерные блоки. Получен ответ на древний вопрос! Я признаю, что еще многое требуется изучить, но вы уже на пути к созданию полностью трехмерной стратегической игры с блочной графикой.