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

       

Как вычислить видимую позицию


Следующая функция в программе, DrawBitmap(), является искусственной функцией, которая напоминает обычную графическую функцию, используемую для отображения двухмерного растрового изображения. В вызове этой воображаемой функции присутствуют три параметра: графический блок, экранная координата X и экранная координата Y.

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

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

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

Вернемся к примеру на Рисунок 5.10. Координаты X и Y в пикселах для блока с координатами на карте 5, 5 будут равны (5 * 64), (5 * 64), или 320, 320. Большинство графических функций позволяют указать координаты X и Y растрового изображения, так что пример не должен вызывать никаких затруднений.

Вот как вычисляются координаты X и Y для блока в пикселях:

Координата X в пикселях = Координата X блока на карте * Ширина блока

Координата Y в пикселях = Координата Y блока на карте * Высота блока



Как загрузить сегмент


Теперь вы должны загрузить сегмент в объект исполнителя. Эту задачу выполняет функция IDirectMusicSegment8::Download(), прототип которой выглядит так:

HRESULT Download( IUnknown* pAudioPath );

Как приятно и просто. Всего один параметр, который является указателем на интерфейс, в который загружается сегмент. Я в данном параметре передаю указатель на объект исполнителя g_pPerformance.

Взгляните на Рисунок 7.2, чтобы увидеть описанные к данному моменту этапы.



Карта с составленной из блоков буквой О





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

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



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


Сперва я опишу структуру класса звуковой системы, которая показана на Рисунок 7.8.



Классическая игра Empire ©2003 Killer Bee Software All Rights Reserved





Если вас заинтересовала игра Empire, проверьте сайт Killer Bee Software. Марк Кинкэд из Killer Bee Software приобрел права на серию Empire и планирует выпустить восстановленную игру Empire и обновленную версию.



Ключевые типы данных Direct3D


В первых строках кода объявлены несколько ключевых переменных DirectX, необходимых для работы программы. Вот как выглядит этот фрагмент:

LPDIRECT3D9 g_pD3D = NULL; LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; LPDIRECT3DVERTEXBUFFER9 g_pVBInterface = NULL; LPDIRECT3DTEXTURE9 g_pTexture[32];

Первая переменная относится к типу LPDIRECT3D9 и названа g_pD3D. Это указатель на интерфейс Direct3D. Без этого интерфейса программа не сможет выполнять никаких действий, относящихся к отображению трехмерной графики на экране. Это важный фрагмент головоломки, так что убедитесь, что помните о нем.

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

Следующая переменная, g_pVBInterface типа LPDIRECT3DVERTEXBUFFER9 используется для хранения необходимых примеру данных трехмерной геометрии. Буфер вершин хранит вершины, являющиеся основными кирпичиками из которых строятся трехмерные объекты. Чуть позже мы обсудим эту тему более подробно.

И, наконец, в коде объявлен массив текстур с именем g_pTexture[]. В нем хранится 32 элемента типа LPDIRECT3DTEXTURE9. Это всего лишь место хранения изображений, необходимых примеру. Благодаря вспомогательным библиотекам DirectX использовать текстуры очень удобно. Кроме того, они очень важны, так что уделите им достаточно внимания. Может быть вам интересно, почему я создал массив для 32 текстур. Для этого нет никаких причин. В программе используется только семь элементов массива, но всегда лучше предохраняться, чем извиняться!

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

int g_iXOffset = 0; int g_iYOffset = 0; int g_iWindowWidth = 640; int g_iWindowHeight = 480;

Сначала перечислены две целочисленных переменных, g_iXOffset и g_iYOffset, которые применяются для хранения смещения клиентской области окна относительно координат окна. Я объясню их назначение позже, при обсуждении файла main.cpp. Сейчас просто примите их такими, как они есть, так же как смерть и налоги.

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



Компиляция и выполнение кода




Итак, ваша программа целиком введена и вы готовы идти дальше. Чтобы скомпилировать программу в Visual C++ 6.0 вам необходимо сделать активным окно с компилируемой программой и нажать клавишу F7. Если все закончится успешнно, то в окне с выходными данными компилятора вы увидите следующий текст:

---------Configuration: CreateWindow - Win32 Debug------------ Compiling... CreateWindow.cpp Linking... CreateWindow.exe - 0 error(s), 0 warning(s)

Если во время компиляции будут обнаружены какие-либо ошибки, заново проверьте код и убедитесь, что все набрано правильно. Когда будут исправлены все ошибки, вы сможете запустить программу, нажав комбинацию клавиш Ctrl+F5. В результате начнет выполняться программа активного проекта. На Рисунок 2.13 показано, как должна выглядеть работающая программа рассматриваемого примера.



Конечная цель сюжета


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

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



Контроль исходного кода

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

Делали ли вы когда нибудь изменение в стабильном коде только чтобы обнаружить, что оно привело к появлению ошибки или к нестабильности? Если да, то возврат к предыдущей версии и вычисление, какие изменения привели к возникновению проблем, может быть действительно болезненным. Используя контроль версий, вы можете получить старый код, проверить его и вычислить где появилась ошибка. Это дает как минимум отправную точку для последующих поисков. Кроме того утилиты, такие как WinDiff, позволяют вам сравнивать две версии кода и точно видеть различия между ними. Это неоценимо!

СОВЕТ Утилита WinDiff включена в операционную систему Windows. Если вы используете Windows 2000, ME или XP, она должна у вас быть. Щелкните по кнопке Start (Пуск), выберите пункт Run (Выполнить), и введите WinDiff. После щелчка по кнопке OK, вы увидите окно программы WinDiff. Поиграйтесь с программой и попробуйте сравнить два похожих файла, чтобы увидеть различия. Держу пари, вы полюбите эту утилиту!

На рынке существует множество программ, которые помогут вам в организации контроля исходного кода. Наиболее попуярной из тех, которые я видел до настоящего времени, является CVS. Чтобы увидеть ее в действии, посетите сайт www.sourceforge.net. На SourceForge.net существуют тысячи проектов с открытым исходным кодом, которые вы можете посмотреть. Там есть даже свободно распространяемые стратегические игры и игровые библиотеки! Информацию о CVS можно получить на сайте www.cvshome.org.



Координаты окна определяют его позицию в зависимости от того является окно дочерним или нет





Значения координат обоих окон на Рисунок 2.11 равны (10,10). Большее окно является родительским, поэтому его координаты (10,10) задают размещение окна почти вплотную к левой и верхней границам экрана. Меньшее окно является дочерним. Поэтому его координаты являются смещениями относительно родительского окна. Это означает, что координаты (10,10) в действительности представляют (parentx+10,parenty+10) или (20,20) в системе координат экрана.

Седьмой параметр создающей окно функции задает ширину окна в пикселах. В нашем примере ширина окна равна 320 пикселам.

Восьмой параметр задает высоту окна в пикселах. В примере высота окна равна 200 пикселам.

Девятый параметр функции указывает родителя данного окна. Он полезен, когда создается приложение с несколькими окнами. Поскольку в рассматриваемом примере есть только одно главное окно, данному параметру присваивается значение NULL чтобы указать, что у окна нет родителя.

Десятый параметр используется для задания дескриптора связанного с окном меню. В примере нет никаких меню, поэтому данному параметру присваивается значение NULL.

Одиннадцатый параметр используется для задания дескриптора экземпляра модуля. В этом параметре вы передаете дескриптор экземпляра, полученный функцией WinMain() в одном из ее параметров.

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

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




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





На Рисунок 6.11 представлены две сетки. Сетка слева изображает текстуру размером 16 на 16 точек. Сетка справа изображает текстуру размером 32 на 32 точки. Под каждой сеткой изображена шкала координат текстуры с диапазоном значений от 0.0 до 1.0. Поскольку шкала связана с размером текстуры, размеры шкал совпадают с размерами сеток. Шкала справа в два раза больше, чем шкала слева, но диапазон значений на ней все тот же — от 0.0 до 1.0. Я понимаю, что говорю очевидные вещи, но они важны для понимания координат текстур.

Корме того, для каждой сетки я показываю диапазон координат точек. Меньшая сетка начинается с точки (0, 0) и заканчивается точкой (15, 15). Большая текстура начинается с точки (0, 0) и простирается до точки (31, 31). Здесь нет ничего особенного; числа просто обозначают координаты точек.

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



Корабли


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

Рыблолвецкими судами можно управлять, так что вы можете отправить их в плавание по океану. Когда вы помещаете рыболовецкий корабль на квадрат с косяком рыбы, он начинает производить золото. Кроме того, рыболовецкое судно автоматически кормит 500 человек. Эти корабли абсолютно необходимы игроку.

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



Метрики качества


Одно из преимуществ наличия истории ошибок состоит в том, что на основании исторического архива вы можете создавать метрики качества. Вы можете вернуться во времени и увидеть, где было обнаружено больше всего ошибок. Была ли большая часть ошибок найдена во время тестирования? Или они были обнаружены в бета-версии? А может они были обнаружены во время производства? Ответив на эти вопросы вы сможете обнаружить дыры в ваших стандартах качества и попытаться устранить их. Если большая часть ошибок была обнаружена во время тестирования, попытайтесь выяснить, почему разработчики отправляют тестировщикам код, который содержит так много ошибок. Если большинство ошибок найдено в бета-версии, необходимо установить, почему тестировщики пропустили их. Если большинство ошибок обнаружено во время производства, вы должны выяснить, почему их пропустила команда, выпускающая бета-версию. Хуже всего, когда ошибка достигает фазы производства. Устранение ошибок на этом этапе может обойтись вам очень дорого.

Суть в следующем: метрики качества позволяют изолировать узкие места в вашем цикле тестирования и отслеживать вносимые усовершенствования.



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


Ах, моя любимая часть стратегических игр — многопользовательская игра! Лично я не слишком много играю в стратегии реального времени в режиме однопользовательской кампании, а предпочитаю сразиться с другими игроками в сети или играть против компьютера вместе с несколькими друзьями. Нет ничего более интересного, чем жаркая битва с другими людьми вокруг. Тем не менее, однопользовательский режим очень важен, поскольку многие люди наслаждаются им.

Раз вы собираетесь писать стратегическую игру, вам следует предусмотреть включение поддержки многопользовательского режима игры через Интернет или локальную сеть. Сейчас для этой цели обычно применяются два метода: использование сокетов или DirectPlay. По ряду причин я предпочитаю DirectPlay, но низкоуровневые сокеты работают ничуть не хуже.

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

С поддержкой многопользовательского режима связано несколько технических проблем. Одна из них — поддержка сохранения игры. Лишь несколько стратегических игр поддерживают такую возможность в многопользовательском режиме. Единственной, где я успешно использовал ее, была Age of Empires II. Я должен сказать, что это очень полезная возможность. Поскольку сеанс стратегической игры может длиться несколько часов, возможность сохранения неоценима.



Многослойные блоки


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

Многослойные блоки используются для добавления деталей и глубины составленной из блоков карте.



Мозаика из блоков на экране





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

Теперь, когда вы знаете о том, что такое блоки, как насчет нескольких примеров? Взгляните на Рисунок 5.2, где показан пример нескольких блоков для ландшафтной библиотеки.



Начальные цели


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



Давайте продолжим. Запустите вашу копию


Давайте продолжим. Запустите вашу копию Visual C++ 6.0. Вы должны увидеть экран, аналогичный представленному на Рисунок 2.2.


Настройка параметров отображения


Теперь объект интерфейса трехмерной графики готов к работе и пришла пора настроить параметры отображения. В нашем примере первый этап — это определение формата вторичного буфера для визуализации. Чтобы освежить знания об экранных буферах, взгляните на Рисунок 6.16.



Настройка среды визуализации


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

D3DXMATRIX *D3DXMatrixOrthoLH( D3DXMATRIX *pOut, FLOAT w, FLOAT h, FLOAT zn, FLOAT zf );

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

Второй параметр, w, задает ширину области просмотра.

Третий параметр, h, задает высоту области просмотра.

Четвертый параметр, zn, задает минимальное значение по оси Z. Все объекты, расположенные ближе к камере, чем указанное значение, не отображаются.

Пятый параметр, zf, задает максимальное значение по оси Z. Объекты, расположенные дальше от камеры не отображаются.

Взгляните на фрагмент моего кода, который выполняет эту операцию:

D3DXMatrixOrthoLH(&matproj, (float)g_iWindowWidth, (float)g_iWindowHeight, 0, 1); g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matproj);

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

Проекционная матрица является основой и готова к использованию. Последним шагом в настройке проекции для двухмерной визуализации является вызов функции SetTransform() с параметром D3DTS_PROJECTION. Он активирует проекционную матрицу и открывает путь к двухмерной визуализации.

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

Следующей настройкой состояния визуализации является отключение аппаратного освещения для сцены. Этот шаг не является необходимым; я добавил его только чтобы упростить данный пример. Для выключения освещения переменной состояния визуализации с именем D3DRS_LIGHTING присваивается значение FALSE.

Затем я отключаю Z-буфер, присваивая переменной состояния визуализации D3DRS_ZENABLE значение FALSE. Я выключаю z-буферизацию для того, чтобы никакие фрагменты моей двухмерной картинки не были удалены во время визуализации.

Последним шагом настройки состояний визуализации является отключение записи в z-буфер. Для этого переменной состояния визуализации D3DRS_ZWRITEENABLE присваивается значение FALSE. Я делаю это чтобы предотвратить изменение z-буфера операциями двухмерной визуализации. Это очень важно, когда в одной сцене совместно используются двухмерные и трехмерные элементы. Вы же не хотите, чтобы двухмерные элементы интерфейса изменяли буфер глубины, используемый вашими трехмерными объектами.

На этом настройка среды визуализации закончена. Пришло время функции vInitInterfaceObjects().



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


Очевидно, сперва следует выбрать название подразделения. Если ваша игра относится ко Второй Мировой войне, вы можете использовать названия «Танк Т-34» или «Танк "Тигр"». Возможно, сюжет вашей игры разворачивается в будущем и подразделения будут называться «Плазменный танк» или «Лазерный танк». Выбор названий может показаться легкой задачей, но я советую отнестись к нему ответственно. Если вы не создаете реалистичную игру с использованием названий реально существующих боевых единиц, я рекомендую выбирать такие названия, которые будут объяснять, что данное подразделение делает. Если вы назовете ваш фантастический танк «Змей», это почти ничего не скажет игроку и не будет служить подсказкой во время игры. Если вы чувствуете, что такие имена необходимы, пользуйтесь ими в качестве префиксов (например, «Змей: Тяжелый лазерный танк»).

Хорошим примером из реального мира является лучник из игры Warcraft III: Reign of Chaos. Лучник — это эльфийский стрелок, выпускающий во врагов стрелы из своего лука. Название интуитивно понятно и соответствует подразделению. Изображение данного подразделения приведено на Рисунок 8.1.



Нефтеперегонный завод





Теперь, когда ресурсы игры Battle Armor остались позади, вы увидели процесс определения ресурсов для ваших стратегий реального времени. Он сводится к следующим этапам:

Определение необходимых ресурсов. Определение способов получения ресурсов. Определение ограничений на получение ресурсов.

Единственный предел создания ресурсов для вашей игры — ваше воображение. Для Battle Armor был использован очень простой метод. Я поощряю вас придумывать более занимательные типы ресурсов и способы их добычи.



Некоторые стандартные значки Windows ©2002 Microsoft All Rights Reserved





Восьмой член структуры данных WNDCLASSEX очень похож на седьмой, за исключением того, что он задает используемый окном курсор. Его тип HCURSOR, а имя — hCursor. Тип HCURSOR это еще один замаскированный дескриптор. Обычно здесь указывается значение дескриптора класса курсора, который будет использован в вашей программе для ее собственного курсора. Но вместо того, чтобы создавать нестандартный курсор, я воспользуюсь функцией LoadCursor().

Функция LoadCursor() похожа на функцию LoadIcon() за исключением того, что она загружает ресурсы курсора, а не ресурсы значка. Вот ее прототип:

HCURSOR LoadCursor( HINSTANCE hInstance, LPCTSTR lpCursorName );

Первый параметр называется hInstance, и содержит дескриптор экземпляра модуля, чей исполняемый файл содержит курсор, который вы собираетесь использовать. В своем примере я присваиваю данному параметру значение NULL. Это позволяет использовать встроенные курсоры Windows. Их также часто называют стандартными курсорами. Не чувствуете ли вы, что уже читали что-то похожее?

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

Таблица 2.4. Константы для стандартных курсоров

Значение Описание
IDC_APPSTRING Это курсор в форме стандартной стрелки с присоединенными к ней песочными часами. Обычно, данный курсор устанавливается, когда ваша программа занята.
IDC_ARROW Стандартный курсор Windows.
IDC_CROSS Создает курсов, выглядящий как перекрестье прицела.
IDC_HELP Этот курсор выглядит как стандартная стрелка с присоединенным к ней вопросительным знаком. Его хорошо использовать, когда пользователю предоставляется возможность задать вопрос.
IDC_IBEAM Курсор в форме буквы «I». Обычно используется в режиме ввода и редактирования текста.
IDC_NO Курсор в виде перечеркнутого круга. Его можно использовать, когда пользователь наводит курсор на область, которая не реагирует на щелчки кнопок мыши.
IDC_SIZEALL Курсор с перекрещенными стрелками. Применяется, когда пользователь изменяет размер окна или графического элемента.
IDC_SIZENESW Еще один курсор для изменения размера. В отличие от предыдущего курсора, у которого стрелки направлены во все четыре стороны, здесь стрелки направлены только на северо-восток и юго-запад.
IDC_SIZENS То же, что и предыдущий курсор, но стрелки направлены на север и на юг.
IDC_SIZENWSE То же, что и предыдущие два курсора, но стрелки направлены на северо-запад и юго-восток.
IDC_SIZEWE Еще один курсор со стрелками. В данном случае они направлены на запад и на восток.
IDC_UPARROW Курсор в виде стрелки, направленной вверх.
IDC_WAIT Курсор в виде песочных часов. Я рекомендую использовать этот курсор в тех случаях, когда ваша программа занята и пользователь может только ждать пока она не закончит работу.

После того, как вы освоились с относящейся к курсорам частью структуры данных окна, пришло время поговорить о цвете фона. Чтобы задать цвет фона вашего окна, вы просто указываете желаемый цвет в члене данных hbrBackground. Это девятый элемент структуры, и он имеет тип HBRUSH. Возможно, вы подумали, что тип HBRUSH это всего лишь дескриптор класса кисти. Но этот член данных более гибкий и позволяет задать как дескриптор кисти, которая будет использоваться, так и значение используемого цвета. Существует одно требование — задавая цвет, вы должны использовать одно из следующих значений:

COLOR_ACTIVECAPTION COLOR_APPWORKSPACE COLOR_BACKGROUND COLOR_BTNFACE COLOR_BTNSHADOW COLOR_BTNTEXT COLOR_CAPTIONTEXT COLOR_GRAYTEXT COLOR_HIGHLIGHT COLOR_HIGHLIGHTTEXT COLOR_INACTIVEBORDER COLOR_INACTIVECAPTION COLOR_MENU COLOR_MENUTEXT COLOR_SCROLLBAR COLOR_WINDOW COLOR_WINDOWFRAME COLOR_WINDOWTEXT

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

wndclass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT;

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

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

HGDIOBJ GetStockObject( int fnObject );

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

Моей программе посчастливилось использовать встроенный объект WHITE_BRUSH. Он окрашивает фон окна в белый цвет. В таблице 2.5 приведены еще несколько значений, которые можно использовать в качестве аргумента функции GetStockObject().

Таблица 2.5. Предопределенные объекты Windows

Значение Описание
BLACK_BRUSH Соответствует названию. Объект предоставляет кисть, рисующую черным цветом.
DKGRAY_BRUSH Темно-серая кисть.
GRAY_BRUSH Серая кисть.
HOLLOW_BRUSH Возможно вы смотрели фильм «Человек-невидимка»? (Если не смотрели, не волнуйтесь — все равно фильм плохой.) Эта кисть невидима для пользователя, так же как и человек-невидимка. Это означает, что при использовании данной кисти не появляется никаких цветов. Аналогичным образом действует кисть NULL_BRUSH.
LTGRAY_BRUSH Светло-серая кисть.
NULL_BRUSH То же самое, что и HOLLOW_BRUSH.
WHITE_BRUSH Белая кисть. Именно она используется в моем примере.
BLACK_PEN Черное перо. Перья не влияют на цвет фона. Вы должны использовать их для задания цвета текста.
WHITE_PEN Белое перо.
ANSI_FIXED_FONT Объект устанавливает моноширинный системный шрифт.
ANSI_VAR_FONT Объект устанавливает системный шрифт с переменной шириной символов.
DEFAULT_GUI_FONT Устанавливается заданный по умолчанию системный шрифт.
DEFAULT_PALETTE Объект установленной по умолчанию палитры.

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

Одиннадцатый член структуры данных также является строкой, которая должна завершаться нулевым символом. Его имя — lpszClassName. Как сказано в имени, эта строка используется для задания имени класса окна. Имя класса является уникальным идентификатором типа класса. Поэтому очень важно, чтобы вы не использовали заданное здесь имя для других классов вашей программы.

Двенадцатый, и последний, член структуры WNDCLASSEX — это переменная с именем hIconSm. Она аналогична члену данных hIcon, за исключением того, что здесь задается используемый программой маленький значок. В моем примере для этого применяется уже знакомая вам функция LoadIcon().

Ну что? Мы завершили изучение структуры данных WNDCLASSEX! Теперь пришло время зарегистрировать ее.



Несколько блоков из набора





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



Нормаль треугольной грани





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



Новый блок с изображением дороги помешается рядом с существующими дорогами





Итак, взглянем на Рисунок 5.25. На только что созданный блок с изображением дороги указывает большая стрелка. Представленная здесь проблема заключается в необходимости вычислить, каким образом должно быть повернуто изображение дороги на вновь добавляемом блоке. Мозг сообщает вам: «Элементарно — это должен быть угловой блок». (С робкой надеждой мозг спрашивает, не ошиблись ли вы в выборе профессии.) Независимо от способа, по ряду причин вы твердо уверены, что здесь необходим угловой блок.

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



Объекты для постройки


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



Обнаружение активных зон


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



Обнаружение сообщений кнопок мыши


Работа функции vCheckInput() зависит от состояния кнопок мыши. К счастью, определение состояния кнопок мыши — очень простой процесс. Взгляните на функцию fnMessageProcessor() из программы. Вот как выглядит ее фрагмент:

switch(msg) { case WM_LBUTTONDOWN: g_bLeftButton = 1; break; case WM_LBUTTONUP: g_bLeftButton = 0; break; case WM_RBUTTONDOWN: g_bRightButton = 1; break; case WM_RBUTTONUP: g_bRightButton = 0; break; case WM_DESTROY: PostQuitMessage(0); return 0; default: break; }

Первые четыре инструкции case проверяют системные сообщения мыши. Первое из них, WM_LBUTTONDOWN, позволяет узнать что нажата левая кнопка мыши. Следующее, WM_LBUTTONUP, сообщает вам, что левая кнопка мыши была отпущена. То же самое справедливо и для правой кнопки мыши, только используются сообщения WM_RBUTTONDOWN и WM_RBUTTONUP.

Простейший способ хранить состояние мыши — воспользоваться глобальными переменными. Для хранения состояния двух кнопок мыши я использую переменные g_bRightButton и g_bLeftButton. Что может быть проще?



Обработчик сообщений

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

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

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

Очередь событий


Вторая ключевая область называется очередь событий (event queue). С этого момента проявляются отличия программирования в Windows от программирования в других операционных системах. События, о которых я говорил ранее, помещаются в очередь событий. Почти всегда, когда программа Windows получает входные данные или уведомление от пользователя или операционной системы, происходит генерация события, которое сохраняется в очереди. Программы используют систему очередей, чтобы сообщения никогда не терялись. Кроме того, сообщение может храниться в очереди так долго, как требуется программе.

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

События, помещенные в очередь обрабатываются по принципу «первым пришел — первым ушел (FIFO)». Это означает, что первое помещенное в очередь событие будет первым вытолкнуто из очереди, когда программа запросит следующее событие. В этом нет ничего революционного — всего лишь стандартная очередь.

На Рисунок 2.1 в очереди событий содержится три сообщения: WM_KEYDOWN, WM_KEYUP и WM_SIZE. Для разработчика они представляют три возможных действия пользователя. Сначала пользователь нажал клавишу и затем отпустил ее. После этого пользователь изменил размер окна.



Очки повреждений


Термин «очки повреждений» (hit points) пришел из ролевых игр старой школы, но нашел свое место и в других, связанных со сражениями компьютерных играх. Вы уже знаете, как вычислить защищенность или оценку брони, а теперь следует учесть какое количество повреждении может выдержать боевая единица до своего разрушения.

Для очков повреждений нет никаких сложных алгоритмов — это обычное число, показывающее сколько единиц повреждений может получить боевая единица до своего уничтожения. Давайте вернемся к примеру с броней. У пехотинца, получившего 90 единиц повреждений, было 50 очков повреждений, а значит в результате атаки он погиб. С другой стороны, танк получил только 10 единиц повреждений, обладая 1000 очков повреждений. Чтобы упростить жизнь я рекомендую создать для боевых единиц вашей игры шкалу от 50 до 5000 единиц. Слабейший из слабых может выдержать только 50 единиц повреждений, в то время, как самая сильная боевая единица может получить до 5000 единиц повреждений. Видите, как просто?



Огневая мощь боевых единиц


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



Океан


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



Окно программы D3D_MouseZones





На Рисунок 6.22 показан экран с главным окном программы. В данном примере работают не все кнопки меню, а только Options и Exit. Программа устанавливает активные зоны и реагирует на щелчки пользователя по кнопкам меню. Теперь загрузите проект, чтобы перейти к изучению кода. Имя проекта — D3D_MouseZones.



Окно программы демонстрирующей





Проект, содержащий код программы называется D3DFrame_Isometric2DSpriteTiles. Вы найдете его в сопроводительных файлах. Загрузите его и следуйте дальше.



Окно программы демонстрирующей визуализацию двухмерных блоков





Выглядит не слишком захватывающе? Хорошо, я понимаю, что вы уже видели десятки блочных карт, но для этой в сопроводительных файлах к книге (www.wordware.com/files/games) доступен полный исходный код, создающий ее! Загрузите проект D3DFrame_2DTiles, чтобы двигаться дальше.



Окно программы DMusic_PlayMIDI





Спорим, что программа выглядит знакомо? Я сделал лишь несколько косметических изменений в программе воспроизведения WAV-файлов. Например, я изменил имя воспроизводимого файла на c:\dxsdk\samples\media\canyon.mid. Если у вас DirectX SDK располагается в другой папке, вам необходимо изменить путь к файлу и заново откомпилировать программу. Вы также можете указать имя файла и путь для любого вашего MIDI-файла. (Я не включил никаких MIDI-файлов в сопроводительные файлы к книге потому что у меня нет лицензионного программного обеспечения для их создания. Может быть в следующий раз.)



окне нет ничего особенного.





Согласен, в изображенном на Рисунок 7. 4 окне нет ничего особенного. Но для этого есть причина: программа создана для воспроизведения файлов MP3, а не для показа вращающихся трехмерных кубов!

Теперь загрузите проект DShow_PlayMP3, чтобы иметь возможность идти дальше. Я рекомендую вам скомпилировать и запустить программу, чтобы услышать результат ее работы. Если вы ничего не услышали, проверьте строку, в которой указано имя файла c:\dxsdk\samples\media\track.mp3. Если в указанном каталоге у вас нет MP3-файла, скорректируйте путь, чтобы он указывал на любой существующий в вашей системе файл MP3. Несколько пригодных для воспроизведения файлов входят в DirectX SDK.


Окно программы DSound_SoundSystem





Полагаю, что окно на Рисунок 7.11 нельзя сравнивать с экранами из игры Warcraft II, но тем не менее это настоящая программа! Фактически в программе нет никаких изображений для разглядывания. Но тем не менее, тут есть что послушать! Запустите программу и нажмите на левую кнопку мыши, а затем на правую. Вы услышите воспроизведение двух разных звуков. Более того, вы можете нажимать на кнопки мыши снова и снова и звук будет воспроизодиться несколько раз.



Окно программы TitleScreen





На Рисунок 6.8 показано окно, полученное в результате работы программы D3D_TitleScreen. Эта программа отображает стартовый экран воображаемой игры Battle Armor, который выглядит так, будто нарисован с использованием двухмерной графики. (Возможно однажды у меня появится время, чтобы действительно закончить работу над игрой Battle Armor, но до тех пор будем просто притворяться, что это реальная игра.) Итак, начнем! А сейчас загрузите проект в Visual C++, чтобы двигаться дальше.



показано созданное мной окно





На Рисунок 6. 13 показано созданное мной окно шириной 640 точек и высотой 480 точек. Обратите внимание, что высота заголовка равна 24 точкам. В результате высота видимой области визуализации будет составлять только 456 точек, что представляет проблему как для художника, так и для программиста. Поскольку все ваши вычисления для визуализации графического интерфейса пользователя базируются на размерах экрана, проблема должна быть решена. Функция GetClientRect() — ваш выход.

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

Новый размер окна по X =
(Желаемый размер по X) + ((Желаемый размер по X) - (Размер клиентской области по X))

Новый размер окна по Y =
(Желаемый размер по Y) + ((Желаемый размер по Y) - (Размер клиентской области по Y))

Для примера, изображенного на Рисунок 6.13 формулы работают следующим образом:

Новый размер окна по X = 640 + (640 - 640)

Новый размер окна по Y = 480 + (480 - 456)

Ширина окна остается равной 640 точкам, а высота становится равной 504 точкам. Теперь размеры области визуализации в окне достаточны для корректного отображения изображений размером 640 на 480 точек. На Рисунок 6.14 показано новое окно и его размеры.


Описание событий


Для начала скажу, что Empire Earth очень похожа на игру Age of Empires где вы создаете цивилизацию на протяжении нескольких эпох, таких как каменный век и средневековье. Главное отличие Empire Earth состоит в том, что вы можете вести вашу цивилизацию гораздо дальше во времени, чем в игре Age of Empires. Фактически, вы можете открыть вашей цивилизации дорогу в будущее. Это не только делает игру более захватывающей, но и вносит в нее изрядную долю сложности. Конечно, это хорошо для вас, поскольку в результате в игре появляется несколько целей.

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



Определение формата вершины


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

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



Определение класса звукового фрагмента


Я создал класс GameSound потому, что вам требуется только один объект исполнителя и один объект загрузчика, но несколько сегментов. Данный класс содержит реальные данные звукового сегмента для отдельного звукового фрагмента. Этот класс ни что иное, как простое хранилище звуковой информации. Вот как выглядит заголовок класса:

class GameSound { public: IDirectMusicSegment8 *m_pSound; IDirectMusicPerformance8 *m_pPerformance; ~GameSound(); GameSound(); };

Не беспокойтесь по поводу указателя m_pPerformance. Он всего лишь указывает на интерфейс исполнителя в классе звуковой системы. Реально используется только один член данных, m_pSound, в котором сразу после загрузки сохраняются звуковые данные.

Взаимосвязь двух классов показана на Рисунок 7.9.



Определение класса звуковой системы


Итак, вы познакомились со структурой класса; теперь пришло время посмотреть на реальный код. А вот и он во всей славе:

class SoundSystem { private: public: HWND m_hWnd; // Звуковая система IDirectMusicLoader8 *m_pLoader; IDirectMusicPerformance8 *m_pPerformance; // Функции SoundSystem(); ~SoundSystem(); HRESULT hrInitSoundSystem(void); HRESULT hrLoadSound(char *szname,GameSound *gs); HRESULT hrPlaySound(GameSound *gs); };

Код определения класса достаточно короток, но не слишком, если его сравнивать с самим классом. Ваше внимание может привлечь одна вещь — тип данных GameSound. Что же это такое?



Определение необходимых блоков


Теперь, когда вы знаете размер базового блока для вашей игры, можно перейти к фактическому созданию блоков. С чего же начать? Я начинаю строительство с создания фрагментов земной поверхности для игры. Например, в играх Age of Empires и Age of Wonders от Ensemble Studios, базовыми строительными блоками библиотеки работы с ландшафтом являются трава, почва или даже снег.

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



Определение ресурсов


После того, как цели определены, вы вычисляете способ их достижения. Первая цель — накормить население — весьма прямолинейна. Чтобы кормить людей игрок должен собирать или выращивать продовольствие. Итак, первый ресурс это пища. Пока все очень просто, правда?

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

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



Определение требований к интерфейсу


Первый аспект разработки интерфейса относится к выяснению того, что требуется от интерфейса. Вы должны спросить себя «Что должен делать интерфейс?». Это ключевой вопрос, потому что не ответив на него вы не сможете начать разработку. Конечно, вы можете начать программирование, оставив размышления об интерфейсе на потом, но я настоятельно не рекомендую так поступать. Итак, вопрос в том, как вам определить требования к интерфейсу?

Моя первая рекомендация — начните с чистого блокнота. Я использую блокноты в которых листы скреплены проволочной спиралью, поскольку они легко складываются и в сложенном виде их удобно использовать в качестве справочника во время написания программы. Вооружившись новым блокнотом выпишите в виде схемы различные интерфейсы, необходимые вашей игре. Схема для простой игры «Крестики-нолики» может выглядеть так:

Начальный экран

Заставка игры (bmp)

Кнопка пропуска заставки (mzone)

Главное меню

Кнопка начала новой игры (mzone)

Кнопка загрузки игры (mzone)

Кнопка записи игры (mzone)

Кнопка выхода из игры (mzone)

Интерфейс игры

Игровое поле (bmp)

Клетки игрового поля (mzones)

Изображение хода игрока (bmp)

Горячая точка для выхода из игры (mzone)

Загрузка игры

Список записанных игр (bmp)

Поле для ввода имени записаной игры (mzone)

Кнопка загрузки (mzone)

Кнопка возврата к главному меню (mzone)

Запись игры

Список записанных игр (bmp)

Поле для ввода имени записываемой игры (mzone)

Кнопка записи (mzone)

Кнопка возврата к главному меню (mzone)

Кнопка возврата к игре (mzone)

Завершение игры

Графика для завершения игры (bmp)

Ничего себе, могли ли вы предположить, что у игры «Крестики-нолики» будет такая сложная схема интерфейса? Интересно и то, что в действительности я пропустил некоторые элементы. Конечно, в большинстве игр «Крестики-нолики» не предусмотрена возможность записи и загрузки игр, но послушайте, это особенности производства.



Ошибки возникающие при использовании четырех угловых блоков





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



Основы блочной графики


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

Что такое блок? Зачем использовать блоки? Как создавать блоки? Как отображать блоки?

Остановка музыки


Следующий фрагмент кода подразумевая, что песня подошла к концу, останавливает музыку, перематывает ее к началу и заново начинает воспроизведение. Остановка музыки осуществляется вызовом функции IMediaControl::Stop(). Параметров у функции нет.

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



Отображение блоков


Мы уже там? Мы уже там? Мы уже там? ДА!!!! Мы уже там. Извините, мне просто вспомнилась последняя поездка на автомобиле, в которую я взял своих детей.

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

Отображение двухмерных блоков. Отображение двухмерных изометрических блоков. Отображение двухмерных изометрических блоков со спрайтами. Отображение трехмерных блоков.