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