Разработка компьютерных игр

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

. Создание объектов и персонажей в Direct3D, рисование, текстурирование, анимация.
. Создание карт, визуализация ландшафта.
. Освещение, шейдеры, HLSL.
. Работа с системами частиц.
. Разработка интерфейса.
. Искусственный интеллект.
. Сетевое программирование.
. Программирование звука для игр.
. Перевод игр на консоли.

Все темы достаточно объемны, поэтому вам предлагается на выбор, с чего лучше стартовать в следующем году. Причем будут изменения в формате представления материалов, они уже не будут выходить как общая серия "Разработка компьютерных игр", а разбиваться тематически. В общем, пишите все ваши пожелания на christopher@tut.by.

Инициализация Direct3D

Теперь с багажом полученных знаний мы начнем внедряться в программирование. Осознанно. Открываем самый первый пример из документации к SDK, который расположен по адресу: …\Program Files\Microsoft DirectX SDK\Samples\C++\Direct3D\Tutorials\Tut01_CreateDevice в рамках нашей Visual Studio. Ваш покорный слуга опирается на Visual Studio 2008, хотя данный момент не сильно принципиален. Смотрим файл CreateDevice.cpp. Заголовочный файл DirectX SDK подключается следующим образом:
#include <d3d9.h>

Итак, первым этапом инициализации идет запрос указателя на интерфейс IDirect3D, который применяется для получения информации об аппаратных устройствах компьютера, а также для создания интерфейса IDirect3DDevice9, который будет являться объектом С++, представляющим аппаратное устройство для вывода трехмерной графики (визуализации). Этому соответствуют строки:
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;

В принципе, лично ваш покорный слуга в качестве примера для избежания путаницы назначил бы другие имена переменных — например, так все было бы намного понятнее:
LPDIRECT3D9 direct3d = NULL;
LPDIRECT3DDEVICE9 device = NULL;

…но все же будем опираться на листинг программы c учетом ее названий переменных.
HRESULT InitD3D( HWND hWnd )
{
Init3D — функция инициализации главного окна приложения, содержащая код инициализации Direct3D. Здесь содержатся ключевые строки. Создавая главный объект, мы вызываем функцию Direct3DCreate9 с единственным параметром D3D_SDK_VERSION, который является предопределенной константой, описанной в заголовочном модуле (d3d9.h), и указывает на версию DirectX. То есть в простейшем виде можно записать:
g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);

В нашем случае мы создаем главный объект следующим образом, причем нам нужно просто узнать, установлен ли DirectX вообще:
if(NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION) ) )
return E_FAIL;

Следующим этапом подключается структура, используемая для создания интерфейса IDirect3DDevice9:
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

Теперь расшифруем: ZeroMemory () — заполняем указанную строку нулями, Windowed=True указывает на то, что запуск будет происходить в окне, в рамках SwapEffect задается режим действия механизма двойной буферизации — в данном случае мы выбрали форсированный вариант
D3DSWAPEFFECT_DISCARD; BackBufferFormat указывает на то, какой формат буфера будет использоваться. Теперь мы создаем устройство вывода (визуализации):
if (FAILED(g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) ) ) {
return E_FAIL;
}
return S_OK;

Отдельно стоит сказать, что в книгах по программированию DirectX вы чаще всего встретите несколько отличный подход к написанию кода, то есть там все выполняется без условий if. В SDK же это показано в рамках более осторожных методов, то есть, "если это возможно…". Теперь расшифровываем строку. Вызывая метод CreateDevice, мы получаем ссылку на IDirect3DDevice9, с помощью которого производится визуализация. Далее все вам должно быть знакомо, хотя пробежаться по параметрам еще раз не помешает: D3DADAPTER_DEFAULT указывает на номер графического адаптера, установленного в системе, D3DDEVTYPE_HAL — тип устройства, и в данном случае мы подразумеваем полностью аппаратную обработку (если бы указали D3DDEVTYPE_REF, то была бы программная), третий параметр позволяет задать окно (Handle), в которое будет производиться выход сцены, D3DCREATE_SOFTWARE_VERTEXPROCESSING указывает на программную обработку вершин, потому как, например, видеоадаптер может ее не поддерживать, в &d3dpp хранятся параметры создаваемого устройства вывода, &g_pd3dDevice — имя переменной, куда будет помещен результат. На самом деле, несмотря на кажущуюся сложность, при близком рассмотрении все оказывается достаточно простым. Причем в рамках этого листинга не делается одна вещь, которую мы описывали ранее, а именно проверка возможностей графического адаптера. Дальше в коде идет функция очистки памяти. Напомню, что мы имеем дело с COM-объектами, и для их удаления не подходит ключевое слово delete, а вместо него используется метод Release.
VOID Cleanup()
{
if( g_pd3dDevice != NULL)
g_pd3dDevice->Release();

if( g_pD3D != NULL)
g_pD3D->Release();
}

Теперь наступила реализация вывода пустого окна, закрашенного в определенный цвет — в указанном примере синий. Для этого используем метод Clear() интерфейса IDirect3DDevice9.
VOID Render()
{
if( NULL == g_pd3dDevice )
return;

Закрашиваем вторичный буфер в синий цвет:
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

Начало сцены:
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
//здесь помещаются объекты сцены
g_pd3dDevice->EndScene();
}

Вывод содержимого вторичного буфера на дисплей с помощью метода Present() интерфейса IDirect3DDevice9.
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

Теперь имеет смысл посмотреть на функцию wWinMain, которая, собственно, и отвечает за все происходящее, а именно инициализирует главное окно и Direct3D, вызывает процедуру инициализации приложения, запускает цикл обработки сообщений, освобождает ресурсы:
INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{

Определяемся с оконным классом:
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
L"D3D Tutorial", NULL };
RegisterClassEx( &wc );

Создание окна приложения с размерами 300х300 пикселей:
HWND hWnd = CreateWindow( L"D3D Tutorial", L"D3D Tutorial 01: CreateDevice",
WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
NULL, NULL, wc.hInstance, NULL );

Инициализация Direct3D:
if( SUCCEEDED( InitD3D( hWnd ) ) )
{

Показать окно:
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );

Запуск цикла обработки сообщений:
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
UnregisterClass( L"D3D Tutorial", wc.hInstance );
return 0;
}

Каждый раз при обновлении цикла DispatchMessage вызывает функцию обработки системных сообщений MsgProc, которая прописана отдельно: LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;

case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}

return DefWindowProc( hWnd, msg, wParam, lParam );
}

Другими словами, программа ждет, пока выполнится закраска WM_PAINT, после чего начинает перерисовывать. А как работают Cleanup() и Render(), мы можем увидеть выше. В результате при запуске программы перед нами появляется окно, закрашенное в однотонный синий цвет. В большинстве случаев листинги следует рассматривать от функции WinMain, но в нашем случае просмотр сверху вниз оправдан. Второй пример — Tutorial02 из DirectX SDK, в котором выводится треугольник с заданными вершинами и градиентной заливкой — мы уже рассматривали, то есть… к самостоятельному изучению.

Тест

На самом деле, в качестве тестового задания попробуйте несколько перевернуть описание нашего примера и самостоятельно сделать блок-схему, логически построенную от WinMain(), а не от создания Direct3D-объекта.

Продолжение следует.

Вот так выглядит результат примера Tutorial01. Появляется окно, закрашенное в синий цвет.


Полный код файла CreateDevice.cpp

// File: CreateDevice.cpp
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------
#include <d3d9.h>
#pragma warning( disable : 4996 )
#include <strsafe.h>
#pragma warning( default : 4996 )
//------------------------
// Global variables
//------------------------
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
//---------------------------
// Name: InitD3D()
// Desc: Initializes Direct3D
//---------------------------
HRESULT InitD3D( HWND hWnd )
{
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
//--------------------------
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
//-------------------------
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
//------------------------
return S_OK;
}
//-----------------------
// Name: Cleanup()
// Desc: Releases all previously initialized objects
//-----------------------
VOID Cleanup()
{
if( g_pd3dDevice != NULL)
g_pd3dDevice->Release();

if( g_pD3D != NULL)
g_pD3D->Release();
}
//----------------------
// Name: Render()
// Desc: Draws the scene
//---------------------
VOID Render()
{
if( NULL == g_pd3dDevice )
return;
// Clear the backbuffer to a blue color
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
// Begin the scene
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// Rendering of scene objects can happen here
// End the scene
g_pd3dDevice->EndScene();
}
// Present the backbuffer contents to the display
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
//-------------------------
// Name: MsgProc()
// Desc: The window's message handler
//-------------------------
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;

case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
//----------------------
// Name: wWinMain()
// Desc: The application's entry point
//----------------------
INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
L"D3D Tutorial", NULL };
RegisterClassEx( &wc );
//---------------------
HWND hWnd = CreateWindow( L"D3D Tutorial", L"D3D Tutorial 01: CreateDevice",
WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
NULL, NULL, wc.hInstance, NULL );
// Initialize Direct3D
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
// Show the window
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
// Enter the message loop
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
UnregisterClass( L"D3D Tutorial", wc.hInstance );
return 0;
}

Кристофер, christopher@tut.by


Компьютерная газета. Статья была опубликована в номере 46 за 2008 год в рубрике технологии

©1997-2022 Компьютерная газета