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:

  1. Sistema de Renderització: Responsable de dibuixar gràfics a la pantalla.
  2. Gestió d'Entrada: Captura i processa les entrades de l'usuari (teclat, ratolí, controladors).
  3. Sistema de Física: Simula la física del món del joc.
  4. Gestió d'Escena: Organitza i gestiona els objectes del joc.
  5. Àudio: Maneja els efectes de so i la música.
  6. 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:

#include <windows.h>

bool IsKeyPressed(int key) {
    return GetAsyncKeyState(key) & 0x8000;
}

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

  1. Inicialitza Direct3D en una finestra.
  2. Renderitza un color de fons a la pantalla.
  3. 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.

© Copyright 2024. Tots els drets reservats