Загрузка...
 
Печать

DirectX Graphics (DirectX 9). Создание и рендеринг объектов

  • Язык программирования C++.
  • Платформа Win32.


Объекты (модели) игры можно создавать двумя способами:

  • генерировать программно;
  • загружать из файлов 3D-моделей, созданных в 3D-редакторах.

В данной статье рассмотрим оба способа.

Для компиляции примера в конце статьи понадобится следующее ПО:

Всё легко гуглится + есть в разделе "Софт" нашего сайта.

Содержание

Применение вершин (Using vertices)

Direct3D позволяет определить вершину множеством различных способов.1 К примеру, если ты используешь только 2D-графику, можно просто указать координаты 2D экранных (трансформированных) координатах. С другой стороны, если ты используешь локальные (local) и мировые (world) координаты, то можешь указать координаты в 3D-проекции (нетрансформированные координаты). Вдобавок, Direct3D позволяет включать (в том числе) в каждую вершину данные об используемом цвете или текстуре. Как же отслеживать всю эту инфу и быть уверенным, что Direct3D понимает, что ты делаешь в данный момент?
Встречаем гибкий формат вершин(external link) (Flexible Vertex Format).

Гибкий формат вершин (Flexible Vertex Format, FVF)

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

С FVF ты можешь решать, какую именно информацию будут хранить вершины. Например: 2D или 3D координаты, цвет и т.д.
На практике FVF реализуется в виде обычной структуры, в которую можно добавлять компоненты по своему усмотрению. Конечно, здесь есть свои ограничения. Например, компоненты следует указывать в определённом порядке + некоторые компоненты могут конфликтовать друг с другом (например не допускается одновременное указание 2D и 3D координат).
После заполнения FVF-структуры создаётся FVF-дескриптор (FVF-descriptor), представляющий собой комбинацию флагов, описывающих твой кастомный формат вершин.
Рассмотрим пример FVF-структуры:

Пример FVF-структуры
typedef struct {
 FLOAT x, y, z, rhw;  // 2D-координаты
 FLOAT x, y, z;    // 3D-координаты
 FLOAT nx, ny, nz;   // Нормали
 D3DCOLOR diffuse;   // Рассеянный (diffuse) цвет
 FLOAT u, v;        // Координаты текстуры
} sVertex;

Данный код содержит структуру вершины (vertex structure), заполненную различными переменными, допустимыми форматом FVF. Порядок их указания важен! При удалении каких-либо переменных из данного примера необходимо сохранить порядок их перечисления. Как видим, единственный конфликт здесь возникает при указании 2D и 3D координат + координат нормалей. Нормали (normals) представляют собой координаты, определяющие направление. Они могут использоваться только совместно с 3D-координатами. Здесь необходимо выбрать, какие координаты (2D или 3D) выбрать, а от каких отказаться. Оба вида координат в одной FVF-структуре использовать нельзя.
Единственное отличие 2D и 3D координат - присутствие у 2D-координат отдельного параметра rhw (reciprocal of the homogeneous W; обратная величина(external link) однородного (в пространстве отсечения) W). Если в двух словах, то rhw обычно представляет собой расстояние от наблюдателя (viewpoint) к вершине, расположенной на оси Z. В большинстве случаев можно выставить rhw в 1,0 и не париться.
Обрати внимание на то, что пользовательская структура sVertex активно использует тип данных FLOAT (значение с плавающей точкой).
Тип данных D3DCOLOR - это по сути переменная типа DWORD, которая применяется в Direct3D для хранения цветовых значений.
Для создания цветового значения типа D3DCOLOR применяют одну из двух функций:

D3DCOLOR D3DCOLOR_RGBA(Red, Green, Blue, Alpha);
D3DCOLOR D3DCOLOR_COLORVALUE(Red, Green, Blue, Alpha);

Каждая из этих функций (вообще, это макросы) принимает 4 параметра, представляющие собой количественные значения (amount) каждого цветового компонента (color component) + цвет альфа-канала (канала прозрачности). Данные значения варьируются:

  • от 0 до 255 для каждого параметра макроса D3DCOLOR_RGBA,
  • от 0.0 до 1.0 для параметров макроса D3DCOLOR_COLORVALUE.

Для цвета альфа-канала можно выбрать любое допустимое значение. Но на практике его обычно выставляют в макс. значение 255 (или 1.0).

Создаём пользовательскую FVF-структуру (пример)

Например, нам необходимо включить в кастомную FVF-структуру только 3D-координаты вершины и данные по её рассеянному цвету (diffuse color):

Пример пользовательской (=кастомной) FVF-структуры
struct VERTEX
{
 FLOAT x, y, z;
 FLOAT tu, tv;
}

Первые 3 переменных члена - это позиция вершины.
tu и tv - переменные, описывающие метод нанесения текстуры. Direct3D также поддерживает оборачивание (wrapping) текстуры вокруг 3D-объекта. В этом случае сперва указывают координаты левого верхнего угла текстуры: tu=0.0 и tv=0.0. Затем правого нижнего: tu=1.0 и tv=1.0 . Технически все полигоны, расположенные между этим прямоугольником имеют нулевые текстурные координаты, что указывает Direct3D просто растянуть текстуру над ними.

Создаём FVF-дескриптор

Как только FVF-структура создана, создаём её FVF-дескриптор, указав один или несколько специальных флагов:

Флаг Описание
D3DFVF_XYZ В структуру включены 3D-координаты вершины.
D3DFVF_XYZRHW В структуру включены 2D-координаты вершины.
D3DFVF_NORMAL В структуру включены данные нормали (вектора).
D3DFVF_DIFFUSE В структуру включены данные о рассеянном (diffuse) цвете вершины.
D3DFVF_TEXT В структуру включены текстурные координаты.

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

#define VertexFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

Указаны только флаги, необходимые нашей FVF-структуре.

Рис. 1 Треугольный список (list) не имеет общих вершин с другими полигонами. Объединяя полигоны в треугольные полосы (strips) и вентиляторы (fans), мы сокращаем число вершин, тем самым экономя ресурсы и увеличивая производительность.
Рис. 1 Треугольный список (list) не имеет общих вершин с другими полигонами. Объединяя полигоны в треугольные полосы (strips) и вентиляторы (fans), мы сокращаем число вершин, тем самым экономя ресурсы и увеличивая производительность.

Квады (quads)

  • Схожи со спрайтами (sprites).
  • Представляют собой квадрат, состоящий их двух треугольных полигонов с одной общей стороной.
  • Является частным случаем полосы (strip; одного из стандартных плоских примитивов Direct3D; см. Рис.1).
  • Широко применяются в игрокодинге наряду со спрайтами.
  • В DirectX 9 для их хранения появилась специальная структура QUAD.2

Взяв за основу предварительно созданную FVF-структуру VERTEX, можно создать специальную структуру QUAD, которая будет хранить инфу о квадах:

struct QUAD
{
 VERTEX vertices;
 LPDIRECT3DVERTEXBUFFER9 buffer;
 LPDIRECT3DTEXTURE9 texture;
}

Структура QUAD полностью самодостаточна (self-contained). В нашем случае мы разместили в ней инфу о 4-х вершинах, соответствующих четырём углам квада. Для одного квада у нас есть вершинный буфер и текстура, которая его покрывает.
Приведённый выше код не создаёт квад, а лишь является своеобразным шаблоном. Прежде чем создать квад и заполнить его вершинами, сперва пишут функцию, создающую вектор (vector):

VERTEX CreateVertex(float x, float y, float z, float tu, float tv)
{
 VERTEX vertex;
 vertex.x = X;
 vertex.y = Y;
 vertex.z = Z;
 vertex.tu = tu;
 vertex.tv = tv;
 return vertex;
}

Функция CreateVertex (здесь) создаёт временную структуру VERTEX и заполняет её значениями, передаваемыми в параметрах. Возвращаемое значение - структура VERTEX, содержащая 5 переменных членов.
Буквально через абзац мы создадим квад. Но сперва рассмотрим тему "Вершинные буферы".

Вершинные буферы (Vertex Buffers)

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

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

Создание вершинного буфера (DirectX 9)

После создания FVF-структуры вершины и её дескриптора, создаём переменную вершинного буфера:

LPDIRECT3DVERTEXBUFFER9 buffer;

Далее создаём сам вершинный буфер путём вызова функции CreateVertexBuffer. Вот её прототип:

Прототип функции CreateVertexBuffer
HRESULT CreateVertexBuffer(
 UINT Length,
 DWORD Usage,
 DWORD FVF,
 D3DPOOL Pool,
 IDirect3DVertexBuffer9** ppVertexBuffer,
 HANDLE* pSharedHandle
);

Первый параметр (Length) указывает размер вершинного буфера, который выбирается так, чтобы его было достаточно для хранения всех вершин полигонов, готовящихся к рендерингу.
Второй параметр указывает способ доступа к вершинному буферу (обычно здесь стоит "только для записи").
Третий параметр указывает тип потока вершин (vertex stream type), который ожидает получить Direct3D. Здесь передаются значения, соответствующие типу FVF-структуры, созданной ранее. В нашем случае в ней есть только позиция и текстурные координаты каждой вершины. Поэтому здесь выставляем D3DFVF_XYZ | D3DFVF_TEX1. Вот пример определения такого FVF-формата:

#define D3DFVF_MYVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)

Пятый параметр - указатель на создаваемый вершинный буфер.
Последний параметр нам не нужен.
Вот пример вызова функции CreateVertexBuffer:

d3ddev->CreateVertexBuffer(
 4*sizeof(VERTEX),
 D3DUSAGE_WRITEONLY,
 D3DFVF_MYVERTEX,
 D3DPOOL_DEFAULT,
 &buffer,
 NULL);

Видно, что для указания объяма буфера в первом параметре стоит вызов функции sizeof(VERTEX), умноженный на 4 (число вершин). В случае отрисовки треугольника, вызов функции sizeof переменожается на 3. Самые важные здесь параметры - это первый (размер буфера) и пятый (указатель на создаваемый вершинный буфер).

Заполнение вершинного буфера вершинами

Данный шаг должен всегда следует после кода, который генерирует или загружает массив вершин. Т.к. эти данные включаются в вершинный буфер.
Ещё раз приведём пример определения структуры QUAD:

struct QUAD
{
 VERTEX vertices;
 LPDIRECT3DVERTEXBUFFER9 buffer;
 LPDIRECT3DTEXTURE9 texture;
};

Применим функцию CreateVertex для определения значений по умолчанию для элементов (вершин) квада:

vertices[0] = CreateVertex(-1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
vertices[1] = CreateVertex(1.0f, 1.0f, 0.0f, 1.0f, 0.0f);
vertices[2] = CreateVertex(-1.0f, -1.0f, 0.0f, 0.0f, 1.0f);
vertices[3] = CreateVertex(1.0f, -1.0f, 0.0f, 1.0f, 1.0f);

И это лишь один из способов заполнения данных вершин. Ты можешь определить в коде программы тип полигона, отличный от вышеприведённого либо загрузить 3D-форму (меш) из файла.
Как только вершины получили свои координаты (позицию + текстурные), можно смело размещать их в вершинном буфере. Перед этим буфер блокируют методом Lock, копируют туда вершины и затем разблокируют методом Unlock. Данная операция требует наличия временного указателя. Вот пример:

void *temp = NULL;
buffer->Lock(0, sizeof(vertices), (void**)&temp, 0);
memcpy(temp, vertices, sizeof(vertices));
buffer->Unlock;

Прототип функции Lock выглядит так:

HRESULT Lock(
 UINT OffsetToLock,
 UINT SizeToLock,
 VOID **ppbData,
 DWORD Flags
);

Рендеринг содержимого вершинного буфера (Rendering the Vertex Buffer)

После инициализации вершинный буфер готов к применению в конвейере Direct3D. Для отправки содержимого вершинного буфера на экран требуется:

  • назначить поток источника (stream source) для объекта устройства Direct3D, который будет указывать на наш вершинный буфер;
  • вызвать функцию DrawPrimitive.

Но перед этим сперва надо назначить используемую текстуру. Данный шаг нередко приводит игрокодеров в замешательство, особенно начинающих. Технически Direct3D работает в данный момент времени лишь с одной текстурой. Поэтому Direct3D надо всегда уточнять, с какой именно текстурой сейчас работаем. В противном случае Direct3D покроет всю сцену последней использованной текстурой. Не существует готовых процедур, которые укажут Direct3D использовать одну текстуру на этом полигоне, а другую - на следующем. Такие процедуры программер пишет сам всякий раз, когда требуется смена текстуры.
Что касается текстурирования квадов, то здесь мы имеем дело с одной текстурой на каждый квад. Поэтому весь процесс заметно упрощается. Можно создать вершинный буфер любого желаемого размера. Но для лучшего понимания процесса 3D-рендеринга мы создадим для каждого квада свой собственный вершинный буфер. Это не самый эффективный способ с точки зрения производительности, зато наглядно покажет весь процесс изнутри.
Итак, каждый квад (в нашем случае) имеет:

  • вершинный буфер;
  • текстуру;
  • код рендеринга (в котором задействована структура QUAD).

Сперва назначим кваду текстуру:

d3ddev->SetTexture(0, texture);

Далее назначим источник потока (stream source), указав таким образом Direct3D, откуда брать вершины и другие параметры:

d3ddev->SetStreamSource(0, q.buffer, 0m sizeof(VERTEX));

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

d3ddev->DrawPrimitive(D3DPT
_TRIANGLESTRIP, 0, 2);

Очевидно, что вызовы трёх вышеприведённых функций лучше объединить в одну авторскую функцию-обёртку (wrapper function) с произвольным, но говорящим названием (в нашем случае - Draw).

Создаём квад (Creating a Quad)

С одной стороны, квад - представляет собой 4 угла прямоугольника, который, в свою очередь, является строительным блоком для любой 3D-сцены. С помощью горстки кубов можно создать много разных объектов. В углу каждого квада расположены вершины. Поэтому с другой стороны, квад - это примитив лента (strip), состоящий из 4-х вершин.

Рисуем треугольники

Существует несколько способов отрисовки объектов, состоящих из треугольных полигонов?

  • Список треугольников (Triangle List) рисует каждый треугольник отдельно, независимо от других.
  • Треугольная лента (полоса; Triangle Strip) рисует 2 и более полигонов, имеющих общие вершины.

Подробнее об этом читаем здесь: Базовые понятия 3D-графики.
Очевидно, что второй способ более эффективен и, таким образом, более предпочтителен т.к. позволяет ускорить процесс рендеринга за счёт уменьшения кол-ва используемых вершин. Но всю сцену из лент не построишь, т.к. зачастую бОльшая часть объектов никак не соединены друг с другом. Но треугольные ленты отлично работают при программной генерации земной поверхности, зданий и других больших объектов.
С точки зрения производительности Direct3D одинаково быстро рендерит как несколько вершинных буферов, так и один большой. В коде это можно представить в виде зацикленных условных переходов for.
Квад состоит из двух треугольников. Ему нужны всего 4 вершины, т.к. оба треугольника в этом случае объединены в полосу треугольников (triangle strip; 2 треугольника имеют одну общую сторону и две общие вершины).

Создаём квад на базе готового буфера вершин

Вообще отрисовка любых полигонов (треугольник, квад или целая модель) выполняется в два этапа.
Сперва вершины копируются в вершинный поток (vertex stream) Direct3D. Для этого вершинный буфер блокируется командой Lock и затем вершины копируются в некий временный (temporary) буфер с указателем (pointer) на него. Далее вершинный буфер разблокируется командой Unlock:

void *temp = NULL;
quad->buffer->Lock(0, sizeof(quad->vertices), (void**)&temp, 0);
memcpy(temp, quad->vertices, sizeof(quad->vertices));
quad->buffer->Unlock();

Следующий этап - назначить текстуру, указав Direct3D, где найти источник потока (stream source), содержащий вершины. После этого вызывается функция DrawPrimitive, которая рендерит полигоны, указанные в потоке вершинного буфера. Иными словами полигоны перемещаются из вершинного буфера в поток (источника) и затем пересобираются на экране монитора:

d3ddev->SetTexture(0, quad->texture);
d3ddev->SetStreamSource(0, quad->buffer, 0, sizeof(VERTEX));
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

Загрузка и рендеринг файла 3D-модели из .X-файла

Определяем структуру MODEL

Сперва определим кастомную (т.е. её содержание определяет программер) структуру, которая будет хранить всю инфу о 3D-объекте, загруженном из .X-файла:

struct MODEL
{
 LPD3DXMESH mesh;
 D3DMATERIAL9* materials;
 LPDIRECT3DTEXTURE9* textures;
 DWORD material_count;
};

Некоторые игрокодеры называют .X-файлы меш-файлами, что не совсем верно. .X-файлы, помимо мешей (полигональных сеток), могут хранить в себе материалы, инструкции по анимации и множество других видов данных. Поэтому наша структура называется более общим понятием MODEL. Она содержит основные объекты, необходимые для загрузки и рендеринга файла модели.
В первой строке, конечно же, стоит указатель на меш (mesh). Если в .X-файле содержится несколько мешей/объектов (.X-формат файла это позволяет), то они должны быть объединены в один меш (например утилитой conv3ds.exe из DirectX SDK 6.1).
Далее идёт указатель на объект типа D3DMATERIAL9, который указывает на массив материалов, определённых в .X-файле.
Указатель LPDIRECT3DTEXTURE9 хранит данные о текстурах. Модель может иметь несколько текстур. Технически текстуры не хранятся в самом .X-файле. В нём хранятся лишь имена растровых файлов текстур, содержащиеся в одном каталоге с ним.
В конце списка элементов структуры MODEL стоит счётчик числа материалов модели, используемых при рендеринге. У модели может быть много материалов, но далеко не все из них требуют текстуру. Текстуры должны быть определены отдельно от материалов. Несмотря на это, в нашей структуре MODEL счётчик текстур отсутствует.
Далее на базе созданного шаблона создаётся объект структуры:

MODEL *model = (MODEL*)malloc(sizeof(MODEL));

Эта строка размещает объект структуры в памяти и возвращает свой указатель в вызвавшую её функцию LoadModel (о ней чуть позже).

Загружаем меш (Loading the Mesh)

Для создания полигональной сетки (=меша) из загруженного .X-файла модели Direct3D (DirectX 9) предоставляет специальную функцию D3DXLoadMeshFromX. Вот её прототип:

Прототип функции D3DXLoadMeshFromX
HRESULT WINAPI D3DXLoadMeshFromX(
 LPCSTR pFilename,
 DWORD Options,
 LPDIRECT3DDEVICE9 pDevice,
 LPD3DXBUFFER *ppAdjacency,
 LPD3DXBUFFER *ppMaterials,
 LPD3DXBUFFER *ppEffectInstances,
 DWORD *pNumMaterials,
 LPD3DXMESH *ppMesh
);

Вот самые важные здесь параметры:

  • pFileName (имя .X-файла);
  • pDevice (объект устройства Direct3D);
  • ppMaterials (указатель на буфер материалов);
  • pNumMaterials (указатель на счётчик материалов);
  • ppMesh (указатель на создаваемый объект меша).

Перед вызовом функции D3DXLoadMeshFromX необходимо создать (проинициализировать) буфер материалов (material buffer), куда будут загружаться данные о материалах из .X-файла:

LPD3DXBUFFER matbuffer;

Вот пример вызова функции D3DXLoadMeshFromX:

result = D3DXLoadMeshFromX(
 filename,  // Имя файла
 D3DXMESH_SYSTEMMEM,  // Опции меша
 d3ddev,  // Объект устройства Direct3D
 NULL,  // Буфер смежных вершин (adjacency buffer),
 &matbuffer,  // Буфер материалов
 NULL,  //  Спецэффекты
 &model->material_count,  // Кол-во материалов
 &model->mesh  // Результирующий меш
);

Загружаем текстуры и материалы

Материалы хранятся в буфере материалов. Но перед их применением в рендеринге модели они должны быть корректно сконвертированы в материалы и тестуры формата Direct3D. Для этого материалы и текстуры копируются из буфера материалов в отдельные (individual) массивы материалов и текстур:

D3DXMATERIAL* d3dxMaterials = (LPD3DXMATERIAL)matbuffer->GetBufferPointer();
model->materials = new D3DMATERIAL9[model->material_count];
model->textures = new LPDIRECT3DTEXTURE9[model->material_count];

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

  • цвет рассеянного (ambient) света;
  • текстура (загружается в объект текстуры).

Так как оба пункта представляют собой массивы, объём каждой можели ограничен лишь объёмом памяти ПК и возможностями видеокарты. Теоретически ты можешь рендерить (средствами DirectX) модель с миллионами граней, каждой из которых присвоен свой собственный материал.
Вот пример кода создания массива материалов:

for(i=0; i < model->material_count; i++)
{
 // Получаем материал
 model->materials[i] = d3dxMaterials[i].MatD3D;

 // Назначаем цвет рассеянного (ambient) света
 model->materials[i].Ambient = model->materials[i].Diffuse;

 model->textures[i] = NULL;
 if(d3dxMaterials[i].pTextureFilename != NULL && lstrlen(d3dxMaterials[i].pTextureFilename) > 0)
 {
  // Загружаем текстуру, указанную в .X-файле
  result = D3DXCreateTextureFromFile(d3ddev. d3dxMaterials[i].pTextureFilename, &model->textures[i]);
 }
}

Рендерим модель

Сперва модели назначается материал и текстура. Затем вызывается функция DrawPrimitive или DrawSubset. Здесь сложность в том, что необходимо итерировать (=опрашивать) модель и рендерить каждую грань отдельно, с учётом счётчика material_count. Вот пример:

for(i=0; i < model->material_count; i++)
{
 d3ddev->SetMaterial(&maodel->materials[i]);
 d3ddev->SetTexture(0, model->textures[i]);
 model->mesh->DrawSubset(i);
}

Пример приложения, загружающего 3D-модель из .X-файла.

Создадим Проект приложения, которое загружает с диска .X-файл и выводит содержащуюся в нём 3D-модель на экран. За основу возьмём исходный код из статьи DirectX Graphics (DirectX 9). Начало работы (финальный вариант Проекта D3D9Init01 в конце статьи), который, в свою очередь, основан на исходном коде базового приложения Windows из статьи Создание приложений (Cpp, Win32)). В этом примере мы продолжим размещать исходный код в отдельных файлах, увеличивая его модульность и тем самым повышая удобочитаемость и юзабилити.

Создаём Проект приложения DX9LoadMesh01

  • Создай пустой Проект с именем DX9LoadMesh01. Проект автоматически разместится внутри Решения с таким же именем.

Весь процесс подробно расписан в статьях

Добавляем в Проект файл исходного кода WinMain.cpp

В Проекте DX9LoadMesh01 пока нет файлов. Ведь это пустой Проект. Создадим основной файл исходного кода WinMain.cpp, где разместим базовые функции создания окна + вызовы функций движка (GameInit, GameRun, GameEnd).

  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта DX9LoadMesh01.
  • Во всплывающем меню Добавить->Создать элемент...
Добавляем исходный файл
Добавляем исходный файл
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp.
  • Жмём "Добавить".

Image
Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле WinMain.cpp набираем код:
WinMain.cpp
// Файл: WinMain.cpp 
// Базовый исходник приложения, реализующий функции для работы с окнами.
// www.igrocoder.ru 2021
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include "game.h"

// Прототип функции WinMain
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow);
// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
	CoInitialize(NULL);

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно белым цветом
	wndClass.lpszMenuName=NULL;		// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",			// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW, // Основной стиль окна
		0, 0,					// Координаты X и Y
		iWidth,	// Ширина окна
		iHeight,	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		wndClass.hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

  if(!hWnd)
    return FALSE;

  ShowWindow(hWnd, SW_NORMAL);
  UpdateWindow(hWnd);

  // Очищаем структуру сообщения
  ZeroMemory(&msg, sizeof(MSG));

  if(GameInit(hWnd) == TRUE) 
  {
	  while (true) 
	  {
		  if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) 
		  {
			  TranslateMessage(&msg);
			  DispatchMessage(&msg);
			  if (msg.message == WM_QUIT) break;
		  }
		  else
		  {
		   // Главный цикл программы (mainloop)
		   GameRun(hWnd);
		  }
	  }
  }

    CoUninitialize();

  //Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		GameEnd(hWnd);
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		return 0;
	}
		// В случае любых других сообщений...
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

В начале листинга в секции include-ссылок видим подключение следующих заголовков:

  • windows.h Предоставляет базовые WINAPI-функции создания окон и работы с сообщениями. Впервые появился в MS Visual Studio ещё в конце 90-х годов XX века. В начале 2000-х у него появился более расширенный "собрат" windowsx.h. Но в экспериментальных целях мы выведем DirectX-графику путём применения "хардкорной" классики.
  • game.h Этот заголовок мы создадим ниже. В нём разместятся ссылки на другие заголовочные файлы, реализующие (в перспективе) различный функционал движка (графика, звук, ввод и т.д.). Представляет собой реализацию концепции единой точки контакта (основопологающей в игрокодинге).

Добавляем в Проект заголовочный файл dxgraphics.h

dxfunc.h содержит объявления DirectX-функций, реализованных в dxgraphics.cpp.
ОК, приступаем.

  • Убедись, что MSVC++2010 запущена и в ней открыт Проект DX9LoadMesh01, созданный выше.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы" Проекта DX9LoadMesh01.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "dxgraphics.h".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле dxgraphics.h набираем следующий код:
dxfunc.h
#define D3DFVF_MYVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)

#ifndef _DXFUNC_H_
#define _DXFUNC_H_

#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>

// Прототипы функций
HRESULT d3d9Init(IDirect3D9 **d3d, 
                IDirect3DDevice9 **d3ddev,
                HWND hWnd, 
				DWORD iWidth,
				DWORD iHeight,
				BOOL bFullScreen
				);
LPDIRECT3DSURFACE9 LoadSurface(char *, D3DCOLOR);
LPDIRECT3DTEXTURE9 LoadTexture(char *, D3DCOLOR);

struct VERTEX
{
	float x, y, z;
	float tu, tv;
};

struct QUAD
{
	VERTEX vertices[4];
	LPDIRECT3DVERTEXBUFFER9 buffer;
	LPDIRECT3DTEXTURE9 texture;
};

void SetPosition(QUAD*, int, float, float, float);
void SetVertex(QUAD*, int, float, float, float, float, float);
VERTEX CreateVertex(float, float, float, float, float);
QUAD* CreateQuad(char*);
void DeleteQuad(QUAD*);
void DrawQuad(QUAD*);
void SetIdentity();
void SetCamera(float, float, float, float, float, float);
void SetPerspective(float, float, float, float);
void ClearScene(D3DXCOLOR);

// Объявления переменных
extern LPDIRECT3D9 d3d;
extern LPDIRECT3DDEVICE9 d3ddev;
extern LPDIRECT3DSURFACE9 backbuffer;

#endif
  • Сохрани Решение (Файл->Сохранить все).

По мере "обрастания" движка новым функционалом список подключаемых здесь заголовков будет увеличиваться. Заголовок d3dx9math.h нужен для матричных вычислений положения камеры в сцене.
Для кода инициализации Direct3D здесь объявлена отдельная функция d3d9Init аж с шестью вводными параметрами.3 Первые 5 мы уже применяли выше. Шестой - булево значение, отслеживающее использование полноэкранного режима. При bFullScreen=TRUE приложение работает в полноэкранном режиме.
В конце листинга видим объявления переменных Direct3D и объекта устройства Direct3D. Перед каждым стоит служебное слово extern, разрешающее их применение во всех исходниках данного Проекта.

Добавляем в Проект файл исходного кода dxgraphics.cpp

В заголовочном файле dxfunc.cpp содержатся реализации DirectX-функций, объявленных в dxgraphics.h.
ОК, приступаем.

  • Убедись, что MSVC++2010 запущена и вней открыт Проект DX9LoadMesh01, созданный выше.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта DX9LoadMesh01.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Файл C++ (.cpp)" и в поле "Имя" введи "dxgraphics.cpp".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле dxfunc.cpp набираем следующий код:
dxgraphics.cpp
// Файл: dxgraphics.cpp
// Реализация DX-функций, объявленных в dxgraphics.h.
// Сборник DirectX-функций

#include "dxgraphics.h"

// Объявления переменных
LPDIRECT3D9 d3d = NULL;
LPDIRECT3DDEVICE9 d3ddev = NULL;
LPDIRECT3DSURFACE9 backbuffer = NULL;

HRESULT d3d9Init(IDirect3D9 **d3d, 
                IDirect3DDevice9 **d3ddev,
                HWND hWnd, 
				DWORD iWidth,
				DWORD iHeight,
				BOOL bFullScreen)
{
  // Инициализируем Direct3D
 if(( *d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
 {
	return E_FAIL;
 }

 // Заполняем основные (общие) параметры представления
  D3DPRESENT_PARAMETERS d3dpp;
  ZeroMemory( &d3dpp, sizeof( d3dpp ) );

  d3dpp.BackBufferWidth = iWidth;
  d3dpp.BackBufferHeight = iHeight;
  d3dpp.EnableAutoDepthStencil = TRUE;
  d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
  d3dpp.hDeviceWindow = hWnd;


  // Запрос на отображение в полноэкранном режиме
  int  iRes;
  if (!bFullScreen)
      iRes=MessageBox(hWnd, "Перейти в полноэкранный режим?", "Screen", MB_YESNO | MB_ICONQUESTION);
  else
	  iRes = IDYES;

  if(iRes == IDYES)
  {
      //////////////////////////////////////////////////////////
	  // Полноэкранный режим
      //////////////////////////////////////////////////////////
	  // Установка параметров полноэкранного режима
	  d3dpp.Windowed         = FALSE;
	  d3dpp.BackBufferCount = 1;
      d3dpp.BackBufferFormat = D3DFMT_R5G6B5;
	  d3dpp.SwapEffect       = D3DSWAPEFFECT_FLIP;
      d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
      d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;  // Direct3D сам выбирает частоту показа.
	  // Обычно она равна частоте кадров.
  } 
  else 
  {
  // Доп. параметры для оконного режима
  // Получить формат пикселя
  D3DDISPLAYMODE d3ddm;
  (*d3d)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

  // Установка параметров оконного режима
  d3dpp.Windowed         = TRUE;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.SwapEffect       = D3DSWAPEFFECT_DISCARD;
  }

 if(FAILED((*d3d)->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, d3ddev)))
 {
	 return E_FAIL;
 }

 // Очищаем бэкбуфер в чёрный цвет
 (*d3ddev)->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

 // Создаём указатель на бэкбуфер
 (*d3ddev)->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
}

LPDIRECT3DSURFACE9 LoadSurface(char *filename, D3DCOLOR transcolor)
{
	LPDIRECT3DSURFACE9 image = NULL;
	D3DXIMAGE_INFO info;
	HRESULT result;

	// Получаем из файла длину и высоту изображения
	result = D3DXGetImageInfoFromFile(filename, &info);
	if(result != D3D_OK)
		return NULL;

	// Создаём поверхность
	result = d3ddev->CreateOffscreenPlainSurface(
		info.Width,  // Ширина изображения
		info.Height,  // Высота изображения
		D3DFMT_X8R8G8B8,  // Формат цветности поверхности
		D3DPOOL_DEFAULT,  // Используемый пул памяти
		&image,  // Указатель на поверхность
		NULL);  // Зарезервирован (всегда NULL)

	if(result != D3D_OK)
		return NULL;

	// Загружаем изображение из файла в созданную поверхность
	result = D3DXLoadSurfaceFromFile(
		image,  // Поверхность-получатель
		NULL,  // Палитра-получатель
		NULL,  // Прямоугольник-получатель
		filename,  // Имя файла-источника
		NULL,  // Прямоугольник-источник
		D3DX_DEFAULT,  // Метод фильтрации изображения
		transcolor,  // Цвет прозрачности (0 если без неё)
		NULL);  // Инфо о файле изображения (обычно NULL)

	if(result != D3D_OK)
		return NULL;

	return image;
}

LPDIRECT3DTEXTURE9 LoadTexture(char *filename, D3DCOLOR transcolor)
{
	// Указатель на текстуру
	LPDIRECT3DTEXTURE9 texture = NULL;

	// Структура, куда сохраним инфу о файле растрового изображения (=битмапе)
	D3DXIMAGE_INFO info;

	// Стандартное значение, возвразаемое в ОС Windows
	HRESULT result;

	// Получаем ширину и высоту изображения из битмапа
	result = D3DXGetImageInfoFromFile(filename, &info);
	if(result != D3D_OK)
		return NULL;

	// Создаём текстуру путём загрузки битмапа
	D3DXCreateTextureFromFileEx(
		d3ddev,  // Объект устройства Direct3D
		filename,  // Имя файла битмапа
		info.Width,  // Ширина битмапа
		info.Height,  // Высота битмапа
		1,  // Кол-во мипмап-уровней (1 - значит без цепочки)
		D3DPOOL_DEFAULT,  // Тип поверхности (стандартная)
		D3DFMT_UNKNOWN,  // Формат цветности поверхности (по умолчанию)
		D3DPOOL_DEFAULT,  // Пул памяти для текстуры
		D3DX_DEFAULT,  // Фильтр изображения
		D3DX_DEFAULT,  // Мип-фильтр
		transcolor,  // Ключевой цвет для прозрачности
		&info,  // Инфо о битмапе (загружено выше из файла битмапа)
		NULL,  // Цветовая палитра
		&texture  // Указатель на конечный объект текстуры
		);

	// Проверяем, что битмап успешно загружен
	if(result != D3D_OK)
		return NULL;

	return texture;
}

void SetPosition(QUAD *quad, int ivert, float x, float y, float z)
{
	quad->vertices[ivert].x = x;
	quad->vertices[ivert].y = y;
	quad->vertices[ivert].z = z;
}

void SetVertex(QUAD *quad, int ivert, float x, float y, float z, float tu, float tv)
{
	SetPosition(quad, ivert, x , y, z);
	quad->vertices[ivert].tu = tu;
	quad->vertices[ivert].tv = tv;
}

VERTEX CreateVertex(float x, float y, float z, float tu, float tv)
{
	VERTEX vertex;
	vertex.x = x;
	vertex.y = y;
	vertex.z = z;
	vertex.tu = tu;
	vertex.tv = tv;
	return vertex;
}

QUAD *CreateQuad(char *textureFilename)
{
	QUAD *quad = (QUAD*)malloc(sizeof(QUAD));
	// Загружаем текстуру
	D3DXCreateTextureFromFile(d3ddev, textureFilename, &quad->texture);

	// Создаём для данного квада вершинный буфер
	d3ddev->CreateVertexBuffer(
		4*sizeof(VERTEX),
		0,
		D3DFVF_MYVERTEX, D3DPOOL_DEFAULT,
		&quad->buffer,
		NULL);

	// Создаём 4 угла этой "ленты", состоящей из двух треугольников.
	// У каждой вершины есть координаты X, Y, Z и текстурные координаты U, V
	quad->vertices[0] = CreateVertex(-1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
	quad->vertices[1] = CreateVertex(1.0f, 1.0f, 0.0f, 1.0f, 0.0f);
	quad->vertices[2] = CreateVertex(-1.0f, -1.0f, 0.0f, 0.0f, 1.0f);
	quad->vertices[3] = CreateVertex(1.0f, -1.0f, 0.0f, 1.0f, 1.0f);

	return quad;
}

void DeleteQuad(QUAD *quad)
{
	if(quad == NULL)
		return;

	// Особождаем вершинный буфер
	if(quad->buffer != NULL)
		quad->buffer->Release();

	// Особождаем память, ранее выделенную под текстуру
	if(quad->texture != NULL)
		quad->texture->Release();

	// Удаляем квад
	free(quad);
}

void DrawQuad(QUAD *quad)
{
	// Заполняем вершинный буфер вершинами квада
	void *temp = NULL;
	quad->buffer->Lock(0, sizeof(quad->vertices), (void**)&temp, 0);
	memcpy(temp, quad->vertices, sizeof(quad->vertices));
	quad->buffer->Unlock();

	// Рисуем треугольную ленту с текстурой, состоящую из двух треугольников
	d3ddev->SetTexture(0, quad->texture);
	d3ddev->SetStreamSource(0, quad->buffer, 0, sizeof(VERTEX));
	d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}

void SetIdentity()
{
	// Устанавливаем положение, масштаб и вращение по умолчанию
	D3DXMATRIX matWorld;
	D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f);
	d3ddev->SetTransform(D3DTS_WORLD, &matWorld);
}

void ClearScene(D3DXCOLOR color)
{
	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, color, 1.0f, 0);
}

void SetCamera(float x, float y, float z, float lookx, float looky, float lookz)
{
	D3DXMATRIX matView;
	D3DXVECTOR3 updir(0.0f, 1.0f, 0.0f);

	// Перемещения камеры
	D3DXVECTOR3 cameraSource;
	cameraSource.x = x;
	cameraSource.y = y;
	cameraSource.z = z;

	// Точка направления взгляда камеры
	D3DXVECTOR3 cameraTarget;
	cameraTarget.x = lookx;
	cameraTarget.y = looky;
	cameraTarget.z = lookz;

	// Настраиваем матрицу вида (view matrix) камеры
	D3DXMatrixLookAtLH(&matView, &cameraSource, &cameraTarget, &updir);
	d3ddev->SetTransform(D3DTS_VIEW, &matView);
}

void SetPerspective(float fieldOfView, float aspectRatio, float nearRange, float farRange)
{
	// Устанавливаем перспективу, чтобы объекты в кадре выглядели не такими большими
	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovLH(&matProj, fieldOfView, aspectRatio, nearRange, farRange);
	d3ddev->SetTransform(D3DTS_PROJECTION, &matProj);
}
  • Сохрани Решение (Файл->Сохранить все).

Естесственно, в начале листинга подключаем заголовок dxgraphics.h, где размещены объявления DirectX-функций.
В листинге dxgraphics.cpp нет ни одного вызова функции. Только реализации. Все эти функции вызываются из других внешних файлов (в основном WinMain.cpp и Game.cpp, который создадим ниже). dxgraphics.cpp служит для хранения реализаций служебных DirectX-функций. В перспективе можно создать и другие пары файлов .h и .cpp с произвольными именами, соответствующие добавляемому функционалу (dxaudio.h, dxinput.h и т.д.).
Ниже видим огромный блок реализации функции d3d9Init. В нём:

  • Инициализируется Direct3D
  • Заполняются параметры представления (в экземпляре структуры D3DPRESENT_PARAMETERS).

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

  • На основе заполненной структуры параметров представления создаётся объект устройства Direct3D.

Добавляем в Проект заголовочный файл Game.h

Game.h содержит объявления функций, специфичных для игры, реализованных в Game.cpp.
ОК, приступаем.

  • Убедись, что MSVC++2010 запущена и в ней открыт Проект DX9LoadMesh01, созданный выше.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы" Проекта DX9LoadMesh01.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "Game.h".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле dxfunc.h набираем следующий код:
Game.h
// Файл: Game.h 
// Объявления функций, специфичных для игры, реализации которых
// размещены в Game.cpp

#ifndef _GAME_H_
#define _GAME_H_

#include "dxgraphics.h" // Для инициализации Direct3D + подключения d3d9.h

// Заголовок приложения (выводится на тулбаре окна)
#define APPTITLE "Loading .X-file demo"

// Настройки видеорежима
#define FULLSCREEN 1
#define iWidth 640
#define iHeight 480

// Макрос асинхронного считывания нажатий клавиатуры (без Direct Input)
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1:0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1:0)

// Прототипы игровых функций
bool GameInit(HWND);  // Инициализирует игру.
void GameRun(HWND);
void GameEnd(HWND);

#endif
  • Сохрани Решение (Файл->Сохранить все).

В начале листинга присваиваем значения переменным заголовка окна (APPTITLE; подставляется в WinMain.cpp при создании окна), iWidth и iHeight, устанавливающим начальные значения ширины и высоты окна.
Ниже видим пару макросов асинхронного считывания нажатий клавиатуры средствами WinAPI. Нужны для закрытия окна приложения по нажатию Esc на клавиатуре.
Вообще, достаточно лишь одной строки #define KEY_DOWN... Но пусть будут обе.
Ниже видим триаду функций главного игрового цикла: GameInit (инициализирует игру), GameRun (рисует графику, выводит звук и т.д.) и GameEnd (удаляет ресурсы из памяти при завершении работы приложения.

Добавляем в Проект файл исходного кода Game.cpp

В заголовочном файле Game.cpp содержатся реализации функций, специфичных для игры, объявленных в Game.h.
ОК, приступаем.

  • Убедись, что MSVC++2010 запущена и вней открыт Проект DX9LoadMesh01, созданный выше.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта DX9LoadMesh01.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Файл C++ (.cpp)" и в поле "Имя" введи "Game.cpp".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле Game.cpp набираем следующий код:
Game.cpp
// Файл: Game.cpp 
// Реализация функций, специфичных для игры, объявленных в Game.h

#include "Game.h"

#define WHITE D3DCOLOR_ARGB(0, 255, 255, 255)
#define BLACK D3DCOLOR_ARGB(0, 0, 0, 0)

// Координаты камеры вынесены в определения define. Вообще это лишь начальные значения переменных,
// которые позднее можно изменить, в том числе с помощью пользовательского ввода.
// Отдаляем камеру по оси X на 20 единиц, чтобы объект целиком входил в кадр.
#define CAMERA_X -20.0f
#define CAMERA_Y 4.0f
#define CAMERA_Z 7.0f

HRESULT result;

// Определяем структуру MODEL
struct MODEL
{
	LPD3DXMESH mesh;
	D3DMATERIAL9* materials;
	LPDIRECT3DTEXTURE9* textures;
	DWORD material_count;
};

MODEL *car;

MODEL *LoadModel(char *filename)
{
	MODEL *model = (MODEL*)malloc(sizeof(MODEL));
	LPD3DXBUFFER matbuffer;
	HRESULT result;

	// Загружаем меш из указанного .X-файла
	result = D3DXLoadMeshFromX(
		filename,  // Имя файла
		D3DXMESH_SYSTEMMEM,  // Опции меша
		d3ddev,  // Объект устройства Direct3D
		NULL,  // Буфер смежных вершин (adjacency buffer)
		&matbuffer,  // Буфер материалов
		NULL,  // Спецэффекты
		&model->material_count,  // Кол-во материалов модели
		&model->mesh  // Результирующий меш
		);

	if(result != D3D_OK)
	{
		MessageBox(NULL, "Ошибка выполнения функции D3DXLoadMeshFromX", "Error", MB_OK);
		return NULL;
	}

	// Извлекаем из буфера материалов свойства материала и имена текстур
	D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)matbuffer->GetBufferPointer();
	model->materials = new D3DMATERIAL9[model->material_count];
	model->textures = new LPDIRECT3DTEXTURE9[model->material_count];

	// Создаём материалы и текстуры модели
	for(DWORD i=0; i < model->material_count; i++)
	{
		// Выбираем материал
		model->materials[i] = d3dxMaterials[i].MatD3D;

		// Назначаем цвет рассеянного (ambient) света
		model->materials[i].Ambient = model->materials[i].Diffuse;

		model->textures[i] = NULL;
		if(d3dxMaterials[i].pTextureFilename != NULL && lstrlen(d3dxMaterials[i].pTextureFilename) > 0)
		{
			// Загружаем файл текстуры, указанный в .X-файле
			result = D3DXCreateTextureFromFile(d3ddev, d3dxMaterials[i].pTextureFilename, &model->textures[i]);

			if(result != D3D_OK)
			{
				MessageBox(NULL, "Не могу найти файл текстуры", "Error", MB_OK);
				return NULL;
			}
		}
	}

	// Освобождаем буфер материала т.к. больше не нужен
	matbuffer->Release();

	return model;
}

void DeleteModel(MODEL *model)
{
	// Освобождаем память от материалов
	if(model->materials != NULL)
		delete[] model->materials;

	// Освобождаем память от текстур
	if (model->textures != NULL)
	{
		for(DWORD i=0; i < model->material_count; i++)
		{
			if(model->textures[i] != NULL)
				model->textures[i]->Release();
		}
		delete[] model->textures;
	}

	// Удаляем меш (сетку) модели из памяти
	if(model->mesh != NULL)
		model->mesh->Release();

	// Удаляем структуру модели из памяти
	if(model != NULL)
		free(model);
}

void DrawModel(MODEL *model)
{
	// Рисуем каждый субсет меша
	for(DWORD i=0; i<model->material_count; i++)
	{
		// Назначаем для субсета материал и текстуру
		d3ddev->SetMaterial(&model->materials[i]);
		d3ddev->SetTexture(0, model->textures[i]);

		// Рисуем субсет меша
		model->mesh->DrawSubset(i);
	}
}

// Инициализация игры
bool GameInit(HWND hWnd)
{
	if (d3d9Init(&d3d, &d3ddev, hWnd, iWidth, iHeight, FALSE)!=S_OK)
	{
	  MessageBox(hWnd, "DirectX Initialize Error", "Error", MB_OK);
	  return FALSE;
	}

	// Устанавливаем камеру и перспективу
	SetCamera(CAMERA_X, CAMERA_Y, CAMERA_Z, 0, 0, 0); // Начальные координаты камеры определены в начале листинга.
	float ratio = (float)iWidth/(float)iHeight;
	SetPerspective(45.0f, ratio, 0.1f, 1000.0f);

	// Включаем рассеянное (ambient) освещение и Z-буферизацию
	d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);
	d3ddev->SetRenderState(D3DRS_AMBIENT, WHITE);

	car = LoadModel("car.x");
	if(car == NULL)
	{
		MessageBox(hWnd, "Ошибка загрузки .X-файла", "Error", MB_OK);
		return 0;
	}

	// Если выполнение дошло до сюда, то возвращаем ОК
	return 1;
}

// Главный цикл игрового приложения
void GameRun(HWND hWnd)
{
	// Проверяем корректность созданного объекта устройства Direct3D
	if(d3ddev == NULL)
		return;

	ClearScene(BLACK);

	// Начинаем рендеринг
	if(d3ddev->BeginScene())
	{
		// Вращаем вид
		D3DXMATRIX matWorld;
		D3DXMatrixRotationY(&matWorld, timeGetTime()/1000.0f);
		d3ddev->SetTransform(D3DTS_WORLD, &matWorld);

		// Рисуем модель машины
		DrawModel(car);

		// Завершаем рендеринг
		d3ddev->EndScene();
	}

	// Выводим содержимое бэкбуфера на экран
	d3ddev->Present(NULL, NULL, NULL, NULL);

	// При нажатии Esc завершаем работу приложения
	if(KEY_DOWN(VK_ESCAPE))
		PostMessage(hWnd, WM_DESTROY, 0, 0);
}

// Освобождаем память, стирая ранее выделененые ресурсы
void GameEnd(HWND hWnd)
{
	DeleteModel(car);
}
  • Сохрани Решение (Файл->Сохранить все).

Многие из этих функций являются авторскими функциями-обёртками (wrapper functions) DirectX-функций объявленных в dxgraphics.h. include-ссылка на данный заголовок есть в Game.h, а Game.h, в свою очередь, подключен оператором include в начале листинга Game.cpp.
В начале листинга определены словесные (=символьные) переменные для координат камеры (CAMERA_X, CAMERA_Y, CAMERA_Z). Они активно юзаются здесь же в функции GameInit и других функциях по работе с камерой.
Кастомная (пользовательская) структура MODEL хранит в себе данные о сетке модели, материалах и текстурах.
Авторская функция LoadModel является функцией-обёрткой DirectX-функции D3DXLoadMeshFromX, загружающей меш из .X-файла. Здесь же с помощью функции D3DXCreateTextureFromFile на основе данных из .X-файла создаётся текстура модели.
В авторской функции-обёртке DrawModel размещены функции отрисовки модели и в частности вызов DirectX-функции DrawSubset.
В авторской функции-обёртке GameInit:

  • Инициализируется Direct3D (путём вызова такой же авторской функции-обёртки d3d9Init).
  • Устанавливается камера (SetCamera) + её перспектива (= направление взора, фокусное расстояние; SetPerspective).
  • Включается фоновое освещение сцены и Z-буферизация.
  • Загружается модель из файла car.x (путём вызова авторской функции-обёртки LoadModel).

В авторской функции-обёртке GameRun:

  • Очищаем сцену объекта устройства Direct3D (ClearScene).
  • Рендерим содержимое бэкбуфера между вызовами функций BeginScene и EndScene.
  • Выводим содержимое бэкбуфер на экран (Present).

Здесь же прописан выход из приложения по нажатию Esc на клавиатуре.
В авторской функции-обёртке GameEnd:

  • Удаляем объект MODEL.

Готовим Проект DX9LoadMesh01 к компиляции

Весь код написан. Но перед компиляцией Преокт надо сперва грамотно настроить.

Устанавливаем многобайтовую кодировку (multibyte encoding)

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...

  • В Обозревателе решений щёлкаем по названию только что созданного Проекта DX9LoadMesh01.
  • Во всплывающем меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём "Свойства конфигурации", в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
  • Жмём ОК.

Image

Отключаем инкрементную компоновку (incremental linking)

Иначе при копиляции возможны ошибки вроде этой:

Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден
  • В Обозревателе решений щёлкаем по названию только что созданного Проекта DX9LoadMesh01.
  • Во всплывающем меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение "Нет (/INCREMENTAL:NO)".
  • Жмём ОК.
  • Сохрани Решение (File -> Save All).

Указываем пути к DirectX SDK 9

Если попытаться скомпилировать Проект DX9LoadMesh01 в таком виде, то ничего не выйдет.
В dxgraphics.h стоит директивы включения (#include) заголовочных файлов DirectX:

Фрагмент dxgraphics
...
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>
...

На данном этапе MSVC++2010 ничего не знает о его местоположении. В статье Настройка MS Visual C plus plus 2010 и DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Проделаем те же операции для нашего Проекта DX9LoadMesh01.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект DX9LoadMesh01.
  • Убедись, что DirectX SDK 9 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.

В Обозревателе решений видим: "Решение "DX9LoadMesh01"", а строкой ниже жирным шрифтом название Проекта (тоже DX9LoadMesh01).

  • Жмём правой кнопкой мыши по названию Проекта DX9LoadMesh01. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.

Image

  • В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.


Указываем каталог включений (include) DirectX SDK 9

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."

Image

  • В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным файлам DirectX SDK 9 (include). В нашем случае это C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Жмём "ОК".


Указываем каталог библиотек (lib) DirectX SDK 9

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек DirectX SDK 9 (lib). В нашем случае это C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
  • Жмём "ОК".

Image

  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).

Готово.

Указываем пути к Windows SDK

Помимо указания путей к заголовкам (include) и библиотекам (lib) DirectX SDK, для любого DirectX-Проекта также необходимо казать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней (недоступной для редактирования) части видим список "Унаследованные значения", где в третьей строке стоит значение:

$(WindowsSdkDir)include

В результате видим, что добавленные пути к DirectX SDK (в обоих окнах: include и lib) расположены вверху списка, а пути к Windows SDK - в недоступной области, на несколько строк ниже.
Но заголовочным файлам DirectX SDK 9 "жизненно важно", чтобы они включались после включений заголовков Windows SDK.

Закрыть
noteВажно!

Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей надо разместить пути к Windows SDK выше, а к DirectX SDK - ниже по списку (чтобы они считывались последними).

В данной ситуации мы не можем поднять пути к Windows SDK, прописанные по умолчанию при создании Проекта, т.к. они расположены в специальной нередактируемой области (ограничение бесплатной версии MSVC++2010 Express).
Но можем схитрить и добавить ещё раз пути к тем же самым каталогам Windows SDK, подняв эти строки выше строк DirectX SDK.
Выше мы указывали пути к DirectX SDK 9. Для путей к Windows SDK это делается аналогично. Начнём.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект DX9LoadMesh01.
  • Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.

В Обозревателе решений видим: "Решение "DX9LoadMesh01"", а строкой ниже жирным шрифтом название Проекта (тоже DX9LoadMesh01).

  • Жмём правой кнопкой мыши по названию Проекта DX9LoadMesh01. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.

Image

  • В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++. В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.


Указываем каталог включений (include) Windows SDK

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."

Image

  • В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Меняй порядок считывания каталогов включений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".

Каталог включений DirectX SDK (9) должен всегда стоять в самом конце списка, как на этом скриншоте:
Image

  • Жмём "ОК".


Указываем каталог библиотек (lib) Windows SDK

  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.

Image

  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".

Каталог библиотек DirectX SDK (9) должен всегда стоять в самом конце списка, как на этом скриншоте:
Image

  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).

Готово.

Прописываем библиотеки d3d9.lib, d3dx9.lib и Winmm.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)

Т.к. мы создаём исполняемое приложение (исполняемый .exe-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (=linker). Так вот, этот самый компоновщик по ранее прописанным каталогам нужные библиотеки не ищет. Поэтому данные библиотеки необходимо явно указывать в списке дополнительных зависимостей линкера (=компоновщика). Причём в дальнейшем, по мере роста функционала движка, не только их.
ОК, начинаем.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект DX9LoadMesh01.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Ввод (Configuration Properties -> Linker -> Input).
  • В правой части, напротив строки "Дополнительные зависимости" жмём кнопку с чёрным треугольником.
  • В всплывающем списке жмём "Изменить".
  • В появившемся окне "Дополнительные зависимости" в верхнем поле ввода прописываем имя требуемой библиотеки:

d3d9.lib
d3dx9.lib
Winmm.lib

  • Жмём ОК, ОК.
  • Сохрани Решение (Файл->Сохранить все).

Отключаем использование компоновщиком библиотеки libci.dll

Да, даже на данном этапе при компиляции Проект DX9LoadMesh01 выдаст ошибку. Библиотека libci.dll использовалась в VisualStudio когда-то очень давно. И в современных версях IDE её нет. Тем не менее компоновщик почти всегда вызывает её при компиляции, ругаясь на её отсутствие. Самый простой способ это исправить - запретить использовать libci.dll по умолчанию.
ОК, начнём.

  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект DX9LoadMesh01.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Командная строка (Configuration Properties -> Linker -> Command Promt).

В правой части, внизу, видим поле ввода "Дополнительные параметры".

  • Пишем в него строку: /NODEFAULTLIB:libci
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект DX9LoadMesh01

  • Скомпилируй Проект/Решение (кнопка с зелёным треугольником на Панели инструментов MSVC++ или <F5> на клавиатуре).

Image
Если весь код был введён без ошибок, после компиляции запустится приложение, отображающее окно с белым фоном. Исходный размер окна задан в переменных iWidth и iHeight (WinMain.cpp) и составляет 800х600 точек.

Закрыть
noteОбрати внимание

Если попытаться запустить скомпилированный .exe-файл на компьютере без установленной MSVC++2010, то экзешник (скорее всего) "ругнётся" на отсутствующую библиотеку MSVCR100D.dll. Буква D в её имени означает Debug, т.е. данное приложение скомпилировано с отладочной конфигурацией (профилем), которая выставляется по умолчанию у каждого вновь создаваемого Проекта/Решения. При релизе игры приложение напротив, компилируют с конфигурацией Release (релиз). При этом при создании инсталлятора в дистрибутив с игрой добавляют т.н. набор библиотек времени выполнения (в нашем слчае MS Visual C++ 2010 Runtime Redistributable). Обычно он устанавливается вместе с игрой, т.к. без него игра тупо не стартанёт. Если для Release-профиля такой набор нетрудно найти даже на сайте Microsoft (например здесь: https://www.microsoft.com/en-us/download/details.aspx?id=5555(external link), 4,8 Мб для 64-разрядной версии ОС), то для запуска Debug-версии на отдельном компе на него потребуется установить целую MSVC++2010. Всё так сложно в том числе с целью не допустить утечек предрелизных разработок с компов игрокодерских компаний. Релиз есть релиз. А с дебаг-версиями, как правило, работают только сами игрокодеры, на своих компах отлавливая ошибки.
Конфигурации Debug и Release легко переключаются на странице свойств открытого Проекта (в MSVC++2010 в главном меню выбираем Проект->Свойства, в верхней части диалога видим всплывающий список "Конфигурация"). В Интернете также распространены специальные паки Release-наборов библиотек для всех версий MSVC++.

Программа ругнётся на отсутствующий .X-файл.

Готовим .X-файл

В Game.cpp есть такой код:

Фргамент Game.cpp
... 
	car = LoadModel("car.x");
	if(car == NULL)
	{
		MessageBox(hWnd, "Ошибка загрузки .X-файла", "Error", MB_OK);
		return 0;
	}
...

Здесь загружается модель из .X-файла car.x. По умолчанию программа ищет данный файл в каталоге со своим исполняемым файлом.
Организуем .X-файл c 3D-моделью. Вот лишь несколько способов:

    • Найти в Интернете.

Это сложно, т.к. по запросу .X-файлы моделей ищется всё, что угодно, только не DirectX-файлы моделей. Куда проще найти файлы моделей формата .3ds (3D Studio Max) и грамотно сконвертировать их в .X-формат. По этой теме на Игрокодере есть хорошая статья Anim8or. Моделируем автомобиль и экспортируем в .X-файл. Здесь интересен только последний абзац "Конвертируем Car01.3ds в Car01.x с изменением масштаба".

    • Создать модель в любом 3D-редакторе, грамотно экспортировав его в файл формата .x .

Создание моделей в 3D-редакторе - сложная задача. Опять же, читай статью на Игрокодере Anim8or. Моделируем автомобиль и экспортируем в .X-файл. Или найди уроки по 3DS Max в Интернете. Напомним, вся соль заключается в грамотном конвертировании модели в формат .X. Под 3DS Max в Интернете есть несколько бесплатных плагинов-конвертеров. Установка, настройка и работа с одним из них (Panda X-converter) подробно описана в статье на Игрокодере 3D Studio Max 7 Установка и настройка экспорта .X-файлов.

    • Взять из примеров DirectX SDK.

Наверное самый простой способ. В недрах DX SDK 9 по пути по умолчанию c:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Samples\C++\Direct3D\Tutorials\Tut06_Meshes\ расположен файл модели tiger.x.
Когда получишь заветный .X-файл любым из способов:

  • Помести его в каталог с исполняемым файлом нашего приложения (в нашем случае путь до него такой: C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\DX9LoadMesh01\Debug).
  • Переименуй его в car.x .
  • Снова запусти приложение DX9LoadMesh01.exe .

На экране появится окно с запросом на переход в полноэкранный режим. При выборе любого варианта в окне появится вращающаяся 3D-модель из .X-файла.

Закрыть
noteОбрати внимание

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

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

Исходные коды примера (ZIP-архив с проектом для MSVC++2010) забираем здесь: https://disk.yandex.ru/d/TLBw0tXTkvCnNg(external link)

Заключение

Значение этой статьи для любого начинающего игрокодера трудно переоценить. 3D-графика - самый сложный элемент DirectX. Кроме того, в исходном коде примера мы разнесли код по авторским функциям-обёрткам с "говорящими" названиями. В WinMain.cpp прописаны вызовы основных трёх функций главного игрового цикла (GameInitm GameRun, GameEnd). Исходный код данного примера можно взять за основу при создании фреймворка игрового приложения/движка, добавив в него требуемый функционал. Конечно, данный подход не является единственно верным, но помогает сориентироваться в бездне различных авторских подходов, являя собой пример грамотно организованного каркаса будущей игры.

Источники:


1. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press. 2002
2. Harbour J.S. Beginning Game Programming. - Thomson Course Technology. 2005
3. Фленов М.Е. Искусство программирования игр на C++. - СПб.: БХВ-Петербург, 2006.

Contributors to this page: slymentat .
Последнее изменение страницы Пятница 21 / Май, 2021 13:18:32 MSK автор slymentat.

Помочь проекту

yoomoney.ru (бывший Яндекс-деньги): 410011791055108