Глава 9 Спрайты
Трехмерные объекты в
интерактивных приложениях (например, играх)
помогают воссоздать на экране реалистичных
персонажей и окружение. Тем не менее
необходимость обсчета тысяч граней при любом
изменении макета заметно снижает
производительность. В этой главе мы увидим, как
можно пользоваться плоскими фигурами для
создания персонажей в интерактивной трехмерной
игре. Разумеется, подобная методика не
ограничивается только сферой развлечений,
однако именно игры требуют от приложения
наивысшей производительности. Приложение-пример
для этой главы находится в каталоге Sprites.
Плоские растровые
изображения неправильной формы, с которыми нам
придется иметь дело, в различных источниках
именуются по-разному. Например, в документации к
DirectX 2 SDK они называются декадами, но я буду
называть их спрайтами. Спрайт можно представить
себе в виде фигурки, вырезанной из картона и
установленной в определенной точке макета,
наподобие рис. 9-1.
Рис. 9-1. Картонные фигурки в
макете
Ради правдоподобия
фигурки на рис. 9-1 стоят на подставках. Любой
ребенок скажет вам, что без подставок фигурка
немедленно упадет. У наших спрайтов нет никаких
подставок, но они будут стоять вполне нормально.
Давайте прикинем, как
будет выглядеть макет с картонными фигурками на
рис. 9-1 для зрителя. Если наблюдатель постоянно
находится перед макетом, то персонажи будут
смотреться вполне нормально, причем дальние
фигурки будут казаться меньше ближних. Возможный
вид макета изображен на рис. 9-2.
217
Рис. 9-2. Вид спереди для
макета, изображенного на рис. 9-1
Теперь подумайте, что
произойдет, если наблюдатель посмотрит на макет
сбоку. Картонные фигурки не имеют толщины, и
поэтому для наблюдателя они исчезнут. А если бы
каждая фигурка могла вращаться, чтобы она всегда
оказывалась обращена к наблюдателю лицевой
стороной? Наблюдатель двигался бы куда угодно и
при этом все равно видел фигурку.
Однако в таком случае
изображение всегда будет оставаться одним и тем
же, что не очень правдоподобно. Обычно сзади люди
выглядят несколько иначе, чем спереди. Допустим,
человек собирается напасть на вас. Он не будет
спокойно следить за тем, как вы подходите к нему
со спины, и развернется к вам лицом.
Разворачивая фигурки к
наблюдателю, мы сможем имитировать таких
картонных агрессоров. К сожалению,
среднестатистический бандит выглядит не так
статично, как картонная фигурка. Следовательно,
нам придется не только следить за ориентацией
фигурки, но и изменять ее внешний вид, чтобы она
казалась подвижной.
Если мы создадим несколько
вариантов изображения для персонажа, поместим
один из них в макет так, чтобы он был постоянно
обращен лицевой стороной к наблюдателю, и при
необходимости будем изменять вид изображения, то
у нас появится вполне приемлемая альтернатива
построению сложных трехмерных фигур из
отдельных граней.
Реализация спрайтов
Спрайты, подобно
текстурам, можно размещать в любом месте и
масштабировать до любых размеров. Текстуры
накладываются на грани любой формы; если бы можно
было создать текстуру неправильной формы, у нас
почти получился бы
218
Глава 9. Спрайты
спрайт. Однако на практике
оказывается, что форма текстуры вовсе не
обязательно должна быть неправильной. Сама
текстура может быть и прямоугольной, если при
этом сделать некоторые ее участки прозрачными.
Задавая прозрачный цвет для текстуры, фактически
мы создаем изображения произвольной формы.
До настоящего момента мы
не рассматривали прозрачных изображений, потому
что большинство трехмерных объектов
представляют собой твердые тела, и при их
раскрашивании от прозрачности не будет никакого
прока. Прозрачность реализуется достаточно
просто - один из цветов назначается
<прозрачным>, и во время воспроизведения все
пиксели данного- цвета просто не копируются из
источника (обычно текстурного покрытия) в
приемник (буфер воспроизведения).
Текстуры не обладают лишь
одним атрибутом, необходимым для создания
спрайтов - определенным положением в макете. Эту
проблему можно решить, присоединяя текстуру к
фрейму. Фрейм предоставляет информацию о
положении, а текстура - визуальный компонент.
Механизм визуализации
особым образом работает с текстурами,
присоединенными к фреймам. Они всегда
воспроизводятся в плоскости ракурса - и потому
они гарантированно обращены лицевой стороной к
наблюдателю. Кроме того, текстуру можно
масштабировать в зависимости от ее удаленности
по оси г. Положение текстуры выбирается таким
образом, чтобы базовая точка текстуры
(выбираемая произвольно) находилась в
определенной точке фрейма. По умолчанию базовая
точка растрового изображения расположена в
левом верхнем углу, поэтому при позиционировании
текстуры левый верхний угол должен находиться в
точке фрейма с координатами х, у, z. Как мы увидим
позднее, базовую точку можно переместить -
например, в середину нижнего края изображения, и
такое ее положение будет более логичным при
размещении персонажей в макете. На рис. 9-3
изображены: стандартное положение базовой точки
(слева) и более логичный вариант (справа).
Рис. 9-3. Различные положения
базовой точки при размещении спрайта
Реализация спрайтов
219
По умолчанию прозрачным
цветом считается черный. На мой взгляд это
неудобно, поскольку нарисовать растровое
изображение на черном фоне довольно сложно.
Интерфейс текстур содержит функцию
SetDecalTransparentColor, которая назначает другой
прозрачный цвет. На момент написания книги мне
так и не удалось заставить эту функцию работать,
так что пришлось в своих картинках использовать
черный цвет в качестве прозрачного. Впрочем, нам
нужен хотя бы один работающий прозрачный цвет, и
он у нас имеется.
Работа с несколькими
изображениями
Некоторые спрайты состоят
всего из одного изображения. Например, для кубка
одного изображения будет вполне достаточно -
форма спрайта остается постоянной, так как кубок
с любой стороны выглядит одинаково. Тем не менее
для большинства спрайтов все же требуется
несколько изображений, и мы должны научиться
создавать по ним объект-спрайт. Я называю такие
изображения-компоненты фазами, поскольку с
изменением фазы изображение движется - это
похоже на чередование фаз Луны.
Тривиальное решение
Самый очевидный способ
создания спрайта с несколькими изображениями
заключается в том, чтобы создать для каждой фазы
отдельное изображение в отдельном растровом
файле и загружать их в массив текстур, как
показано на рис. 9-4.
Рис. 9-4. Использование
нескольких отдельных изображений для создания
спрайта
Такая методика достаточно
проста и надежна. Спрайт-объект следит за тем,
какое изображение в данный момент времени
присоединено к фрейму в качестве визуального
элемента. Когда потребуется сменить изображение
в макете, мы уда-
220
Глава 9. Спрайты
ляем из фрейма текущий
визуальный элемент и присоединяем вместо него
новый. Поскольку все эти операции производятся с
указателями, они работают очень быстро. Сначала
моя версия класса C3dSprite была основана именно на
этом принципе, поскольку я не мог прочесть полную
документацию по механизму визуализации и
создать что-нибудь получше - поэтому я точно знаю,
что данный метод работает.
Тем не менее есть два
важных довода против использования отдельных
изображений. Во-первых, на хранение структур
данных с большими изображениями и текстурами
расходуется много памяти. Во-вторых,' поддержка
общей палитры для всех изображений достаточно
затруднительна, поскольку файлы никак не связаны
друг с другом и могут редактироваться по
отдельности.
Лучшее решение
Я предпочитаю создавать
спрайты по одному растровому изображению, в
котором хранятся все возможные состояния
спрайта. Работа с одним растром во многих случаях
оказывается более удобной, поскольку при этом
очень мало избыточных данных. Например, на рис. 9-5
показано растровое изображение с различными
фазами спрайта бегущей собаки, использованного в
одном из приложений книги (Microsoft Press, 1995).
Рис. 9-5. Полоска из
нескольких изображений, используемых при
создании спрайта с несколькими фазами,
объединенных в одном растре
Работа с несколькими
изображениями '''lit 221
Все кадры в такой полоске
имеют одинаковый размер, и в них используется
одинаковая палитра. При этом вам будет труднее
пропустить кадр или перепутать их порядок и
легче выдержать постоянство цветов в различных
фазах спрайта.
Полоска может иметь и
другие размеры - скажем, семь собак в ширину и
одну собаку в высоту, или же может состоять из
рядов по четыре собаки - как вы считаете нужным.
Раз вы знаете точное положение каждого кадра в
таком изображении, то сможете написать код для
извлечения любого из них. Вертикальное
расположение кадров немного упрощает программу.
Поскольку для вас, вероятно, не имеет особого
значения, как именно расположены кадры спрайта, я
бы предложил выбрать тот вариант, который
облегчает вашу работу.
Создание текстуры по полоске
изображений
Следующий вопрос: как же
создать текстуру по полоске изображений?
Для того чтобы создать
текстуру, необходимо присоединить изображение к
структуре данных, которая описывает работу с
изображением при воспроизведении текстуры.
Изображение состоит из заголовка и собственно
графических данных. Связь между ними изображена
на рис. 9-6.
Рис. 9-6. Строение текстуры
Сначала я решил, что самый
естественный способ сменить фазу спрайта -
просто обновить значение указателя в заголовке,
чтобы он ссылался на другой набор графических
данных. К сожалению, на момент написания книги
механизм визуализации требовал, чтобы адрес
графических данных оставался неизменным.
Следовательно, вместо того чтобы изменять
указатель, нам придется обновлять данные. На
практике это означает, что в вашей полоске
изображений должен присутствовать один пустой
кадр, используемый в качестве рабочей области. В
этом кадре будет создаваться исходная текстура.
Если нам понадобится изменить вид спрайта, мы
просто копируем нужные графические данные в
рабочую область и сообщаем механизму
визуализации об изменении текстуры. На рис. 9-7
показано, как это происходит.
В момент создания
текстуры, высота всего изображения, хранящаяся в
соответствующем поле заголовка растрового
изображения, заменяется высотой одного кадра.
Наверное, вы уже поняли, почему так удобно иметь
дело с вертикальной полоской: изображение
элементарно делится на кадры, если их высота
остается
222 ЩУ Глава 9.
Спрайты
Рис. 9-7. Создание текстуры на
основе растра с несколькими кадрами
неизменной, а копирование
любого кадра в рабочую область осуществляется
функцией memcpy или аналогичной ей.
ПРИМЕЧАНИЕ
Возможно, в более поздних
версиях Direct3D будут разрешены непосредственные
манипуляции с графическими данными, что позволит
обойтись без копирования.
Создание растровых
изображений
Создание растровых
изображений, из которых состоит спрайт, требует
некоторого предварительного планирования. Если
ваше приложение должно работать в системе с 256
цветами, следует в первую очередь подумать о том,
как в растре будут использоваться цвета. Если в
ней поддерживается больше цветов, вы обладаете
большей свободой в создании изображений.
Поскольку я абсолютно
лишен каких-либо художественных способностей,
пришлось создавать растровые изображения с
помощью видеокамеры и карты захвата видео. Я
снимал пластиковую игрушку одного из своих
детей. Специалисты по компьютерной графике умеют
творить чудеса с помощью своих излюбленных
инструментов, но мне пришлось добиваться нужного
результата с помощью самых простых средств. На
тот случай, если у вас под рукой не окажется
художника, я опишу, как я это сделал.
Для создания изображений я
воспользовался белой картонной ширмой и
фотографическим прожектором. Поставив игрушку
на ширму, я установил камеру так, как показано на
рис. 9-8.
Создание растровых изображений
'i^ 223
Рис. 9-8. Домашняя киностудия
Я придал игрушке
агрессивную позу, заснял ее и перенес
изображение с видеокамеры на компьютер. Затем я
передвинул игрушку в новое положение и сделал
новый снимок. Это повторялось до тех пор, пока у
меня не появился полный набор кадров с игрушкой,
размахивающей руками с явно недружелюбными
намерениями. Изображения были сняты в виде
24-битных растров в разрешении примерно 300х200
пикселей.
Я обрезал и масштабировал
все изображения до размеров 256'<256 пикселей, а
затем перевел их в 8-битный формат (256 цветов) с
произвольной палитрой. После того как все
изображения были преобразованы, я скопировал
одно из них в качестве заполнителя для рабочей
области в финальном растре. Затем я присвоил
рабочей области имя SOO, а кадры назвал S01, S02 и т. д.
Далее я воспользовался
программой, написанной мной для книги по
анимации, для того, чтобы взять серию
изображений, преобразовать их к единой палитре и
построить полоску со всеми изображениями.
Программа называется Viewdib и находится в каталоге
Tools. В результате получился растровый файл,
напоминающий рис. 9-5, за исключением того, что
верхний кадр в нем представляет собой копию
одного из других кадров.
Затем я удалил все
посторонние детали (тени, отражения и т. д.), чтобы
оставить чистое изображение игрушки, и заполнил
окружающий фон черным (прозрачным) цветом.
Разумеется, это значит, что в вашем изображении
не может использоваться черный цвет. Если вы
хотите использовать черный в качестве
прозрачного цвета и при этом ваше изображение
должно содержать черные участки, замените черный
цвет в изображении другим оттенком (например,
вместо RGB: 0, 0, 0 можно использовать RGB: О, О, 1).
Наконец, я воспользовался Microsoft Imager, чтобы
сократить количество цветов с 256 до 8. Результат
изображен на рис. 9-9.
224 ЦЩ^ Глава 9. Спрайты
Рис. 9-9. Изображения для
составного спрайта
ПРИМЕЧАНИЕ
Верхний кадр является точной
копией кадра, расположенного под ним, и
резервирует место под рабочую область в
графических данных текстуры.
Должен сказать, что это
занятие оказалось на редкость нудным. Мой низкий
поклон тем, кто подобным образом зарабатывает
себе на хлеб. Нам остается лишь написать
программу.
Класс C3dSprite
Давайте посмотрим, как
устроен класс для работы с фреймом и
изображениями, из которых состоит спрайт, и как
им пользоваться в приложении. Одна часть
225
Создание растровых изображений
кода находится в классе
C3dlmage, а другая - в классе C3dSprite. Класс C3dlmage
содержит функции для загрузки растрового
изображения и деления его на кадры. Для работы с
текстурами, необходимыми для создания
анимационного спрайта, класс C3dSprite пользуется
услугами класса C3dTexture, производного от C3dlmage. На
самом деле здесь нет ничего сложного, поэтому
давайте рассмотрим процесс создания спрайта и на
ходу заполним возможные пробелы. Класс C3dSprite
определяется в 3dPlus.h следующим образом:
class C3dSprite : public C3dFrame
{
public:
DECLARE_DYNAMIC(C3dSprite) ;
C3dSprite() ;
virtual --C3dSprite () ;
BOOL Create(C3dScene* pScene,
double x, double y, double z, double scale, UINT
uiIDBitmap, int iPhases = 1);
BOOL SetPhase(int iPhase) ;
int GetNumPhases() {return m_Tex.GetNumPhases ();
} int GetPhasef) {return m_Tex.Get Phase();}
protected:
C3dTexture m_Tex;
};
Обратите внимание - класс
C3dSprite является производным от C3dFrame, и его членом
является объект C3dTexture. Мы подробно рассмотрим
две функции, Create и SetPhase, поскольку именно они
выполняют основную работу объекта-спрайта.
Функция Create спроектирована так, чтобы включение
спрайта в макет происходило как можно проще.
Давайте подробно рассмотрим ее, шаг за шагом.
BOOL C3dSprite::Create(C3dScene* pScene,
double x, double у, double z, double scale, UINT
uiIDBitmap, int iPhases) (
ASSERT(pScene) ;
ASSERT(iPhases > 0);
ASSERT(uiIDBitmap) ;
// Создать фрейм и включить
его в список фигур макета C3dFrame::Create(pScene) ;
pScene->m_ShapeList .Append (this) ;
// Установить фрейм в
заданное положение SetPosition(x, у, z);
226 ДЃ^ Глава 9. Спрайты
// Загрузить растровое
изображение для создания текстуры if ( !m_Tex.C3dIniage:
:Load (uiIDBitmap) ) {
TRACE("Failed to load texture
image\n");
return FALSE;
}
// Задать количество фаз
m_Tex.C3d!mage::SetNumPhases(iPhases) ;
// Создать текстуру по
растровому изображению m_Tex.Create ();
// Задать свойства
IDirect3DRMTexture* pITex = m_Tex.Getlnterface ();
ASSERT(pITex) ;
// Разрешить глубинное
масштабирование m_hr = pITex->SetDecalScale (TRUE) ;
ASSERT(SUCCEEDED(m_hr)) ;
// Задать исходный размер
double a = (double)m_Tex.GetWidth() / (double)m_Tex.GetHeight();
m__hr = pITex->SetDecalSize (scale * a,
scale);
ASSERT(SUCCEEDED(m_hr)) ;
// Разрешить прозрачность m
hr = pITex->SetDecalTransparency(TRUE);
ASSERT(SUCCEEDED(m_hr)) ;
// Назначить прозрачным
цветом черный m hr = pITex->SetDecalTransparentColor(RGB_MAKE(0, 0, 0)) ;
ASSERT(SUCCEEDED(m_hr)) ;
// Перенести начало
координат фрейма // в середину нижней стороны m_hr =
pITex->SetDecalOrigin (m_Tex. GetWidth () /2, m_Tex.GetHeight 0-1) ;
ASSERT(SUCCEEDED(m_hr)) ;
// Присоединить текстуру в
качестве визуального элемента return AddVisual(&m_Tex) ;
}
Прежде всего необходимо
создать объект C3dFrame (базового класса C3dSprite). Мы
включаем спрайт в макет и задаем начальное
положение фрейма в макете. Далее загружается
растровое изображение для текстуры из ресурса
BITMAP, добавленного с помощью AppStudio. Обратите
внимание на то, что мы вызываем
Создание оастоовых изобоажении
^Sil 227
функцию C3dlmage::Load базового
класса, а не функцию класса C3dTexture. Это сделано для
того, чтобы иметь возможность слегка изменить
изображение перед тем, как создавать по нему
текстуру.
После того как изображение
загружено, для него задается количество кадров
(iPhases). Кадр, используемый в качестве рабочей
области, не считается. После того как изображение
будет разделено на кадры, мы создаем текстуру.
Возможно, код получился некрасивым, но зато он
достаточно эффективен и позволяет создавать
многофазные спрайты, практически не обращаясь к
коду классов текстуры и растрового изображения.
После того как мы получаем
указатель на интерфейс IDirect3DRMTexture вызовом
функции Getlnterface, необходимо разрешить глубинное
масштабирование, чтобы размер спрайта изменялся
в зависимости от его положения по оси z. Исходный
размер изображения задается в соответствии с
масштабным коэффициентом, передаваемым в виде
аргумента функции Create. Масштабный коэффициент
позволяет задавать размер спрайта независимо от
размера растрового изображения. Небольшой
дополнительный фрагмент кода обеспечивает
сохранение пропорций изображения-оригинала.
Поскольку текстуры (и, следовательно, ваши
спрайты) разрешается масштабировать в обоих
направлениях, при желании вы можете сделать
своих персонажей низенькими и толстыми или,
наоборот, - высокими и тощими. Думаю, что
графические изображения большей частью
создаются такими, какими они должны выглядеть на
экране, поэтому важно сохранять правильные
пропорции.
Далее мы разрешаем
использование прозрачности в текстуре.
Прозрачным цветом назначается принятый по
умолчанию черный. Я сделал это, чтобы
продемонстрировать вам, как вызывается функция
SetDecalTransparentColor. В прошлом я пользовался и другим
подходом - назначал прозрачным цвет левого
верхнего пикселя изображения. Это дает
значительно больше свободы при разработке
графики, поскольку изображение можно окружить
любым цветом при условии, что данный цвет не
используется в самом изображении.
Последним шагом в создании
текстуры является перенос начала координат
фрейма в середину нижнего края изображения. Мне
показалось, что с этой точкой будет удобно
работать, особенно тогда, когда спрайт должен
стоять <на поверхности земли> - в данном случае
для правильного расположения спрайта достаточно
присвоить его координате у значение координаты у
поверхности.
После того как текстура
создана, остается лишь включить ее в качестве
визуального элемента фрейма. Теперь наш спрайт
будет виден на экране при воспроизведении
макета.
Изменение фазы
Чтобы изменить фазу
спрайта, следует вызвать функцию C3dSprite::SetPhase и
передать ей номер новой фазы:
BOOL C3dSprite::SetPhase(int iPhase) f
return m_Tex.SetPhase(iPhase);
Работа этой функции
сводится к вызову C3dlmage::SetPhase:
228 Я? Глава 9. Спрайты
BOOL C3dlmage::SetPhase(int iPhase)
{
if ((iPhase <0) ЃI (iPhase>= m_iPhases)) f
return FALSE;
} m iCurPhase = iPhase;
// Скопировать
графические данные нужной фазы в рабочую
область
int iBytes =
m_rlimg.bytes_per_line * m_rlimg.height;
memcpy(m rlimg.bufferi,
(BYTE*)m_rlimg.bufferl + (m_iCurPhase + 1) *
iBytes,
iBytes) ;
// Известить производные
классы об изменениях _OnImageChanged() ;
return TRUE;
}
Приведенный выше фрагмент
реализует операцию копирования, изображенную на
рис. 9-7. Мы вычисляем адрес нужного кадра, затем
функцией memcpy копируем данные кадра в рабочую
область. После завершения копирования
вызывается функция _OnlmageChanged. Эта виртуальная
функция класса C3dlmage не делает ничего, однако она
может быть переопределена в производных классах
(таких, как C3dTexture), которым необходимо сообщать о
внесении изменении в изображение. Класс C3dTexture с
помощью этой функции, расположенной в файле
3dlmage.cpp, уведомляет механизм визуализации об
изменении графических данных текстуры:
// виртуальная функция
void C3dTexture::_OnImageChanged()
{
if (!m pITexture) return; // Возможно,
текстура еще не
создана
// Новое изображение - известить
механизм визуализации // об изменении
графических данных (но не палитры!) m hr = m
pITexture->Changed(TRUE, FALSE);
ASSERT(SUCCEEDED(m_hr)) ;
}
При этом крайне важно
уведомить механизм визуализации об изменении
графических данных, поскольку они могут
кэшироваться; простое обновление графических
данных еще не гарантирует изменения макета на
экране.
Создание растровых изображений ''IfHj
229
Приложение Sprites
Когда я пишу
приложение-пример, то стараюсь избегать всего,
что не является абсолютно необходимым для
раскрытия темы. Стоило мне наладить работу
класса C3dSprite в приложении Sprites, как тут же
немедленно захотелось добавить в него массу
новых возможностей. Тем не менее я устоял перед
искушением, и результат моих усилий можно в
первом приближении назвать игрой.
Приложение Sprites - это
DOOM-образная игра. Если вам еще не приходилось
иметь дело с DOOM, ребята из Id Software наверняка с
большим удовольствием продадут вам ее (но
пожалуйста, не заказывайте игру сейчас, иначе вы
никогда не закончите читать эту книгу!)
Основная цель игры (моей, а
не DOOM!) - выслеживать врагов и издавать громкие
звуки, имитирующие выстрелы из оружия, которое бы
вам хотелось иметь при себе в подобной ситуации.
Действие игры происходит в лабиринте стен,
построенных из красного вещества. Стены
абсолютно непрозрачны и кажутся довольно
прочными, однако вы наделены магическими силами
и можете проходить прямо сквозь них. Пол
лабиринта покрыт дешевым ковром.
Для этой игры вам
понадобится джойстик. Я воспользовался своим
SideWinder Pro и настроил его так, чтобы кнопка 1
(гашетка на SideWinder) выполняла стрельбу. Если у вас
есть только мышь, вы все равно сможете находить
врагов, однако выстрелы придется вообразить. На
рис. 9-10 показан вид экрана в начале игры -
собственно, ничего больше вам знать и не
потребуется.
Рис. 9-10. Похоже, хозяев нет
дома
При разработке этого
приложения я воспользовался планом лабиринта,
изображенным на рис. 9-11 (для самых недогадливых
он станет подсказкой).
230 ЃЃЃИ' Глава 9. Спрайты
Рис. 9-11. План лабиринта
Каждая стена лабиринта
состоит из четырех граней, и на каждую грань
наложена красная текстура. Для стен я создал
класс CWall, производный от C3dShape, код которого мало
отличается от того, что мы видели в предыдущих
главах, и потому я не стану приводить его.
Построение лабиринта с помощью класса CWall
происходит следующим образом:
BOOL CMainFrame::NewScene() {
WALLINFO wi [] = {
(-6, 5, 6, 6, 1},
{ 5, -5, 6, 5, 0.9},
{-6, -6, 6, -5, 1.1},
(-6, -5, -5, 5, 0.8},
(-3, 2, 2, 3, 0.5},
( 2, -3, 3, 3, 0.9},
(-4, -1, 0, 0, 1},
(-1, -5, 0, -2, 1.3}
};
int nWalls = sizeof(wi) / sizeof(WALLINFO);
WALLINFO* pwi = wi;
for (int i = 0; i
CWall* pWall = new CWall(pwi);
m_pScene->AddChild(pWall) ;
m_pScene->m_ShapeList .Append (pWall) ;
pwi++;
}
Приложение Sprites
231
Данные для каждой стены
состоят из двух наборов угловых координат х и у и
высоты. Стены поочередно создаются и включаются
в макет.
Пол представляет собой
объект с одной гранью, на которую наложена
текстура. Для фона макета выбран синий цвет,
чтобы создать иллюзию неба над стенами:
m_pScene->SetBackground(0, О, 1);
CFloor* pFloor = new CFioor(-6, -6, 6, 6)
;
m_pScene->AddChild (pFloor);
m_pScene->m_ShapeList .Append (pFloor) ;
Для пола я также завел
отдельный класс, но опять же его код ничем не
отличается от того, с помощью которого мы
создавали объект с одной гранью в главе о цветах.
Последнее, что необходимо
добавить в наш лабиринт, - врагов и взрывы. И снова
я создал специальные классы для каждого из этих
объектов. Они являются производными от C3dSprite, и
поскольку оба класса похожи, мы рассмотрим лишь
один из них. Ниже приведено определение класса
CSoldier (находящееся в файле maze.h), с помощью которого
мы создаем себе врагов:
class CSoldier : public C3dSprite
{
public:
CSoldier(C3dScene* pScene, double х,
double z) ;
void Update 0;
>;
Как видите, здесь нет
ничего сложного: конструктор создает объект, а
функция Update заведует его перемещением. Код
конструктора (из файла maze.cpp) выглядит следующим
образом:
CSoldier::CSoldier(C3dScene* pScene, double х,
double z) (
Create(pScene, х, 0, z, 0.5, IDB_SOLDIER,
4);
Должно быть, выше
приведена одна из самых простых функций, которые
нам приходилось видеть в этой книге. Функция
C3dSprite::Create выполняет всю основную работу по
созданию объекта-спрайта. Ей передается
идентификатор растрового ресурса. Последний
аргумент (4) определяет количество кадров в
спрайте (не считая рабочей области).
Поскольку спрайты должны
двигаться, нам понадобится функция Update для
изменения фазы:
void CSoldier::Update() {
SetPhase((GetPhaseO + 1) % GetNumPhases ());
232 fli"' Глава 9. Спрайты
Данная функция вызывается
во время пассивной работы приложения,
непосредственно перед обновлением всего макета.
Класс для взрывов содержит аналогичную функцию
для изменения внешнего вида взрыва со временем.
Постойте, какого еще
взрыва?!? А это вы можете выяснить самостоятельно
(или в компании врагов).
Довольно о плоских фигурах
Мы выяснили, как
пользоваться спрайтами для отображения
персонажей или объектов в трехмерной сцене. Как
нетрудно убедиться, создавать многогранные
фигуры вовсе не обязательно. Спраиты могут
использоваться вместе с трехмерными объектами,
чтобы обеспечить оптимальное соотношение между
качеством изображения и производительностью.
Создание игры отнюдь не
сводится к расстановке спрайтов в трехмерном
макете. Я даже не пытался показать, как
ограничить перемещение игрока в лабиринте или
предупредить его о приближающейся опасности.
Оставляю эту работу вам - сделайте следующий шаг
и реализуйте перечисленные мной функции.
Далее.. |