Программируем игры на DirectX
Урок 1 - Создание окна. Инициализация Direct3D
правитьСоздадим проект с именем Lesson1. Добавим в него файл main.cpp, и начнём его заполнять.
Для работы приложения нам потребуется использовать разные библиотеки, их мы сейчас и подключим к нашему проекту.
//Подключаем библиотеки
#pragma comment(lib,"d3d11d.lib")
#pragma comment(lib,"d3dx11.lib")
#pragma comment(lib,"winmm.lib")
Дальше мы должны подключить заголовочные файлы, чтобы компилятор не выдавал ошибки.
//Подключаем заголовочные файлы
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
Объявим глобальные переменные, которые мы будем использовать в нашей программе.
//Глобальные переменные
HINSTANCE g_hInstance = NULL; //Дескриптор приложения
HWND g_hWnd = NULL; //Дескриптор окна
int g_iWindowWidth = 800; //Ширина окна
int g_iWindowHeight = 600; //Высота окна
bool g_bApplicationState = true; //Состояние приложения (true - работает/false - не работает)
IDirect3D9 *g_pDirect3D = NULL; //Интерфейс для создания устройства рендеринга
IDirect3DDevice9 *g_pDirect3DDevice = NULL; //Интерфейс устройства рендеринга
Теперь объявим прототипы функций, которые мы с вами напишем.
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow); //Точка старта приложения
long WINAPI WndProc(HWND hWnd,UINT iMsg,WPARAM wParam,LPARAM lParam);//Обработчик сообщений
bool InitDirect3D(D3DFORMAT ColorFormat,D3DFORMAT DepthFormat); //Инициализация Direct3D
void DrawFrame(); //Рисуем кадр
void Shutdown(); //Освобождаем память
Поговорим о функции WinMain. Эта функция является точкой старта приложения. Код, который в ней написан, начинает выполнение при запуске программы. В этой функции мы должны создать окно и отобразить его.
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow)
{
g_hInstance = GetModuleHandle(NULL);
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX); //Размер структуры
wc.style = CS_HREDRAW|CS_VREDRAW; //Стили класса окна
wc.lpfnWndProc = WndProc; //Функция обработки сообщений
wc.cbClsExtra = 0; //Количество выделяемой памяти при создании приложения
wc.cbWndExtra = 0; //Количество выделяемой памяти при создании приложения
wc.hInstance = g_hInstance; //Дескриптор приложения
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); //Загружаем стандартную иконку
wc.hCursor = LoadCursor(0,IDC_ARROW); //Загружаем стандартный курсор
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//Окно будет закрашено в белый цвет
wc.lpszMenuName = 0; //Не используем меню
wc.lpszClassName = "Lesson 1"; //Названия класса
wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION); //Загружаем стандартную иконку
if(!RegisterClassEx(&wc)) //Регистрируем класс в Windows
{
Shutdown(); //Освобождаем память
MessageBox(NULL,"Can`t register window class","Error",MB_OK|MB_ICONERROR); //Выводим сообщение
return 0; //Завершаем работу приложения
}
g_hWnd = CreateWindowEx( //Создаем окно
WS_EX_APPWINDOW|WS_EX_WINDOWEDGE, //Расширенный стиль окна
"Lesson 1", //Названия класса окна
"Lesson 1 - Create Window. Init Direct3D", //Названия окна
WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,//Стиль окна
0, //Позиция окна по оси Х
0, //Позиция окна по оси У
g_iWindowWidth, //Ширина окна
g_iWindowHeight, //Высота окна
NULL, //Это наше главное окно
NULL, //Нету меню
g_hInstance, //Дескриптор приложения
NULL); //Дополнительный настроек не используем
if(g_hWnd == NULL) //Если не создали окно
{
Shutdown();
MessageBox(NULL,"Can`t create window","Error",MB_OK|MB_ICONERROR);//Выводим сообщение
return 0; //Завершаем работу приложения
}
if(!InitDirect3D(D3DFMT_R5G6B5,D3DFMT_D16)) //Если не смогли инициализировать Direct3D
{
Shutdown();
MessageBox(NULL,"Can`t create direct3d","Error",MB_OK|MB_ICONERROR);//Выводим сообщение
return 0; //Завершаем работу приложения
}
ShowWindow(g_hWnd,SW_SHOW); //Отображаем окно
UpdateWindow(g_hWnd); //Обновляем окно
SetFocus(g_hWnd); //Устанавливаем фокус на наше окно
SetForegroundWindow(g_hWnd); //Устанавливаем приоритет окна выше среднего
MSG msg;
ZeroMemory(&msg,sizeof(msg));
while(g_bApplicationState) //Начинаем бесконечный цикл обработки сообщений
{
if(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))//Получаем сообщения
{
TranslateMessage(&msg); //Обрабатываем сообщения
DispatchMessage(&msg); //Обрабатываем сообщения
}
else
DrawFrame(); //Если сообщений нету рисуем кадры
}
Shutdown(); //Освобождаем память
return 0; //Завершаем работу приложения
}
Теперь мы должны написать функцию, которая у нас будет обрабатывать сообщения. Принцип действия этой функции таков: если мы получили сообщения — перехватываем его, и делаем действия которые нам требуются.
long WINAPI WndProc(HWND hWnd,UINT iMsg,WPARAM wParam,LPARAM lParam)
{
switch(iMsg)
{
case WM_DESTROY: //Если получаем сообщение о разрушении окна
{
g_bApplicationState = false; //Устанавливаем состояния приложения в false (это значит что цикл обработки сообщений остановиться)
return 0; //Говорим Windows что мы это сообщение обработали
}
}
return DefWindowProc(hWnd,iMsg,wParam,lParam); //Если нету для нас нужных сообщений, пусть это обрабатывает Windows
}
Сейчас наша задача написать функцию инициализации Direct3D. Эта тема для вас новая, постарайтесь сосредоточиться и внимательно просмотреть код.
bool InitDirect3D(D3DFORMAT ColorFormat,D3DFORMAT DepthFormat)
{
if((g_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)//Создаем интерфейс Direct3D
return false; //Иначе возвращяем false
D3DPRESENT_PARAMETERS PresParam; //Структура с помощью которой передаем информацию устройству рендеринга при его создании
ZeroMemory(&PresParam,sizeof(PresParam)); //Обнуляем
HRESULT hr = NULL; //Создаем переменную для записи в неё результатов работы функций
D3DDISPLAYMODE DisplayMode; //Структура для получения информации о режиме отображения в Windows
hr = g_pDirect3D->GetAdapterDisplayMode( //Получаем режим отображения
D3DADAPTER_DEFAULT, //Используем первичную видеокарту
&DisplayMode); //Записываем режим отображения в DisplayMode
if(FAILED(hr)) //Если не получилось
return false; //Возвращаем false
PresParam.hDeviceWindow = g_hWnd; //Дескриптор окна
PresParam.Windowed = true; //Оконный режим?
PresParam.BackBufferWidth = g_iWindowWidth; //Ширина заднего буфера
PresParam.BackBufferHeight = g_iWindowHeight; //Высота заднего буфера
PresParam.BackBufferCount = 1; //Количество задних буферов
PresParam.EnableAutoDepthStencil = true; //Используем буфер глубины и стенцил буфер
PresParam.AutoDepthStencilFormat = DepthFormat; //Формат буфера глубины
PresParam.SwapEffect = D3DSWAPEFFECT_FLIP; //Режим смены кадров
PresParam.BackBufferFormat = DisplayMode.Format;//Устанавливаем формат пикселя определенный в Windows
hr = g_pDirect3D->CreateDevice( //Создаем устройство рендеринга
D3DADAPTER_DEFAULT, //Используем первичную видеокарту
D3DDEVTYPE_HAL, //Устройства рендеринга использует возможности видеокарты
g_hWnd, //Дескриптор окна
D3DCREATE_HARDWARE_VERTEXPROCESSING, //Обрабатываем вершины видеокартой
&PresParam, //Отдаем параметры устройства
&g_pDirect3DDevice); //Создаем устройство рендеринга
if(SUCCEEDED(hr)) //Если получилось
return true; //Возвращаем true
hr = g_pDirect3D->CreateDevice( //Создаем устройство рендеринга
D3DADAPTER_DEFAULT, //Используем первичную видеокарту
D3DDEVTYPE_HAL, //Устройства рендеринга использует возможности видеокарты
g_hWnd, //Дескриптор окна
D3DCREATE_MIXED_VERTEXPROCESSING, //Обрабатываем вершины смешанно (видеокартой и процессором)
&PresParam, //Отдаем параметры устройства
&g_pDirect3DDevice); //Создаем устройство рендеринга
if(SUCCEEDED(hr)) //Если получилось
return true; //Возвращаем true
return false; //Возвращаем false
}
Приступим к написанию функции, которая будет рисовать кадры. У устройства рендеринга есть свои недостатки — потеря устройства. Потеря устройства возникает например в случаях, когда полноэкранное окно не в фокусе и т. д. После того как мы потеряли устройство надо его восстановить, чтобы дальше можно было отображать сцену.
void DrawFrame()
{
HRESULT hr = g_pDirect3DDevice->TestCooperativeLevel();//Проверяем потерял ли Direct3DDevice устройство
if(hr == D3DERR_DEVICELOST) //Если да то
return; //Выходи из функции
g_pDirect3DDevice->Clear( //Очищаем задний буфер
0L, //Размер буфера, 0 - весь буфер
NULL, //Область которую будем очищать, NULL - весь буфер
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, //Чистим задний буфер и буфер глубины
D3DCOLOR_XRGB(0,0,0), //Цвет в который очищаем задний буфер, в нашем случае черный
1.0f, //Очищаем буфер глубины, заполнив его единицами
0L); //Этот параметр игнорируется так как не выставлен соответствующий флаг
g_pDirect3DDevice->BeginScene(); //Начало сцены
g_pDirect3DDevice->EndScene(); //Конец сцены
g_pDirect3DDevice->Present(NULL,NULL,NULL,NULL);//Отображаем весь задний буфер
}
Ну и последняя функция, которая будет освобождать выделенную память. Освобождать память надо в обратном порядке выделения памяти.
void Shutdown()
{
if(g_pDirect3DDevice != NULL) //Если мы еще не освободили интерфейс рендеринга
{
g_pDirect3DDevice->Release(); //То освобождаем его
g_pDirect3DDevice = NULL; //И устанавливаем в ноль
}
if(g_pDirect3D != NULL) //Если мы еще не освободили интерфейс d3d
{
g_pDirect3D->Release(); //То освобождаем его
g_pDirect3D = NULL; //И устанавливаем в ноль
}
if(!DestroyWindow(g_hWnd)) //Если не получилось разрушить окно
g_hWnd = NULL; //Устанавливаем дескриптор окна в ноль
if(!UnregisterClass("Lesson 1",g_hInstance)) //Если не получилось удалить наше зарегистрированное окно
g_hInstance = NULL; //Устанавливаем дескриптор приложения в ноль
}
Теперь компилируем и смотрим что получилось.
Мы написали приложение, которое инициализирует Direct3D в оконном режиме, а в полноэкранном режиме мы напишем, когда наше приложение будет поддерживать клавиатуру.