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

       

Простое дерево технологий




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

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

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

Прототипы функций


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

void vDrawInterfaceObject(int iXPos, int iYPos, float fXSize, float fYSize, int iTexture); void vInitInterfaceObjects(void); LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); HRESULT InitD3D(HWND hWnd); void vRender(void); void vCleanup(void);

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



Проверка кода события


Пришло время использовать старый верный интерфейс IMediaEvent. Этот интерфейс позволяет увидеть состояние музыки. Я вызываю функцию IMediaEvent::WaitForCompletion() чтобы получить самый верхний код события.

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

Если код события равен EC_COMPLETE, я знаю, что воспроизведение песни закончилось. Это явная подсказка, что пора перемотать песню к началу и снова запустить ее воспроизведение. Если возвращен код EC_ERRORABORT или EC_USERABORT, я знаю, что что-то пошло наперекосяк и воспроизведение музыки прекращено.



Пустое пространство проекта для вашей новой программы




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

Source Files — исходные файлы; Header Files — заголовочные файлы; Resource Files — файлы ресурсов.

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

ПРИМЕЧАНИЕ Рабочее пространство — это второй уровень иерархии в среде разработки. В начале цепочки расположен проект, затем следует рабочее пространство. Файлы вашего проекта расположены в конце цепочки. В одном проекте может быть несколько рабочих пространств, точно также как в одном рабочем пространстве может быть несколько исходных файлов.

Рабочее пространство


Итак, вот ваш проект во всей славе! Теперь, после того как проект создан, настало время подробнее поговорить о нем. Взгляните на Рисунок 2.6.



Рабочее пространство с добавленным файлом





Я раскрыл папку Source Files, чтобы показать только что созданный файл CreateWindow.cpp. Когда вы дважды щелкаете по любому файлу, представленному в окне рабочего пространства, он загружается и выводится в области редактирования. На Рисунок 2.8 загружен файл CreateWindow.cpp. В данный момент он пуст и ждет, что вы добавите в него какой-нибудь текст.



Работа программы использующей класс звуковой системы





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





Работа управляемая событиями


Следующее отличие программирования в Windows заключается в том, что программы для Windows управляются событиями. Это значит, что программа, вместо того, чтобы бежать за информацией, может бездельничать и ждать сообщений, поступающих ей через очередь сообщений. Все сообщения получает и обрабатывает обработчик сообщений (message handler). Элементы, обрабатываемые в обработчике сообщений обычно называются событиями (events).



Ранние стратегии реального времени


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



Раскадровка интерфейса


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



Раскадровка интерфейса игры Battle Armor





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

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

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



Расположение текстур на титульном экране





На Рисунок 6.21 изображен экран обрамленный тонкой линией. Кроме того там изображены шесть текстур, расположенных в виде сетки. Эти шесть текстур полностью заполняют экран. Поскольку ширина и высота каждой текстуры равна 256 точкам, их общий размер превышает размеры экрана ширина которого равна 640 точкам, а высота — 480 точкам. Такие размеры текстур необходимы для того, чтобы они соответствовали требованиям оборудования, которое поддерживает только текстуры с размером, равным степени двойки. Большинство современных видеокарт могут работать только с текстурами размером 2 x 2, 4 x 4, 8 x 8, 16 x 16, 32 x 32, 64 x 64, 128 x 128, 256 x 256 и т.д. точек, поэтому и требуется данное действие. Лучший способ учесть эти требования — создать экран в требуемом разрешении, а затем разбить его на блоки размером 256 x 256 точек. Конечно, в результате увеличится объем использукмой памяти, но это можно проигнорировать.

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



Распространение


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

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



Расстановка целей


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

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

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

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



Различия между клиентской областью и пространством рабочего стола





На Рисунок 6.24 показано окно игры на рабочем столе системы. Ширина клиентской области окна равна 640 точкам, а высота — 480 точкам. Ширина рабочего стола сотавляет 1024 точки, а его высота — 768 точек. В клиентской области окна есть активная зона, и ее координаты в клиентском пространстве — (340, 10). Очень важно понимать, что класс активных зон хранит координаты в клиентском простанстве, а не в пространстве рабочего стола. Теперь представьте себе, что произойдет, если вы будете искать активную зону с координатами (340, 10), а окно будет передвинуто. В этом вам поможет Рисунок 6.25.



Различные типы технологий


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

Инфраструктура. Вооружение. Модернизация.

Разоблачение поддельных трехмерных деревьев





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



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


Приведенный ниже фрагмент кода содержит реализацию класса для блочной графики.

#include "TileClass.h" // Конструктор TileClass::TileClass() { // Инициализация внутренних переменных m_iNumLayers = 0; m_iValue = NULL; m_fRotX = NULL; m_fSize = NULL; } // Деструктор TileClass::~TileClass() { // Освобождаем буфер слоев, если он был выделен if(m_iValue) delete [] m_iValue; if(m_fRotX) delete [] m_fRotX; if(m_fSize) delete [] m_fSize; } // Установка количества слоев void TileClass::vSetNumLayers(int layers) { // Освобождаем ранее выделенные буферы слоев if(m_iValue) delete [] m_iValue; if(m_fRotX) delete [] m_fRotX; if(m_fSize) delete [] m_fSize; // Выделяем память для буфера слоев m_iValue = new int[layers]; memset(m_iValue, 0, layers * sizeof(int)); m_fRotX = new float[layers]; memset(m_fRotX, 0,layers * sizeof(int)); m_fSize = new float[layers]; memset(m_fSize, 0,layers * sizeof(int)); // Устанавливаем количество слоев m_iNumLayers = layers; } // Получение значения блока int TileClass::iGetValue(int layer) { // Проверяем правильность указанного номера слоя if(layer >= m_iNumLayers) { return(-1); } // Возвращаем значение return(m_iValue[layer]); } // Установка значения блока void TileClass::vSetValue(int value, int layer) { // Проверяем правильность указанного номера слоя if(layer >= m_iNumLayers) { return; } // Устанавливаем значение m_iValue[layer] = value; } // Установка угла поворота void TileClass::vSetRotation(float fRot, int layer) { // Проверяем правильность указанного номера слоя if(layer >= m_iNumLayers) { return; } m_fRotX[layer] = fRot; } // Установка размера блока void TileClass::vSetSize(float fSize, int layer) { // Проверяем правильность указанного номера слоя if(layer >= m_iNumLayers) { return; } m_fSize[layer] = fSize; } // Получение угла поворота float TileClass::fGetRot(int layer) { // Проверяем правильность указанного номера слоя if(layer >= m_iNumLayers) { return(-1.0f); } return(m_fRotX[layer]); } // Получение размера блока float TileClass::fGetSize(int layer) { // Проверяем правильность указанного номера слоя if(layer >= m_iNumLayers) { return(-1.0f); } return(m_fSize[layer]); }

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

Следующая функция — это деструктор класса. Она просто проверяет была ли выделена какая-нибудь область памяти, и, если да, то освобождает ее.

Затем следует код функции TileClass::vSetNumLayers(), устанавливающей количество слоев блока. Поскольку вы можете указать в качестве количества слоев любое осмысленное число, главная задача этой функции заключается в выделении необходимой для каждого слоя памяти. Сначала функция освобождает выделенную ранее память. Затем она выделяет память для переменных класса m_iValue, m_fRotX и m_fSize. Как только эта задача выполнена, выделенная память заполняется нулями с помощью вызова функции memset(). Помните, что эта функция должна быть вызвана перед первым использованием объекта блока. Если вы попытаетесь получить данные несуществующего слоя, класс вернет ошибку.

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

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

Следующие две функции, TileClass::vSetRotation() и TileClass::vSetSize, работают точно так же, как функция установки значения слоя, за исключением того, что они изменяют переменные класса m_fRotX и m_fSize.

Последние две функции, TileClass::fGetRot() и TileClass::fGetSize(), работают аналогично функции получения значения блока, за исключением того, что они возвращают значения членов данных класса m_fRotX и m_fSize.

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



Реализация класса звукового фрагмента


Класс звукового фрагмента является очень простым и содержит только конструктор и деструктор. Вот как выглядит реализация данного класса:

// Конструктор GameSound::GameSound() { m_pSound = NULL; m_pPerformance = NULL; } // Деструктор GameSound::~GameSound() { if(m_pSound) { if(m_pPerformance) m_pSound->Unload(m_pPerformance); } SAFE_RELEASE(m_pSound); }

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

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



Реализация класса звуковой системы


Обзор закончен, так как насчет нескольких фрагментов кода реализации класса, чтобы поддержать разговор? Ниже представлен код конструктора класса:

SoundSystem::SoundSystem() { m_pLoader = NULL; m_pPerformance = NULL; }

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



Реализация классов звуковой системы


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

Инициализация DirectSound Загрузка звуковых файлов Воспроизведение звуковых файлов

Редактирование и хранение блоков


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

Хранение блоков в двухмерном массиве. Хранение многослойных блоков. Реализация класса для блочной графики.

Редактор миссий


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

Большинство стратегических игр содержат от 20 до 30 встроенных миссий для однопользовательской игры. Этого достаточно, чтобы занять игрока, пока не будет выпущен дополнительный пакет миссий. Да, я знаю, что это может казаться неудовлетворительным решением, но игровые компании не могут существовать не получая прибыли. Вы должны всегда планировать свои игры расширяемыми, чтобы если они обретут популярность можно было гарантировать быстрый выпуск дополнительных миссий или продолжения. Достаточно взглянуть на серию игр Age of Empires от Ensemble, чтобы увидеть что могут сделать для вас продолжения. Другой замечательный пример — RollerCoaster Tycoon. Держу пари, что Крис Сойер, автор игры, никогда не воображал тот огромный успех, которым пользуется его захватывающая игра, посвященная строительству парка аттракционов. Если вы следите за этой серией, то знаете что были выпущены десятки дополнений.



Как вы теперь знаете, Utopia


Как вы теперь знаете, Utopia имеет весьма небольшой набор руководящих правил. Игровой процесс достаточно прямолинеен, поскольку ваша основная задача — произвести наибольшее количество золота. В конце игры побеждает тот игрок, у которого больше золота. Очень интересно, что в игре с таким небольшим количеством правил и параметров существует множество возможных стратегий.
Я описал эту игру так подробно, чтобы показать, что очень простая (по сегодняшним меркам) игра может быть и интересной и многогранной. Когда вы будете разрабатывать и программировать игры, помните, что простой набор базовых правил может сделать вашу игру более привлекательной для игрока. Нет необходимости в очень сложных игровых системах. Главное — чтобы игрок получал удовольствие.

Рука NOD в действии ©2002 Electronic Arts All Rights Reserved





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

Воздушные силы GDI действительно великолепны. Летяшие в строю атакующие геликоптеры практически неуничтожимы. Одна из используемых мною в игре стратегий состояла в создании флота геликоптеров Orca и отправке этого роя для уничтожения врага. Если вы построите достаточное количество геликоптеров, они сокрушат систему ПВО. Геликоптер Orca изображен на Рисунок 1.12.



Рыба погода и пираты


Игрок имеет значительный контроль над миром, но есть вещи неподвластные ему. Косяки рыбы и пираты беспорядочно бороздят океан. Рыба полезна для получения золота, а пираты могут потопить ваш рыболовный флот.




Сайты аукционов


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

Большинство записывающих приводов CD-ROM в наши дни очень дешевы, настолько же дешевы чистые диски. Общая стоимость упаковки вашей игры не должна превысить двух долларов. Как видите, прибыль весьма значительна; только убедитесь, что вы продемонстрировали игру широкой общественности до того, как будете пытаться продать ее. Я так же не беспокоился бы о том, чтобы заранее упаковывать много копий. Делайте это по мере поступления заказов.

Вы можете даже пробовать продавать разрешения на загрузку вашей игры. Некоторые клиенты не согласятся на такой вариант, но если у вас есть подобная возможность, это более простой способ, чем продажа компакт-дисков. Одно из мест, где вам предоставят такую возможность — www.digibuy.com.



Сегменты DirectMusic


Сегменты в DirectMusic представляют собой реально воспроизводимые звуковые данные. Любой файл WAV или последовательность MIDI, которые вы воспроизводите должны быть сперва загружены в сегмент. В DirectMusic существует два типа сегментов: первичные и вторичные. Первичный сегмент является главной звуковой дорожкой. Вторичные сегменты обычно используются для спецэффектов.

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

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

Метод Описание
AddNotificationType Добавляет тип события.
Clone Копирует сегмент.
Compose Составляет дорожку.
Download Копирует данные в объект исполнителя.
GetAudioPathConfig Возвращает конфигурацию аудио-пути.
GetDefaultResolution Возвращает разрешение времени для сегмента.
GetGraph Возвращает инструментальный граф.
GetLength Возвращает длину сегмента.
GetLoopPoints Возвращает точки начала и конца цикла.
GetParam Возвращает параметры дорожки.
GetRepeats Возвращает количество цикличских повторений сегмента.
GetStartPoint Возвращает начальную точку.
GetTrack Возвращает дорожку, соответствующую заданным условиям поиска.
GetTrackGroup Возвращает группу битов дорожки.
InitPlay Инициализирует состояние воспроизведения.
InsertTrack Вставляет дорожку.
RemoveNotificationType Удаляет тип события.
RemoveTrack Удаляет дорожку.
SetDefaultResolution Устанавливает разрешение по умолчанию.
SetGraph Устанавливает инструментальный граф.
SetLength Устанавливает длину.
SetLoopPoints Устанавливает начальную и конечную точки цикла.
SetParam Устанавливает параметры дорожки.
SetPChannelsUsed Устанавливает используемый канал исполнителя.
SetRepeats Устанавливает количество циклических повторов сегмента.
SetStartPoint Устанавливает начальную точку.
SetTrackConfig Конфигурирует дорожку.
Unload Удаляет данные из объекта исполнителя.

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



Сглаживание





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

ПРИМЕЧАНИЕ Запомните, что множественная выборка корректно работает только в том случае, если значение параметра, определяющего режим работы цепочки переключения экранных буферов равно D3DSWAPEFFECT_DISCARD.

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

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

Таблица 6.2. Режимы переключения экранных буферов

Значение Описание
D3DSWAPEFFECT_DISCARD Разрешает драйверу видеокарты самому выбрать наиболее эффективный метод переключения. Этот метод обычно быстрее, чем другие. Единственная проблема этого метода заключается в том, что он негарантирует сохранность вторичного буфера и его содержимого. Так что при использовании этого метода вы не можете полагаться на неизменность содержимого вторичного буфера. Вы должны использовать этот метод, если значение параметра, определяющего тип множественной выборки, отличается от D3DMULTISAMPLE_NONE.
D3DSWAPEFFECT_FLIP Использует циклическую схему экранных буферов. Буферы перемещаются по кругу в замкнутой кольцевой очереди. Метод позволяет добиться гладкой визуализации, если значение параметра, определяющего интервал переключения не равно D3DPRESENT_INTERVAL_IMMEDIATE.
D3DSWAPEFFECT_COPY Этот метод может использоваться только в том случае, если у вас есть один вторичный буфер. Кроме того, этот метод гарантирует неизменность изображения во вторичном буфере. Недостатком метода является то, что в оконном режиме он может вызвать нарущения целостности графики. Это вызвано тем, что изображение выводится во время обратного хода кадровой развертки монитора. Не используйте этот метод, если ваша программа работает в оконном режиме и вам необходима гладкая графика.
D3DSWAPEFFECT_FORCE_DWORD Не используется

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

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

Затем расположен флаг EnableAutoDepthStencil. Он сообщает системе визуализации будет ли она управлять буфером глубины или нет. Если вы присвоите данному параметру значение TRUE, Direct3D будет управлять буфером глубины за вашу программу.

Следующий член структуры, названный AutoDepthStencilFormat, устанавливает используемый формат буфера глубины. Он используется только в том случае, если значение параметра EnableAutoDepthStencil равно TRUE. Если вы используете автоматическую установку буфера глубины, убедитесь, что в этом параметре вы указали корректный формат.

Следующий член данных в списке носит неопределенное имя Flags. Вы не слишком любите такие прямолинейные имена как это? Значения для этого параметра перечислены в таблице 6.3.

Таблица 6.3. Флаги режима отображения

Значение Описание
D3DPRESENTFLAG_LOCKABLE_BACKBUFFER Разрешает приложению блокировать вторичный буфер.
D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL Разрешает системе освобождать z-буфер после каждого отображения данных буфера. Это может увеличить быстродействие, если драйвер видеокарты поддерживает данную возможность.
D3DPRESENTFLAG_DEVICECLIP Область визуализации в оконном режиме будет обрезаться. Работает только в Windows 2000 и Windows XP.
D3DPRESENTFLAG_FORCEGDIBLT Для копирования изображений будет использоваться GDI. Работает только в Windows 2000 и Windows XP.
D3DPRESENTFLAG_VIDEO Сообщает драйверу видеокарты, что вторичный буфер содержит видеоданные.

Следующий член данных называется FullScreen_RefreshRateInHz. Его назначение достаточно очевидно: он задает частоту с которой видеокарта будет обновлять изображение на экране монитора. Для визуализации в оконном режиме здесь следует указать значение D3DPRESENT_RATE_DEFAULT; в этом случае будет установлена частота кадров по умолчанию данной видеокарты.

Ах, вот и последний элемент параметров отображения! Он называется PresentationInterval. Эта переменная устанавливает максимальную частоту, с которой будет отображаться вторичный буфер. Оконные приложения требуют, чтобы значение этого параметра было D3DPRESENT_INTERVAL_IMMEDIATE. Для полноэкранной визуализации можно использовать значение D3DPRESENT_INTERVAL_DEFAULT или одно из значений, перечисленных в таблице 6.4.

Таблица 6.4. Интервалы отображения

Значение Описание
D3DPRESENT_INTERVAL_DEFAULT Система сама выбирает частоту смены кадров. В оконном режиме используется частота по умолчанию.
D3DPRESENT_INTERVAL_ONE Перед отображением графики система ждет один период кадровой развертки.
D3DPRESENT_INTERVAL_TWO Перед отображением графики система ждет два периода кадровой развертки.
D3DPRESENT_INTERVAL_THREE Перед отображением графики система ждет три периода кадровой развертки.
D3DPRESENT_INTERVAL_FOUR Перед отображением графики система ждет четыре периода кадровой развертки.
D3DPRESENT_INTERVAL_IMMEDIATE Система не ожидает завершения кадровой развертки перед выводом изображения. Этот метод может вызвать несогласованность графики.


Схема кадра меню многопользовательской схватки





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



Схема кадра меню режима "схватка"





Схема, соответствующая Рисунок 6.4, выглядит так:

3. Меню схватки

A. Одиночная игра

Графика

Активная зона

(7. Меню однопользовательской схватки)

Звук

(H. Щелчок по кнопке)

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

Графика

Активная зона

(8. Меню многопользовательской схватки)

Звук

(H. Щелчок по кнопке)

C. Возврат к основному меню

Графика

Активная зона

(2. Основное меню)

Звук

(H. Щелчок по кнопке)

D. Нижняя полоса

Графика

E. Верхняя полоса

Графика

F. Кнопка выхода

Графика

Активная зона

(0. Рабочий стол)

G. Музыкальное сопровождение

Звуковой файл

H. Щелчок по кнопке

Звуковой файл

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



Схема кадра с меню игрового сервера





Интерфейс на Рисунок 6.6 более сложен, чем те, которые мы изучали до сих пор. Вот схема этого маленького шедевра:

9. Экран сервера многопользовательской схватки

A. Флажок готовности

Графика

(unchecked_box.bmp)

Активная зона

Графика

(checked_box.bmp)

Звук

(O. Щелчок по кнопке)

B. Имя игрока

Текстовое поле

Ширина: 16 символов

Переменная состояния

m_szPlayerName[]

С. Цвета игрока

Графика

Переменная состояния

m_iPlayerColor[]

(color0.bmp – color9.bmp)

Активная зона

Звук

(O. Щелчок по кнопке)

D. Команда игрока

Графика

Переменная состояния

m_iPlayerTeam[]

(team0.bmp – team5.bmp)

Активная зона

Звук

(O. Щелчок по кнопке)

E. IP-адрес игрока

Текстовое поле

Ширина: 16 символов

Переменная состояния

m_szPlayerIP[]

F. Верхняя полоса

Графика

(top_bar.bmp)

G. Окно переговоров

Текстовое поле

Ширина: 24 символа

Переменная состояния

m_szChatBuffer[]

Н. Поле ввода реплик

Текстовое поле ввода

Ширина: 24 символа

Переменная состояния

m_szChatSendBuffer[]

I. Кнопка выхода

Графика

(exitbutton.bmp)

Активная зона

(0. Рабочий стол)

J. Карта игры

Графика

Переменная состояния

m_iGameMapID

(gamemap_0.bmp – gamemap9.bmp)

К. Кнопка выбора карты

Графика

(choosmapbutton.bmp)

Активная зона

(10. Меню выбора карты)

Звук

(O. Щелчок по кнопке)

L. Кнопка начала игры

Графика

(startbutton.bmp)

Активная зона

(11. Игровой экран многопользовательской схватки)

Звук

(O. Щелчок по кнопке)

M. Музыкальное сопровождение

Звуковой файл

N. Нижняя полоса

Графика

(bottom_bar.bmp)

O. Щелчок по кнопке

Звуковой файл

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

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

Следующий элемент называется «Цвет игрока» и указанные для него тип и свойства уже знакомы вам. Единственное отличие — имена графических файлов. Я указал диапазон имен, чтобы отметить, что для отображения данного элемента используются несколько графических изображений. С графическим элементом связана также переменная состояния. Это сообщает вам, что выводимое графическое изображение должно соответствовать значению переменной состояния.

Перейдем ниже, к элементу с меткой «H», чтобы познакомиться с новым типом элемента — текстовым полем ввода. Этот тип указывает, что игрок может вводить текст в данное поле. Переменная состояния, указанная в свойствах элемента, будет хранить введенный текст.

Типы остальных элементов мы уже рассмотрели ранее, поэтому здесь мы их пропустим.



Схема кадра с основным меню





На Рисунок 6.3 показан кадр с основным меню. В нем нет ничего сложного — всего лишь несколько графических изображений и активных зон. Но подождите! Взгляните на изображение динамика рядом с элементом «A». Да ведь это напоминает звук! Перед тем, как подробнее разобраться с этим вопросом, взгляните на схему меню:

2. Основное меню

A. Схватка

Графика

Активная зона

(3. Меню схватки)

Звук

(I. Щелчок по кнопке)

B. Кампания

Графика

Активная зона

(4. Меню кампании)

Звук

(I. Щелчок по кнопке)

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

Графика

Активная зона

(5. Меню загрузки игры)

Звук

(I. Щелчок по кнопке)

D. Настройка

Графика

Активная зона

(6. Меню настройки)

Звук

(I. Щелчок по кнопке)

E. Нижняя полоса

Графика

F. Верхняя полоса

Графика

G. Кнопка выхода

Графика

Активная зона

(0. Рабочий стол)

H. Музыкальное сопровождение

Звуковой файл

I. Щелчок по кнопке

Звуковой файл

Ничего себе, какой сложной вещью оказалось так просто выглядящее меню. Первым в списке элементов стоит пункт меню «Схватка». Так как этот элемент является пунктом меню, с ним связаны графическиое изображение и активная зона. Главным отличием этого элемента является добавленный к нему элемент «Звук». Этот новый элемент сообщает вам, что при щелчке по активной зоне, связанной с пунктом меню «Схватка» должен воспроизводиться звук. Элемент, расположенный под пунктом «Звук», сообщает, какой именно звук должен быть воспроизведен. В данном случае будет воспроизведен звук, который перечислен в списке элементов под меткой «I». Элемент с меткой «I» называется «Щелчок по кнопке» и в нем указан тип воспроизводимого звука. Если желаете, вы можете указать здесь имя реального файла WAV или MP3.



Схема кадра стартового экрана игры Battle Armor





На Рисунок 6.2 каждому элементу стартового экрана присвоена метка. Всего отмечено шесть различных эелементов. Они перечислены в алфавитном порядке, но их порядок не имеет значения. Я назначил им метки произвльным образом. Теперь, глядя на кадр интерфейса, давайте разберем его схему:

1. Стартовый экран

A. Заставка

Графика

Активная зона

(2. Основное меню)

B. Индикатор загрузки

Текст

C. Нижняя полоса

Графика

D. Верхняя полоса

Графика

E. Кнопка выхода

Графика

Активная зона

(0. Рабочий стол)

F. Музыкальное сопровождение

Звуковой файл

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

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

Далее располагается элемент с меткой «A» и названием «Заставка». Ниже перечислены различные свойства этого элемента. Первое из перечисленых свойств — «Графика». Оно сообщает вам, что данный элемент содержит графическое изображение. В данном случае изображением является картинка с названием игры. Следующее свойство — «Активная зона». Это значит, что данный элемент также является горячей точкой и выполняет какие-то действия, когда пользователь щелкает по нему мышью. Чтобы узнать, какие действия будут выполнены, взгляните на следующую строчку. Там расположена запись «(2. Основное меню)». Она означает, что при щелчке мышью по данному элементу пользователь переходит ко второму кадру интерфейса, который называется «Основное меню». Знаете, что? Вы только что создали взаимосвязь между стартовым экраном и основным меню! Полагаю это было для вас неожиданно, но здесь действительно нет ничего сложного.

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

Следующие два элемента с метками «C» и «D» являются графическими элементами, образующими верхнюю и нижнюю полосы интерфейса. В них нет ничего особенного.

Элемент с меткой «E» более интересен, так как содержит и графику и активную зону. Возможно, вам интересно, где расположен элемент «(0. Рабочий стол)». Это специальная метка, означающая рабочий стол Windows. Другими словами, при щелчке пользователя по данному элементу программа завершает свою работу и игрок возвращается к рабочему столу системы. Итак, вы сделали это — определили еще одну важную взаимосвязь интерфейса.

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



Щелчки мышью и взаимодействие с интерфейсом


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

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

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



Широкомасштабные многопользовательские игры


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

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



Сюжет


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



Сюжетная основа


Любой хороший сюжет имеет основу, и сюжет игры не является исключением. Сюжетная основа придает игровому сценарию глубину и смысл. Игрок сильнее стремится к победе, когда понимает причину этого. Например, взглянем на игру Command & Conquer от Westwood. В ней NOD сражается с GDI за контроль над полями тибериума. Если бы этой основы не существовало, игроки могли бы задаваться вопросом: почему NOD и GDI сражаются друг с другом.

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



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




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

СОВЕТ Не следует использовать в вашей игре блоки странных размеров. Блоками с размерами 1,0 или 10,0 единиц гораздо легче управлять, чем блоками с размером 3,5 единиц. Это становится особенно ясно при вычислении скорости передвижения и пройденного пути.

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


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

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



Скорость добычи

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

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

Скорость оружия


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



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


Скорость передвижения подразделений очень важна для выбора игроком стратегии использования войск в игре. Обычно подразделения, оборудованные тяжелыми вооружениями, передвигаются медленнее, чем легковооруженные. Это имеет смысл, поскольку платой за более мощное вооружение является увеличение веса. Хотя бывают и исключения. Возьмите для примера 105-мм пушку и винтовку M-16. Пушка весит значительно больше, чем винтовка, но танк, оборудованный 105-мм пушкой передвигается быстрее, чем пехотинец с винтовкой M-16.

Поскольку в наши дни для визуализации подразделений все чаще используется трехмерная графика, для указания скорости перемещения лучше всего использовать единицы используемой трехмерной системы координат. Для примера установим, что размер блока игрового поля равен единице. В этом случае медленно движущиеся подразделения будут перемещаться за такт игры на 1/10 размера блока, а быстро перемещающиеся подразделения могут за то же время преодолевать 8/10 блока. Эта концепция проиллюстрирована на Рисунок 8.4.



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


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



Сложность навигации по меню


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

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



Смещение


Сперва свойство смещения может показаться очень странным, но оно весьма полезно при визуализации. Если вы помните, раньше я упоминал о выборе размера блока для вашей игры. Достаточно часто используются размеры 32 x 32, 64 x 64 и даже 128 x 128. Но что, если вам потребуется несколько блоков, которые не помещаются целиком в заданный размер? Примером могут служить деревья. Они обычно высоки и не очень широки. Если вы попытаетесь втиснуть дерево в изометрический блок, размером 32 x 32, то столкнетесь с проблемами. Лучший способ обхода проблемы состоит в использовании блоков неодинаковой формы.

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



Вы можете задаться вопросом, какие


Вы можете задаться вопросом, какие виды событий обрабатываются. Существуют сотни, если не тысячи возможных событий, и некоторые из них описаны в таблице 2.1.
Таблица 2.1. События Windows
Событие Описание
WM_KEYDOWN Это событие генерируется каждый раз, когда нажимается клавиша на клавиатуре. Ввод с клавиатуры жизненно необходим для большинства игр, поэтому данное сообщение очень важно.
WM_KEYUP Это событие генерируется когда нажатая клавиша будет отпущена. Вам необходимо знать не только когда клавишу нажали, но и когда ее отпустили. Поэтому данное событие также важно.
WM_LBUTTONDOWN Это событие генерируется когда пользователь нажимает левую кнопку мыши, если указатель мыши находится в пределах окна.
WM_LBUTTONUP Это событие генерируется когда пользователь отпускает левую кнопку мыши, если указатель мыши находится в пределах окна.
WM_SETFOCUS Это событие генерируется когда приложение получает фокус клавиатуры. Например, событие WM_SETFOCUS генерируется, когда вы щелкаете мышью по неактивному окну.
WM_SIZE Это событие сообщает окну, что его размеры были изменены. Оно важно в ситуациях, когда для подгонки интерфейса к новому размеру окна требуется изменение расположения элементов.
СОВЕТ Существует множество других событий. Я советую вам воспользоваться встроенной справкой Visual C++ и поискать описание всех сообщений, названия которых начинаются с WM_.

События аудиовизуального потока


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

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

Метод Описание
CancelDefaultHandling Отменяет установленную по умолчанию обработку события фильтром.
FreeEventParams Освобождает связанные с параметром ресурсы.
GetEvent Возвращает следующее событие из очереди.
GetEventHandle Возвращает дескриптор следующего сообщения в очереди.
RestoreDefaultHandling Восстанавливает обработчик по умолчанию.
WaitForCompletion Ожидает пока граф фильтров не завершит воспроизведение аудиовизуального потока. Я использую эту функцию в примере программы чтобы проверить завершено ли воспроизведение музыки.


Согласованность интерфейса


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

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

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



Соседние блоки с присвоенными номерами





Помечены два блока, поэтому вы берете назначенные им числа и складываете их вместе. В результате получается 1 + 2 = 3. Проконсультировавшись со справочной таблицей, вы увидите что блок с номером 3 — это угловой блок с изображением дороги. Не волнуйтесь о разгадывании справочной таблицы — она представлена на Рисунок 5.28. Счастливого Рождества!



Состояния интерфейса


Ради примера, представьте, что игрок выбрал в главном меню пункт «Схватка». Согласно схеме, в результате этого действия игрок переходит к меню с номером 3. Кадр с этим меню изображен на Рисунок 6.4.



Совместное использование угловых блоков с другими переходными блоками





Видите как угловые блоки работают вместе с другими переходными блоками при создании приятно выглядящих больших квадратных и прямоугольных областей на карте? Угловые блоки обеспечивают хорошо выглядящие скругленные углы. Теперь подумайте, что случится если область которую вы сформируете не будет квадратной или прямоугольной? Если вы предположили, что карте не хватает некоторых ключевых блоков — получите 100 очков. (Я не знаю что это за 100 очков, но полагаю, что это счет игры!) Рисунок 5.23 иллюстрирует недостатки созданных к данному моменту блоков.



Создание буфера вершин


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

HRESULT CreateVertexBuffer( UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE* pHandle );

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

Второй параметр, Usage, содержит одну или несколько констант типа D3DUSAGE. Доступные значения перечислены в таблице 6.7.

Таблица 6.7. Значения D3DUSAGE

Значение Описание
D3DUSAGE_DYNAMIC Буфер вершин требует динамического использования памяти. Это позволяет драйверу видеокарты управлять буфером для оптимизации скорости визуализации. Если данный флаг отсутствует, буден создан статический буфер. Совместно с этим флагом нельзя использовать флаг параметров пула D3DPOOL_MANAGED
D3DUSAGE_AUTOGENMIPMAP Для буфера автоматически генерируются mip-текстуры. Этот метод требует больше памяти, но благодаря его использованию достигается более высокое качество визуализации
D3DUSAGE_DEPTHSTENCIL Буфер является буфером глубины или буфером трафарета. Совместно с этим флагом можно использовать только флаг параметров пула D3DPOOL_DEFAULT
D3DUSAGE_RENDERTARGET Буфер является целевым буфером визуализации. Совместно с этим флагом должен использоваться флаг параметров пула D3DPOOL_DEFAULT

В рассматриваемой программе я присваиваю параметру значение NULL. В результате будет создан статический буфер вершин.

Третий параметр, FVF, содержит комбинацию флагов D3DFVF. Он сообщает системе, какая информация будет содержаться в данных каждой вершины. Вы можете предпочесть не использовать формат FVF для буфера вершин. В этом случае присвойте данному параметру значение NULL. За дополнительной информацией о флагах D3DFVF обращайтесь к документации DirectX SDK.

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

Таблица 6.8. Значения D3DPOOL

Значение Описание
D3DPOOL_DEFAULT Разрешает системе выделять память в наиболее подходящем пуле данных. Данный метод требует освобождения выделенных ресурсов перед сбросом устройства Direct3D
D3DPOOL_MANAGED Разрешает системе в случае необходимости копировать ресурсы из системной памяти в память устройства. Это позволяет выполнять сброс устройства без предварительного принудительного освобождения управляемой памяти
D3DPOOL_SYSTEMMEM Требует, чтобы система хранила ресурсы вне памяти устройства. Это не самый эффективный способ для тех систем, где есть аппаратные ускорители трехмерной графики. Созданные ресурсы не требуется освобождать перед сбросом устройства
D3DPOOL_SCRATCH Ресурсы данного типа недоступны для устройства Direct3D. Тем не менее их можно создавать, блокировать и копировать
D3DPOOL_FORCE_DWORD Не используется

В программе для настройки пула памяти я использую значение D3DPOOL_DEFAULT. В данной ситуации это простейший способ действий.

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

Последний параметр, pHandle, в данное время не используется, так что не стоит о нем беспокоиться. Разве это не здорово?



Создание интерфейса исполнителя


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

if(FAILED(hResult = CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**) &g_pPerformance))) { return(0); }

И снова для создания интерфейса я применяю функцию CoCreateInstance(). Интерфейс IDirectPerformance8 использует CLSID CLSID_DirectMusicPerformance и идентификатор интерфейса IID_DirectMusicPerformance8. Указатель на интерфейс я сохраняю в глобальной переменной g_pPerformance.



Создание интерфейса загрузчика


Следующий фрагмент кода создает интерфейс загрузчика. Как вы, возможно, помните, интерфейс загрузчика отвечает за загрузку звуковых данных, таких как файлы WAV и файлы MIDI. Вот как выглядит соответствующий код:

if(FAILED(hResult = CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**) &g_pLoader))) { return(0); }

Для получения интерфейса загрузчика я вызываю функцию CoCreateInstance(). Интерфейс IDirectMusicLoader8 использует CLSID CLSID_DirectMusicLoader и идентификатор интерфейса IID_IDirectMusicLoader8. Указатель на интерфейс сохраняется в глобальной переменной g_pLoader.

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