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


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

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

Следующие инструкции вычисляют, в каком месте экрана должен размещаться отображаемый блок. Поскольку в программе используется трехмерная графика, для задания смещения блока применяются значения с плавающей точкой. Размеры создаваемого программой окна составляют 480 точек в ширину и в высоту. Учитывая эту особенность, для того, чтобы блоки отображались вплотную к границе окна, они должны смещаться на 240 единиц влево. Аналогичным образом для того чтобы блоки отображались вплотную к верхней границе окна, они должны смещаться на 240.0 – 48.0, или 192.0 единицы вверх от начала координат.

В следующей строке кода вызывается написанная мной функция vDrawTile(). Прототип функции выглядит следующим образом:

vDrawTile( float fXPos, float fYPos, float fXSize, float fYSize, int iTexture)

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

Следующий параметр, fYPos, аналогичен первому, за исключением того, что задает местоположение в трехмерной системе координат вдоль оси Y.

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

Последний параметр является индексом в массиве текстур m_pTexture. Он задает текстуру для визуализации.

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

Смотрите, это было не так уж и плохо, правда? Поток выполнения программы показан на Рисунок 5.40.










Начало  Назад  Вперед


Книжный магазин

Файл программы Main cpp


Файл main.cpp не слишком сложен, поскольку он по большей части следует каркасу приложения DirectX. Первая представляющая для нас интерес функция — это конструктор класса. Вот его код:

CD3DFramework::CD3DFramework() { m_strWindowTitle = _T("2D Tile Example"); m_pStatsFont = NULL; m_shWindowWidth = 480; m_shWindowHeight = 480; m_shTileMapWidth = 10; m_shTileMapHeight = 10; }

Как видно из приведенного фрагмента кода, в конструкторе класса задается размер блочной карты. Я установил высоту и ширину карты равной 10 блокам, чтобы при выводе карта занимала все окно целиком. Ширина и высота блока равны 48 пикселам, поэтому размер окна устанавливается равным 480 на 480 точек. Поскольку 10 * 48 = 480, данный размер как раз обеспечивает точное совпадение окна и выводимой карты.

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

HRESULT CD3DFramework::OneTimeSceneInit() { m_pStatsFont = new CD3DFont(_T("Arial"), 8, NULL); if(m_pStatsFont == NULL) return E_FAIL; // Заполнение карты блоками с изображением травы memset(m_iTileMap, 0, (m_shTileMapWidth * m_shTileMapHeight) * sizeof(int)); // заполнение второй половины блоками с изображением песка for(int i = 0; i < 50; i++) { m_iTileMap[i+50] = 3; } // Случайное размещение камней на траве // Инициализация генератора случайных чисел srand(timeGetTime()); for(i = 0; i < 50; i++) { // Размещение камней на траве, если случайное число = 5 if(rand()%10 == 5) m_iTileMap[i] = 1; } // Размещение переходных блоков между травой и песком for(i = 50; i < 60; i++) { m_iTileMap[i] = 2; } return S_OK; }

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

Следующая часть кода представляет собой цикл, в котором блокам в нижней половине карты присваивается значение 3. Блок с номером 3 содержит текстуру с изображением песка; таким образом этот код формирует песчанный пляж в нижней части карты.

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

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

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

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

sprintf(szFileName, "grass00.bmp"); if(FAILED(D3DXCreateTextureFromFile(m_pd3dDevice, szFileName, &m_pTexture[0]))) { return S_OK; } sprintf(szFileName, "grass_rocks.bmp"); if(FAILED(D3DXCreateTextureFromFile(m_pd3dDevice, szFileName, &m_pTexture[1]))) { return S_OK; } sprintf(szFileName, "grass_edging_bottom.bmp"); if(FAILED(D3DXCreateTextureFromFile(m_pd3dDevice, szFileName, &m_pTexture[2]))) { return S_OK; } sprintf(szFileName, "beach.bmp"); if(FAILED(D3DXCreateTextureFromFile(m_pd3dDevice, szFileName, &m_pTexture[3]))) { return S_OK; }

Код инициализации текстур использует вспомогательную функцию DirectX с именем D3DXCreateTextureFromFile(). Это действительно крутая функция, ведь она содержит весь код, необходимый для загрузки изображений таких форматов, как BMP, TGA и JPEG. Чтобы использовать ее необходимо включить в проект библиотеку d3dx9.lib и заголовочный файл d3dx9tex.h. Прототип функции выглядит следующим образом:

HRESULT D3DXCreateTextureFromFile( LPDIRECT3DDEVICE9 pDevice, LPCSTR pSrcFile, LPDIRECT3DTEXTURE9* ppTexture );

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

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

Последний параметр, ppTexture, является указателем на текстуру. Если вы вернетесь назад, к моему описанию заголовочного файла, то вспомните, что для хранения указателей на текстуры я использую массив m_pTexture. В этом параметре я также указываю индекс в массиве текстур. Например, для текстуры с номером 1 я указываю в параметре m_pTexture[0], для текстуры с номером 2 использую m_pTexture[1], и так далее.

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

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

HRESULT CD3DFramework::Render() { D3DXMATRIX matTranslation; D3DXMATRIX matRotation; D3DXMATRIX matRotation2; D3DXMATRIX matScale; int iX; int iY; int iCurTile; float fTileX, fTileY; // Очистка порта просмотра m_pd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(120,120,120), 1.0f, 0L); // Начало создания сцены if(SUCCEEDED(m_pd3dDevice->BeginScene())) { // Вертикаль for(iY = 0; iY < m_shTileMapHeight; iY++) { // Горизонталь for(iX = 0; iX < m_shTileMapWidth; iX++) { // Вычисление номера отображаемого блока iCurTile = m_iTileMap[iX + (iY * m_shTileMapWidth)]; // Вычисление экранных координат fTileX = -240.0f + (iX * 48.0f); fTileY = 192.0f - (iY * 48.0f); // Отображение блока vDrawTile(fTileX, fTileY, 48.0f, 48.0f, iCurTile); } } // Отображение частоты кадров m_pStatsFont->DrawText(2, 0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats); // Отображение сведений о видеокарте m_pStatsFont->DrawText(2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats); // Завершение создания сцены m_pd3dDevice->EndScene(); } return S_OK; }

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

Далее расположен вызов функции BeginScene(). Она запускает механизм трехмерной визуализации. Вы должны вызвать эту функцию перед выполнением операций трехмерной графики.

ВНИМАНИЕ
Вы должны начинать визуализацию с вызова функции BeginScene() и завершать ее вызовом функции EndScene(). Если вы не сделаете это, ваша программа аварийно завершится.