Программируем игры на DirectX

Материал из Викиучебника — открытых книг для открытого мира
Перейти к: навигация, поиск

Введение[править]

В этом учебнике я постараюсь научить вас программировать игры на языке С++ с помощью DirectX SDK. Программировать мы будем с вами для операционной системы Windows XP. В этом учебнике мы охватим такие важные темы как графика, музыка, физика, искусственный интеллект. Для применения полученных знаний мы напишем пару игр. Код используемый в книге был написан мною в среде Microsoft Visual C++ 6.0 с использованием DirectX SDK November 2009.

Урок 1 - Создание окна. Инициализация Direct3D[править]

Создадим проект с именем Lesson1. Добавим в него файл main.cpp, и начнём его заполнять.

Для работы приложения нам потребуется использовать разные библиотеки, их мы сейчас и подключим к нашему проекту.

//Подключаем библиотеки
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.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 в оконном режиме, а в полноэкранном режиме мы напишем, когда наше приложение будет поддерживать клавиатуру.