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

DirectX Graphics (DirectX 9). Начало работы

  • Является одним из компонентов DirectX (версии 8 и более поздних), отвечающим за вывод на экран 2D и 3D-графики.1

Закрыть
noteЛюбопытный факт

В более ранних версиях DirectX компонент DirectX Graphics состоял из двух отдельных компонентов: Direct3D и DirectDraw.

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

В литературе по DirectX Graphics и в исходных кодах примеров можно встретить множество отсылок именно к Direct3D. Часто пишут Direct3D, подразумевая его более современный аналог DirectX Graphics. И наоборот. Таким образом часто (но не всегда) эти два названия синонимичны.


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

Содержание

Intro

Прежде чем начать работать с DirectX Graphics, его необходимо грамотно инициализировать. В нюансах процесс изменяется от версии к версии DirectX. Но суть остаётся та же.
Мы изучим применение бэкбуферов для получения на экране плавной анимации.
В конце данной статьи мы напишем простенькое Windows-приложение, которое:

    • инициализирует Direct3D,
    • создаёт объект устройства (device object) Direct3D.


Для компиляции примеров на понадобится:

    • MS Visual C++ 2010 Express,
    • Microsoft DirectX SDK 9.

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


Для успешного применения Direct3D, или любого другого компонента DirectX, важно знать, как правильно обращаться с подключаемыми библиотеками (libs; здесь размещаются сами функции) функций и их заголовочными файлами (headers; здесь размещаются интерфейсы вызова функций). Если кратко, то с каждой статичной библиотекой в наборе идёт заголовочный файл (с тем же именем), который лекго правится в текстовом редакторе и содержит вызовы функций, содержащихся в библиотеке. DirectX 9 хранит свои функции (бОльшую часть) в библиотеке d3d9.lib. И, чтобы программа "увидела" их, в одном из файлов исходного кода проекта (или заголовке) включают вот такую ссылку на заголовочный файл d3d9.h:

#include <d3d9.h>

Интерфейсы Direct3D (DirectX 9)

Для написания Direct3D-приложений необходимо сперва создать две переменные:

  • одну для интерфейса Direct3D (LPDIRECT3D9)
  • и одну для объекта графического устройства (graphic device; LPDIRECT3DDEVICE9).

Вот пример:

LPDIRECT3D9  d3d  = NULL;
LPDIRECT3DDEVICE9  d3ddev  = NULL;

Объект LPDIRECT3D9 является своеобразным "большим босом" библиотеки Diretct3D, т.к. он находится на вершине иерархии и контролирует абсолютно всё. Объект LPDIRECT3DDECICE9 контролирует лишь видеоадаптер компьютера. Префикс LP в их именах означает "long pointer" (англ. "длинный указатель"). Таким образом объект LPDIRECT3D9 является длинным указателем на объект Direct3D9. Это и многие другие определения описаны в заголовочном файле d3d9.h, что расположен в подкаталоге include установленного DirectX SDK (в нашем случае (Win7 x64): C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include). Вот его фрагмент:

Фрагмент d3d9.h
typedef struct IDirect3D9 *LPDIRECT3D9

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

  • IDirect3D9 - это сам интерфейс,
  • LPDIRECT3D9 - это длинный указатель на него.

По аналогии LPDIRECT3DDECICE9 является длинным указателем на интерфейс IDirect3DDevice9.

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

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

Создание объекта Direct3D (Direct3D device object, DirectX 9)

Главный (и обычно единственный) объект класса IDirect3D инициализируется так:

d3d = Direct3DCreate9(D3D_SDK_VERSION);

После этого созданный объект (=инстанс) становится полностью готовым к применению.
Сразу после этого создаём программный объект устройства (Direct3D device object), через который и будет рендериться вся графика. Здесь выходит на первый план переменная d3ddev, объявленная выше:

d3d->CreateDevice(
  D3DADAPTER_DEFAULT,  // Используем видеокарту, установленную в Windows по умолчанию (= в качестве основной).
  D3DDEVTYPE_HAL,  // Используем аппаратный рендеринг (Hardware Abstraction Layer).
  hWnd,  // Дескриптор окна
  D3DCREATE_SOFTWARE_VERTEXPROCESSING,  // Не использовать "фишки" T&L для лучшей совместимости с бОльшим числом видеоадаптеров.
  &d3dpp,  // Указатель на заранее заполненную структуру параметров отображения (Present Parameters; о ней чуть позже).
  &d3ddev);  // Указатель на создаваемый объект устройства Direct3D.

В последних двух параметрах стоит &d3dpp, указатель на заранее заполненную структуру параметров отображения (о ней ниже), и &d3ddev, собственно имя новосозданного объекта устройства.

Что такое T&L?

T&L (англ. Transform & Lighting - трансформация и освещение) - это аппаратная поддержка видеокартой манипуляций с полигонами + добавление к ним эффектов освещения (Не путать с RTX!).
Появление первых видеоадаптеров от компании 3Dfx произвело революцию на рынке компьютерных игр. Именно тогда принцип "хочешь играть - купи мощный компьютер" сменилось на "хочешь играть - купи видеокарту с 3D-ускорителем". Далее эстафету переняла NVIDIA, в начале 2000-ных явив миру T&L. Уже к 2002 году все более или менее игровые видеокарты шли с обязательной поддержкой T&L.
Суть технологии - перенести расчёт трансформаций полигонов и (часть) освещения с центрального процессора на видеокарту. При этом аппаратные (т.е. рассчитанные видеокартой) сцены как правило выглядят в разы эффектнее. Кроме того использование T&L значительно разгружает процессор (CPU), что позволяет загрузить его чем-нибудь ещё (например наделить ботов искусственным интеллектом или реализовать в игре крутую физику). Во многом благодаря этому современные игры стали в разы эффектнее.
Сегодня едва ли можно найти компьютер с видеокартой, не поддерживающей аппаратный T&L. Если, конечно, это не "жёсткий китай" с Али.
Так вот. DirectX 9 позволяет выбирать:

  • Аппаратную поддержку T&L (если поддерживается видеокартой);
  • Программно эмулировать T&L с переложением расчётов на процессор (CPU).

Это осуществляется в функции CreateDevice, путём указания в четвёртом параметре D3DCREATE_SOFTWARE_VERTEXPROCESSING (для программной эмуляции) либо D3DCREATE_HARDWARE_VERTEXPROCESSING (для включения аппаратного T&L).
В нашем случае для простоты изложения мы выбрали программную обработку вершин D3DCREATE_SOFTWARE_VERTEXPROCESSING.

Структура D3DPRESENT_PARAMETERS (DirectX 9)

  • Хранит параметры представления (=презентации) создаваемого объекта устройства (device object) IDirect3DDevice9.

Вот её шаблон:

Шаблон структуры D3DPRESENT_PARAMETERS (DirectX 9)
typedef struct _D3DPRESENT_PARAMETERS_ {
    UINT      BackBufferWidth;
    UINT      BackBufferHeight;
    D3DFORMAT BackBufferFormat;
    UINT      BackBufferCount;
    D3DMULTISAMPLE_TYPE MultiSampleType;
    DWORD     MultiSampleQuality;
    D3DSWAPEFFECT SwapEffect;
    HWND      hDeviceWindow;
    BOOL      Windowed;
    BOOL      EnableAutoDepthStencil;
    D3DFORMAT AutoDepthStencilFormat;
    DWORD     Flags;
    UINT      FullScreen_RefreshRateInHz;
    UINT      PresentationInterval;
} D3DPRESENT_PARAMETERS;

Вот описание её параметров:

Параметр Описание
BackBufferWidth Ширина бэкбуфера (в пикселах).
BackBufferHeight Высота бэкбуфера (в пикселах).
BackBufferFormat Формат цветности пикселей бэкбуфера. Например D3DFMT_R8G8B8 – 24 бит, по 8 бит на цвет (красный, зеленый и голубой).
BackBufferCount Кол-во бэкбуферов.
MultiSampleType Кол-во уровней мультисемплирования для полноэкранного сглаживания (antialiasing). Типичное значение здесь D3DMULTISAMPLE_NONE.
MultiSampleQuality Качество мультисемплирования. Типичное значение здесь 0.
SwapEffect Метод свопа (перелистывания, смены) бэкбуферов.
hDeviceWindow Родительское (=основное для вывода) окно для данного объекта устройства.
Windowed Булево значение, назначающее запуск в окне либо в полноэкранном режиме. Ставь TRUE для запуска игры в окне.
EnableAutoDepthStencil Булево значение, разрешающее Direct3D контролировать глубину экранных буферов. Обычно ставят в TRUE.
AutoDepthStencilFormat Формат цветности экранных буферов.
Flags Дополнительные флаги. Обычно ставят в 0.
FullScreen_RefreshRateInHz Частота обновления при работе в полноэкранном режиме. В случае запуска в окне выставляем 0!
PresentationInterval Контролирует частоту смены (свопов) экранных буферов.


Перед применением её создают:

D3DPRESENT_PARAMETERS d3dpp;

Имя произвольное.
Затем сразу обнуляют все её члены:

ZeroMemory(&d3dpp, sizeof(d3dpp));

В шаблоне структуры D3DPRESENT_PARAMETERS указано 14 параметров. На деле в самом простом случае (оконный режим) можно указать только 4:

// Заполняем структуру D3DPRESENT_PARAMETERS
 D3DDISPLAYMODE        d3ddm;

 if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;

 D3DPRESENT_PARAMETERS d3dpp;

 ZeroMemory(&d3dpp, sizeof(&d3dpp));

 d3dpp.Windowed = TRUE;
 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
 d3dpp.BackBufferFormat = d3ddm.Format;
 d3dpp.EnableAutoDepthStencil = FALSE;

Пример приложения инициализирующего объект устройства Direct3D (DirectX 9).

Создадим простой Проект приложения, инициализирующего объект устройства Direct3D (DirectX 9). За основу возьмём исходный код базового приложения Windows из статьи Создание приложений (Cpp, Win32)), который затем слегка дополним.

Перед началом на компьютер необходимо установить следующие программные компоненты:

  • MS Visual C++ 2010 Express;
  • DirectX SDK 9.

Все эти штуки:

  • + инструкции по их установке ты найдёшь в разделе "Софт" нашего сайта;
  • Бесплатны;
  • Без труда гуглятся.

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

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

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

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

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.

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

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

  • В только что созданном и открытом файле WinMain.cpp набираем исходный код простейшего оконного приложения, взятого из статьи Создание приложений (Cpp, Win32):
WinMain.cpp
// Файл: WinMain.cpp 
// Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows,
// выводящее на экран окно.
// www.igrocoder.ru 2015
// При использовании просьба указывать ссылку на источник.

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

#include <windows.h>
#include <windowsx.h>

// Объявление оконной процедуры
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;			// Структура сообщения

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

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;	// Поочерёдный доступ к контексту устройства (Device Context)
	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=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

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

	if(hWnd==NULL) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

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

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	
                // Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	        {
		 // Есть сообщение! Обработаем его как обычно (то есть отправим в оконную процедуру WindowProc).
		 TranslateMessage(&msg);
		 DispatchMessage(&msg);
	        }
		else
		{
			// Нет стандартных сообщений Windows, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}

	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}

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

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

  • Найди в WinMain.cpp фрагмент кода, где оконный класс заполняется данными:
Фрагмент WinMain.cpp
...
   // Заполняем структуру оконного класса
	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=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
...
  • Строку
Фрагмент WinMain.cpp
...
	wndClass.style=CS_CLASSDC;
...

замени на

wndClass.style=CS_HREDRAW | CS_VREDRAW;

  • Сохрани Решение (File -> Save All).

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

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

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

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

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

Image

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

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

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

  • Сохрани Решение (File -> Save All).

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

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

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

Закрыть
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 в главном меню выбираем Проект->Свойства, в верхней части диалога видим всплывающий список "Конфигурация").

Добавляем в Проект D3D9Init01 функцию инициализации Direct3D (DirectX 9) GameInit

Добавляем заголовки d3d9.h и time.h

d3d9.h нужен для вызова функций DirectX 9. Расположен в подкаталоге Include установленного DirectX SDK 9 (в нашем случае C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include).
time.h - заголовочный файл из стандартной библиотеки C++. Расположен в подкаталоге Include установленной MS Visual C++ 2010 Express (в нашем случае C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include). В принципе никак не связан с DirectX. Но игрокодеры часто его применяют для замера времени подготовки и вывода очередного кадра.

  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди список подключаемых заголовков:
Фрагмент WinMain.cpp
...
#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <windowsx.h>
...
  • Ниже строки #include <windowsx.h> добавь следующие строки:
#include <d3d9.h>
#include <time.h>
  • Сохрани Решение (File -> Save All).

Создаём объект класса IDirect3D9 и объект устройства Direct3D

  • В WinMain.cpp найди строку объявления прототипа функции (обратного вызова) обработки сообщений WindowProc:
Фрагмент WinMain.cpp
...
// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
...

Забегая вперёд, скажем, что реализация оконной процедуры WindowProc размещена в самом конце WinMain.cpp . Это обычная практика.
Но вернёмся к области объявлений. Код будет намного читабельнее, если разместить все объявления в одном месте исходника.
В нашем случае в области глобальных объявлений соит всего 1 строка - объявление оконной процедуры.

  • Чуть ниже объявления оконной процедуры (LRESULT CALLBACK WindowProc...) размести строки создания объекта класса IDirect3D9 и объекта устройства Direct3D соответственно:
// Объекты Direct3D (DirectX 9)
LPDIRECT3D9 d3d = NULL;
LPDIRECT3DDEVICE9 d3ddev = NULL;

Вообще имена экземпляров d3d и d3ddev выбираются программером произвольно и сильно варьируются у разных авторов. В данном уроке оставь их как есть.
Значения по умолчанию у обоих объектов сразу выставляются в NULL.

Создаём функцию инициализации GameInit

Данная функция - наша авторская разработка. В DirectX её никогда не было. В ней мы аккуратно разместим реализации функций инициализации DirectX + небольшой таймер для замеров производительности. Её вызов мы разместим сразу после показа окна, но ДО начала процедуры выборки сообщений. Название GameInit произвольное (у одного автора она называлась DoInit). Главное - чтобы по имени функции можно было понять её назначение.

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

По аналогии нетрудно придумать "говорящие" имена других гипотетических функций (GamePreProc, GameDraw, GamePostProc, GameEnd и т.д.), которые можно реализовать в будущем движке при необходимости.


Пишем объявление функции GameInit

  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди список подключаемых заголовков:
Фрагмент WinMain.cpp
...
#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <windowsx.h>

#include <d3d9.h>
#include <time.h>
...
  • Ниже строки #include <time.h> добавь объявление функции GameInit:
// Здесь идут всякие объявления
int GameInit(HWND);
  • Сохрани Решение (File -> Save All).


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

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

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}
  • Сразу после строк реализации оконной процедуры набери код реализации функции GameInit:
int GameInit(HWND hWnd)
{
 // Показываем сообщение об инициализации.
 MessageBox(hWnd, "Программа начинает свою работу", "GameInit", MB_OK);

 // Структура. Нужна для получения текущих настроек рабочего стола.
 D3DDISPLAYMODE        d3ddm;

 // Инициализируем Direct3D в оконном режиме
 if((d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
    return FALSE;

 if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;

 // Заполняем структуру D3DPRESENT_PARAMETERS
 D3DPRESENT_PARAMETERS d3dpp;
 ZeroMemory(&d3dpp, sizeof(&d3dpp));

 d3dpp.Windowed = TRUE;
 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
 d3dpp.BackBufferFormat = d3ddm.Format;
 d3dpp.EnableAutoDepthStencil = FALSE;

 MessageBox(hWnd, "Программа заполнила D3DPRESENT_PARAMETERS", "GameInit", MB_OK);

d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &d3ddev);

 if(d3d != NULL)
 {
	 MessageBox(hWnd, "Программа успешно выполнила CreateDevice", "GameInit", MB_OK);
 }

 // Создаём генератор случайных чисел на основе системного времени.
 srand(time(NULL));

 // Раз достигли этой строки, то всё ОК.
 return 1;
}
  • Сохрани Решение (File -> Save All).

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

Фрагмент WinMain.cpp
...
 D3DDISPLAYMODE        d3ddm;
...

Потом заполняем текущими настройками рабочего стола:

Фрагмент WinMain.cpp
...
 if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;
...

Потом присваиваем элементу структуры d3dpp.BackBufferFormat значение элемента той самой структуры текущих настроек рабочего стола (d3ddm.Format):

Фрагмент WinMain.cpp
...
d3dpp.BackBufferFormat = d3ddm.Format;
...


Пишем вызов функции GameInit

  • В середине WinMain.cpp найди вызов функций показа и обновления созданного окна:
Фрагмент WinMain.cpp
...
	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
...
  • Ниже строки UpdateWindow(hWnd); набери код вызова функции GameInit:
// Инициализируем игру.
if(!GameInit(hWnd))
return 0;
  • Сохрани Решение (File -> Save All).

Здесь вызов функции GameInit "обрамлён" условным переходом if. Если выполнение GameInit провалилось, то досрочно завершаем работу приложения.
Созданный генератор случайных чисел пригодится позднее => пока не трогаем.

Исходный код WinMain.cpp после всех внесённых изменений

WinMain.cpp
// Файл: WinMain.cpp 
// Файл: WinMain.cpp 
// www.igrocoder.ru 2021
// При использовании просьба указывать ссылку на источник.

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

#include <windows.h>
#include <windowsx.h>

#include <d3d9.h>
#include <time.h>

// Здесь идут всякие объявления
int GameInit(HWND);

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Объекты Direct3D (DirectX 9)
LPDIRECT3D9  d3d = NULL;
LPDIRECT3DDEVICE9  d3ddev = NULL;

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

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

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_HREDRAW | CS_VREDRAW;
	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=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

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

	if(hWnd==NULL) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	// Инициализируем игру
	if(!GameInit(hWnd))
		return 0;

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).

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

	while(msg.message != WM_QUIT) 
	{
     if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	 {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
     }
	 else
	 {	// Нет сообщений, ожидающих обработки.
		// Значит приступим к выполнению критичных ко времени операций.
		// Например, займёмся рендерингом.
	 }
	}

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

	// Выходим из приложения
	return (msg.wParam);
}

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

int GameInit(HWND hWnd)
{
 // Показываем сообщение об инициализации.
 MessageBox(hWnd, "Программа начинает свою работу", "GameInit", MB_OK);

 // Структура. Нужна для получения текущих настроек рабочего стола.
 D3DDISPLAYMODE        d3ddm;

 // Инициализируем Direct3D в оконном режиме
 if((d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
    return FALSE;

 if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;

 // Заполняем структуру D3DPRESENT_PARAMETERS
 D3DPRESENT_PARAMETERS d3dpp;
 ZeroMemory(&d3dpp, sizeof(&d3dpp));

 d3dpp.Windowed = TRUE;
 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
 d3dpp.BackBufferFormat = d3ddm.Format;
 d3dpp.EnableAutoDepthStencil = FALSE;

 MessageBox(hWnd, "Программа заполнила D3DPRESENT_PARAMETERS", "GameInit", MB_OK);

 d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &d3ddev);

 // Создаём генератор случайных чисел на основе системного времени.
 srand(time(NULL));

 // Раз достигли этой строки, то всё ОК.
 return TRUE;
}

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

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

Если попытаться скомпилировать Проект D3D9Init01 в таком виде, то ничего не выйдет.
В единственном файле исходного кода WinMain.cpp можно увидеть директиву включения (#include) заголовочного файла d3d9.h:

Фрагмент WinMain.cpp
...
// Include files
#include <windows.h>
#include <windowsx.h>

#include <d3d9.h>
...

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

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

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

  • Жмём правой кнопкой мыши по названию Проекта D3D9Init01. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем 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 запущена и в ней открыт наш текущий Проект D3D9Init01.
  • Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.

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

  • Жмём правой кнопкой мыши по названию Проекта D3D9Init01. Во всплывающем меню выбираем пункт "Свойства". Или в Главном меню выбираем Проект->Свойства. Или нажимаем 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 в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)

Т.к. мы создаём исполняемое приложение (исполняемый .exe-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (=linker). Так вот, этот самый компоновщик по ранее прописанным каталогам нужные библиотеки не ищет. Поэтому библиотеку d3d9.lib необходимо явно указывать в списке дополнительных зависимостей линкера (=компоновщика). Причём в дальнейшем, по мере роста функционала движка, не только её.
d3d9.lib расположена в папке с установленным DirectX SDK 9 (в нашем случае по пути C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86), путь к которому мы прописали выше.
ОК, начинаем.

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

d3d9.lib

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

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

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

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

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

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

Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".

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

========== Перестроение всех: успешно: 1, с ошибками: 0, пропущено: 0 ==========
  • Перейди в каталог Проекта (в нашем случае C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Init01\Debug) и запусти скомпилированный исполняемый файл D3D9Init01.exe .

Появится окно с чёрным фоном + модальное диалоговое окно с сообщением о начале работы.

Добавляем в Проект D3D9Init01 функцию рендеринга GameRun

Создаём функцию GameRun

Данная функция также является авторской разработкой. В DirectX её никогда не было. По аналогии с GameInit, в ней мы аккуратно разместим реализации функций вывода изображения средствами DirectX. А именно зальём фон окна зелёным цветом. Её вызов мы разместим во второй части цикла if...else цикла выборки сообщений.

Пишем объявление функции GameRun

  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди список подключаемых заголовков и чуть ниже объявление функции GameInit:
Фрагмент WinMain.cpp
...
#include <windows.h>
#include <windowsx.h>

#include <d3d9.h>
#include <time.h>

// Здесь идут всякие объявления
int GameInit(HWND);
...
  • Сразу под ним пишем объявление функции GameRun:
void GameRun(HWND);
  • Сохрани Решение (File -> Save All).


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

  • В самом конце WinMain.cpp найди реализацию функции GameInit, написанную чуть ранее:
Фрагмент WinMain.cpp
...
  d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, hWnd,
                                    D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                    &d3dpp, &d3ddev );

   MessageBox(hWnd, "Успешно создали объект устройства Direct3D! Едем дальше)", "GameInit", MB_OK);

 // Создаём генератор случайных чисел на основе системного времени.
 srand(time(NULL));

 // Раз достигли этой строки, то всё ОК.
 return 1;
}
  • Сразу под ней пишем реализацию функции GameRun:
void GameRun(HWND hWnd)
{
 // Проверяем, что объект устройства Direct3D создан и готов к работе.
 if(d3ddev == NULL)
 return;

 // Очищаем бэкбуфер заливкой в цвет.
 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0,64,128,255), 1.0f, 0);

 // Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
  // Что-то делаем.

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

 // Выводим содержимое бэкбуфера на экран.
 d3ddev->Present(NULL, NULL, NULL, NULL);
}
  • Сохрани Решение (File -> Save All).


Пишем вызов функции GameRun

  • В середине WinMain.cpp найди фрагмент процедуры выборки сообщений:
Фрагмент WinMain.cpp
...
	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	        {
		// Есть сообщение! Обработаем его как обычно (то есть отправим в оконную процедуру WindowProc).
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	        }
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
...
  • Внутри блока else (пониже всех комментариев) набери код вызова функции GameInit:
// Главный цикл игры (main gameloop).
GameRun(hWnd);
  • Сохрани Решение (File -> Save All).

Размещение вызова GameRun в блоке else гарантирует завершение работы игрового цикла при закрытии окна приложения. Это, конечно, не отменяет необходимость корректного освобождения ресурсов Direct3D при завершении работы программы. Вот почему для этих целей мы создадим ещё одну функцию GameEnd. Но об этом читай ниже.

Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".

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

========== Перестроение всех: успешно: 1, с ошибками: 0, пропущено: 0 ==========
  • Перейди в каталог Проекта (в нашем случае C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Init01\Debug) и запусти скомпилированный исполняемый файл D3D9Init01.exe .

Появится окно с чёрным фоном + пара модальных диалоговых окон с сообщениями о прохождении приложением стадий инициализации DirectX.

Добавляем в Проект D3D9Init01 функцию освобождения ресурсов GameEnd

Создаём функцию GameEnd

Данная функция также является авторской разработкой. В DirectX её никогда не было. По аналогии с GameInit и GameRun, в ней мы аккуратно разместим реализации функций освобождения ресурсов DirectX. Её вызов мы разместим...

Пишем объявление функции GameEnd

  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди список подключаемых заголовков и чуть ниже объявление функции GameRun:
Фрагмент WinMain.cpp
...
#include <windows.h>
#include <windowsx.h>

#include <d3d9.h>
#include <time.h>

// Здесь идут всякие объявления
int GameInit(HWND);
void GameRun(HWND);
...
  • Сразу под ним пишем объявление функции GameEnd:
void GameEnd(HWND);
  • Сохрани Решение (File -> Save All).


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

  • В самом конце WinMain.cpp найди реализацию функции GameRun, написанную чуть ранее:
Фрагмент WinMain.cpp
...
void GameRun(HWND hWnd)
{
 // Проверяем, что объект устройства Direct3D создан и готов к работе.
 if(d3ddev == NULL)
 return;

 // Очищаем бэкбуфер заливкой в цвет.
 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0,64,128,255), 1.0f, 0);

 // Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
  // Что-то делаем.

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

 // Выводим содержимое бэкбуфера на экран.
 d3ddev->Present(NULL, NULL, NULL, NULL);
}
  • Сразу под ней пишем реализацию функции GameEnd:
void GameEnd(HWND hWnd)
{
 // Показываем сообщение о закрытии.
 MessageBox(hWnd, "Программа завершает свою работу", "GameEnd", MB_OK);

 // Освобождаем ресурсы, выделенные ранее под объект устройства Direct3D.
 if(d3ddev != NULL)
  d3ddev->Release();

 // Освобождаем ресурсы, выделенные ранее под объект (инстанс) класса IDirect3D9.
 if(d3d != NULL)
  d3d->Release();
}
  • Сохрани Решение (File -> Save All).


Пишем вызов функции GameEnd

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

Функция очистки ресурсов GameEnd вызывается в случае (case) поступления сообщения WM_QUIT. Обычно оно генерируется при нажатии кнопки с крестиком в правом верхнем углу окна приложения либо при нажатии клавиш Alt+F4 на клавиатуре.

Исходный код WinMain.cpp после всех внесённых изменений

WinMain.cpp
// Файл: WinMain.cpp 
// Файл: WinMain.cpp 
// www.igrocoder.ru 2021
// При использовании просьба указывать ссылку на источник.

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

#include <windows.h>
#include <windowsx.h>

#include <d3d9.h>
#include <time.h>

// Здесь идут всякие объявления
int GameInit(HWND);
void GameRun(HWND);
void GameEnd(HWND);

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Объекты Direct3D (DirectX 9)
LPDIRECT3D9  d3d = NULL;
LPDIRECT3DDEVICE9  d3ddev = NULL;

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

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

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_HREDRAW | CS_VREDRAW;
	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=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

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

	if(hWnd==NULL) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	// Инициализируем игру
	if(!GameInit(hWnd))
		return 0;

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).

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

	while(msg.message != WM_QUIT) 
	{
     if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	 {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
     }
	 else
	 {	// Нет сообщений, ожидающих обработки.
		// Значит приступим к выполнению критичных ко времени операций.
		// Например, займёмся рендерингом.

		// Главный цикл игры (main gameloop).
		GameRun(hWnd);
	 }
	}

	//Удаляем регистрацию класса
	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);
}

int GameInit(HWND hWnd)
{
 // Структура. Нужна для получения текущих настроек рабочего стола.
 D3DDISPLAYMODE        d3ddm;

 // Инициализируем Direct3D в оконном режиме
 if((d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
    return FALSE;

 if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;

 // Заполняем структуру D3DPRESENT_PARAMETERS
 D3DPRESENT_PARAMETERS d3dpp;
 ZeroMemory(&d3dpp, sizeof(&d3dpp));

 d3dpp.Windowed = TRUE;
 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
 d3dpp.BackBufferFormat = d3ddm.Format;
 d3dpp.EnableAutoDepthStencil = FALSE;

 MessageBox(hWnd, "Программа заполнила D3DPRESENT_PARAMETERS", "GameInit", MB_OK);

 d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &d3ddev);

 // Создаём генератор случайных чисел на основе системного времени.
 srand(time(NULL));

 // Раз достигли этой строки, то всё ОК.
 return TRUE;
}

void GameRun(HWND hWnd)
{
 // Проверяем, что объект устройства Direct3D создан и готов к работе.
 if(d3ddev == NULL)
 return;

 // Очищаем бэкбуфер заливкой в цвет.
 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0,64,128,255), 1.0f, 0);

 // Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
  // Что-то делаем.

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

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

void GameEnd(HWND hWnd)
{
 // Показываем сообщение о закрытии.
 MessageBox(hWnd, "Программа завершает свою работу", "GameEnd", MB_OK);

 // Освобождаем ресурсы, выделенные ранее под объект устройства Direct3D.
 if(d3ddev != NULL)
  d3ddev->Release();

 // Освобождаем ресурсы, выделенные ранее под объект (инстанс) класса IDirect3D9.
 if(d3d != NULL)
  d3d->Release();
}

Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".

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

========== Перестроение всех: успешно: 1, с ошибками: 0, пропущено: 0 ==========
  • Перейди в каталог Проекта (в нашем случае C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Init01\Debug) и запусти скомпилированный исполняемый файл D3D9Init01.exe .

Появится окно с чёрным фоном + модальные диалоговые окна с сообщениями о начале и завершении работы приложения.
Шаблонное приложение, инициализирующее Direct3D (DirectX 9), готово.

Запуск приложения D3D9Init01 в полноэкранном режиме. Изменения в коде WinMain.cpp

Автор Harbour J.S.1 рекомендует сначала разрабатывать игру в оконном режиме, а тестировать релиз в полноэкранном. В идеале "прикрутить" к окну приложения диалоговое окно, запрашивающее у пользователя нужный режим (и настройки видеографики). Но, в целях простоты изложения, сейчас мы не полезем в дебри бинарных ресурсов Windows, а лишь перепишем код таким образом, чтобы наше приложение из оконного (работающего в окне) стало полноэкранным.

Добавляем переменные разрешения экрана в начало листинга

Это сделает код более читабельным и программер в будущем сможет легко менять значения длины и высоты экрана (в точках) при необходимости.

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

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.
...
  • Пониже всех директив define добавь ешё пару строк:
// Разрешение экрана
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480

Здесь мы числовым значениям присвоили символьные имена.

Добавляем поддержку ввода с клавиатуры

Хотя бы для того, чтобы выйти из программы, которая работает в полноэкранном режиме. Привычных кнопок Свернуть, Развернуть, Закрыть (крестик) в полноэкранных приложениях обычно нет.
Конечно, можно задействовать прерывания ОС Windows (Alt+Tab, Alt+F4, Ctrl+Alt+Delete). Но это непрофессионально. Любое Windows-приложение (согласно стандартам программирования Microsoft) должно иметь собственные команды завершения работы. В теории пользовательским вводом в играх "заведует" Direct Input. Но в данной статье обойдёмся средстами Windows API.

  • В самом начале WinMain.cpp пониже всех директив define добавь ешё пару строк:
// Макрос асинхронного считывания нажатий кнопок клавиатуры.
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_kode) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)

Макросы нажатия и отжатия кнопки Esc.

Изменения в функции CreateWindow

  • Найди в WinMain.cpp следующий фрагмент:
Фргамент WinMain.cpp
... 
	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindowEx(
		WS_EX_TOPMOST,		// Дополнительный стиль окна
		"GameClass",		// Класс окна.
		"My game title",			// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,		// Обычное окно с кнопками в правом верхнем углу.
		0, 0,					// Координаты X и Y
		GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
		GetSystemMetrics(SM_CYSCREEN),	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные
...

Источники:


1. Harbour J.S. Beginning Game Programming. - Thomson Course Technology. 2005

Contributors to this page: slymentat .
Последнее изменение страницы Пятница 26 / Февраль, 2021 16:10:46 MSK автор slymentat.

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

Яндекс-деньги: 410011791055108