En aquest tema, aprendrem a construir un motor de joc utilitzant DirectX. Un motor de joc és una plataforma que proporciona les eines i tecnologies necessàries per desenvolupar jocs. Inclou components com el renderitzat gràfic, la física, l'entrada de l'usuari, l'àudio, i més. Aquest mòdul està dissenyat per guiar-te a través dels passos essencials per crear un motor de joc bàsic amb DirectX.
Objectius del Tema
- Comprendre els components bàsics d'un motor de joc.
- Aprendre a estructurar un projecte de motor de joc.
- Implementar un sistema de renderització bàsic.
- Integrar la gestió d'entrada de l'usuari.
- Desenvolupar un bucle principal del joc.
Components Bàsics d'un Motor de Joc
Un motor de joc típicament inclou els següents components:
- Sistema de Renderització: Responsable de dibuixar gràfics a la pantalla.
- Gestió d'Entrada: Captura i processa les entrades de l'usuari (teclat, ratolí, controladors).
- Sistema de Física: Simula la física del món del joc.
- Gestió d'Escena: Organitza i gestiona els objectes del joc.
- Àudio: Maneja els efectes de so i la música.
- Bucle Principal del Joc: Controla el flux del joc, actualitzant i renderitzant cada fotograma.
Estructura del Projecte
Abans de començar a codificar, és important tenir una estructura clara per al nostre projecte. Aquí tens una estructura bàsica:
GameEngine/ ├── Assets/ ├── Include/ │ ├── Graphics/ │ ├── Input/ │ ├── Physics/ │ └── Core/ ├── Source/ │ ├── Graphics/ │ ├── Input/ │ ├── Physics/ │ └── Core/ └── main.cpp
Implementació del Sistema de Renderització
Inicialització de Direct3D
Primer, necessitem inicialitzar Direct3D. Aquí tens un exemple de codi per inicialitzar Direct3D:
#include <d3d11.h>
#include <dxgi.h>
#include <iostream>
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "dxgi.lib")
IDXGISwapChain* swapChain;
ID3D11Device* device;
ID3D11DeviceContext* deviceContext;
ID3D11RenderTargetView* renderTargetView;
bool InitializeDirect3D(HWND hwnd) {
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
HRESULT hr = D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
nullptr,
0,
D3D11_SDK_VERSION,
&swapChainDesc,
&swapChain,
&device,
nullptr,
&deviceContext
);
if (FAILED(hr)) {
std::cerr << "Failed to create device and swap chain." << std::endl;
return false;
}
ID3D11Texture2D* backBuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
device->CreateRenderTargetView(backBuffer, nullptr, &renderTargetView);
backBuffer->Release();
deviceContext->OMSetRenderTargets(1, &renderTargetView, nullptr);
return true;
}Renderització Bàsica
Un cop inicialitzat Direct3D, podem començar a renderitzar. Aquí tens un exemple de codi per renderitzar un color de fons:
void Render() {
float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
deviceContext->ClearRenderTargetView(renderTargetView, clearColor);
swapChain->Present(1, 0);
}Gestió d'Entrada de l'Usuari
Per capturar l'entrada de l'usuari, podem utilitzar la biblioteca DirectInput o la biblioteca de Windows. Aquí tens un exemple de codi per capturar l'entrada del teclat utilitzant la biblioteca de Windows:
Desenvolupament del Bucle Principal del Joc
El bucle principal del joc és el cor del motor de joc. Controla el flux del joc, actualitzant i renderitzant cada fotograma. Aquí tens un exemple de codi per al bucle principal del joc:
void GameLoop() {
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
// Actualitzar l'estat del joc
// ...
// Renderitzar el joc
Render();
}
}
}Exercici Pràctic
Exercici 1: Crear un Sistema de Renderització Bàsic
- Inicialitza Direct3D en una finestra.
- Renderitza un color de fons a la pantalla.
- Captura l'entrada del teclat per tancar la finestra quan es prem la tecla ESC.
Solució
#include <windows.h>
#include <d3d11.h>
#include <dxgi.h>
#include <iostream>
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "dxgi.lib")
IDXGISwapChain* swapChain;
ID3D11Device* device;
ID3D11DeviceContext* deviceContext;
ID3D11RenderTargetView* renderTargetView;
bool InitializeDirect3D(HWND hwnd) {
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
HRESULT hr = D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
nullptr,
0,
D3D11_SDK_VERSION,
&swapChainDesc,
&swapChain,
&device,
nullptr,
&deviceContext
);
if (FAILED(hr)) {
std::cerr << "Failed to create device and swap chain." << std::endl;
return false;
}
ID3D11Texture2D* backBuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
device->CreateRenderTargetView(backBuffer, nullptr, &renderTargetView);
backBuffer->Release();
deviceContext->OMSetRenderTargets(1, &renderTargetView, nullptr);
return true;
}
void Render() {
float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
deviceContext->ClearRenderTargetView(renderTargetView, clearColor);
swapChain->Present(1, 0);
}
bool IsKeyPressed(int key) {
return GetAsyncKeyState(key) & 0x8000;
}
void GameLoop() {
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
if (IsKeyPressed(VK_ESCAPE)) {
PostQuitMessage(0);
}
Render();
}
}
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "GameWindowClass";
RegisterClassEx(&wc);
HWND hwnd = CreateWindowEx(
0,
"GameWindowClass",
"DirectX Game Engine",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
nullptr, nullptr, hInstance, nullptr
);
ShowWindow(hwnd, nCmdShow);
if (!InitializeDirect3D(hwnd)) {
return -1;
}
GameLoop();
return 0;
}Conclusió
En aquest tema, hem après a construir un motor de joc bàsic utilitzant DirectX. Hem cobert la inicialització de Direct3D, la renderització bàsica, la gestió d'entrada de l'usuari i el desenvolupament del bucle principal del joc. Aquestes són les bases sobre les quals pots construir un motor de joc més complex i funcional. En els següents temes, explorarem com integrar altres components com la física, l'àudio i la xarxa per completar el nostre motor de joc.
Curs de Programació DirectX
Mòdul 1: Introducció a DirectX
- Què és DirectX?
- Configuració de l'Entorn de Desenvolupament
- Comprendre l'API de DirectX
- Crear la Teva Primera Aplicació DirectX
Mòdul 2: Conceptes Bàsics de Direct3D
- Introducció a Direct3D
- Inicialitzar Direct3D
- Renderitzar un Triangle
- Gestionar el Bucle de Renderització
Mòdul 3: Treballar amb Shaders
Mòdul 4: Tècniques Avançades de Renderització
Mòdul 5: Models 3D i Animació
Mòdul 6: Optimització del Rendiment
- Perfilat i Depuració
- Optimitzar el Rendiment de la Renderització
- Gestió de Memòria
- Multifil en DirectX
