C言語で始めるDirectXゲーム開発: 基礎から実践まで徹底解説

この記事では、C言語を用いたDirectXを使ったゲーム開発の基礎から実践までを詳しく解説します。初心者でも理解しやすいように段階的に説明し、具体的なコード例を通じて実際の開発プロセスを体験していただきます。ゲーム開発に興味がある方は、このガイドを通じて自分のアイデアを形にしていきましょう。

目次

DirectXの基礎知識

DirectXは、Microsoftによって開発されたマルチメディアプラットフォームです。主にゲーム開発に使用され、Windows環境での高性能なグラフィックやサウンドの処理を可能にします。DirectXは、Direct3D(3Dグラフィックス)、DirectInput(入力デバイス)、DirectSound(サウンド)など、複数のコンポーネントで構成されています。

DirectXの歴史

DirectXは1995年に初めてリリースされ、その後継続的にバージョンアップされてきました。最新のDirectX 12では、マルチスレッド処理や低レベルハードウェアアクセスが強化され、より高度なパフォーマンスが実現されています。

DirectXの用途

DirectXは主にゲーム開発に使用されますが、その他にも3Dモデリング、シミュレーション、ビデオ編集など、多くのマルチメディアアプリケーションで利用されています。その広範な機能と性能から、プロフェッショナルからアマチュアまで幅広いユーザーに支持されています。

DirectXを使用する理由

DirectXを使用することで、Windows環境での高度なグラフィックス処理が簡単に実現できます。クロスプラットフォームのOpenGLと比較して、DirectXはWindows専用であるため、Windows上での最適化が進んでおり、より高いパフォーマンスを発揮します。また、DirectXの豊富なドキュメントとサポートも、開発者にとって大きなメリットです。

開発環境の構築

DirectXを用いたC言語のゲーム開発を始めるためには、適切な開発環境を整える必要があります。ここでは、必要なツールやライブラリのインストール方法を説明します。

Visual Studioのインストール

まず、Microsoftの統合開発環境であるVisual Studioをインストールします。Visual Studioは、C言語およびDirectXの開発に最適なツールで、豊富な機能とデバッグツールが揃っています。

  1. Visual Studioの公式サイトにアクセスし、最新のバージョンをダウンロードします。
  2. インストーラーを起動し、「Desktop development with C++」ワークロードを選択してインストールします。

DirectX SDKのインストール

次に、DirectXのソフトウェア開発キット(SDK)をインストールします。最新のDirectXランタイムは、Windows SDKに含まれていますが、必要に応じて古いバージョンのSDKも利用できます。

  1. Windows SDKのダウンロードページにアクセスし、Windows SDKをダウンロードしてインストールします。
  2. 古いDirectX SDKが必要な場合は、DirectX SDK (June 2010)をダウンロードしてインストールします。

プロジェクトの作成

Visual Studioで新しいDirectXプロジェクトを作成します。ここでは、基本的なプロジェクトの設定方法を説明します。

  1. Visual Studioを起動し、「新しいプロジェクトの作成」を選択します。
  2. 「Win32コンソールアプリケーション」を選択し、プロジェクト名と保存場所を指定して「作成」をクリックします。
  3. プロジェクトが作成されたら、「ソリューションエクスプローラー」でプロジェクトを右クリックし、「プロパティ」を選択します。
  4. 「VC++ディレクトリ」内の「インクルードディレクトリ」と「ライブラリディレクトリ」に、DirectX SDKのパスを追加します。

初期設定の確認

最後に、DirectX開発に必要な初期設定が正しく行われているか確認します。

  1. プロジェクトに必要なDirectXヘッダーファイルをインクルードします。
   #include <windows.h>
   #include <d3d11.h>
   #pragma comment (lib, "d3d11.lib")
  1. プロジェクトをビルドし、エラーがないことを確認します。

これで、DirectXを用いたC言語のゲーム開発環境が整いました。次は、初めてのDirectXプログラムを作成し、基本的な画面表示を実現してみましょう。

初めてのDirectXプログラム

ここでは、最初のDirectXプログラムを作成し、基本的な画面表示を実現します。DirectXの基本的な使い方を学び、簡単なウィンドウを表示させる手順を説明します。

プロジェクトのセットアップ

まず、Visual Studioで新しいプロジェクトをセットアップします。既に開発環境が整っている前提で進めます。

  1. Visual Studioで新しいプロジェクトを作成し、「Win32コンソールアプリケーション」を選択します。
  2. プロジェクトが作成されたら、必要なDirectXヘッダーファイルをインクルードします。
   #include <windows.h>
   #include <d3d11.h>
   #pragma comment (lib, "d3d11.lib")

ウィンドウの作成

次に、ウィンドウを作成し、DirectXの初期化を行います。

  1. WinMain関数を定義し、ウィンドウの設定を行います。
   LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

   int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
       WNDCLASSEX wc;
       HWND hWnd;
       MSG msg;

       wc.cbSize = sizeof(WNDCLASSEX);
       wc.style = CS_HREDRAW | CS_VREDRAW;
       wc.lpfnWndProc = WindowProc;
       wc.cbClsExtra = 0;
       wc.cbWndExtra = 0;
       wc.hInstance = hInstance;
       wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
       wc.hCursor = LoadCursor(NULL, IDC_ARROW);
       wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
       wc.lpszMenuName = NULL;
       wc.lpszClassName = "WindowClass";
       wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

       RegisterClassEx(&wc);

       hWnd = CreateWindowEx(NULL, "WindowClass", "Hello, DirectX", WS_OVERLAPPEDWINDOW, 300, 300, 800, 600, NULL, NULL, hInstance, NULL);

       ShowWindow(hWnd, nShowCmd);

       // メッセージループ
       while (GetMessage(&msg, NULL, 0, 0)) {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }

       return msg.wParam;
   }
  1. ウィンドウプロシージャを定義し、ウィンドウメッセージを処理します。
   LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
       switch (message) {
           case WM_DESTROY:
               PostQuitMessage(0);
               return 0;
           default:
               return DefWindowProc(hWnd, message, wParam, lParam);
       }
   }

DirectXの初期化

DirectX 11のデバイスとスワップチェインを初期化します。

  1. DirectXのデバイスとスワップチェインの設定を行います。
   IDXGISwapChain *swapChain;
   ID3D11Device *dev;
   ID3D11DeviceContext *devcon;
   ID3D11RenderTargetView *backbuffer;

   void InitD3D(HWND hWnd) {
       DXGI_SWAP_CHAIN_DESC scd;

       ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

       scd.BufferCount = 1;
       scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
       scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
       scd.OutputWindow = hWnd;
       scd.SampleDesc.Count = 4;
       scd.Windowed = TRUE;

       D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL, D3D11_SDK_VERSION, &scd, &swapChain, &dev, NULL, &devcon);

       ID3D11Texture2D *pBackBuffer;
       swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
       dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
       pBackBuffer->Release();

       devcon->OMSetRenderTargets(1, &backbuffer, NULL);
   }
  1. DirectXのレンダリングループを実装します。
   void RenderFrame(void) {
       devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
       swapChain->Present(0, 0);
   }
  1. メインループでレンダリングフレームを呼び出します。
   while (TRUE) {
       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           if (msg.message == WM_QUIT)
               break;
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
       else {
           RenderFrame();
       }
   }

プログラムのビルドと実行

すべてのコードを統合し、プロジェクトをビルドして実行します。正常にウィンドウが表示され、背景色が設定されたことを確認します。これで、DirectXを使った基本的な画面表示が実現しました。次は、2Dゲームの開発に進みます。

2Dゲームの開発

ここでは、簡単な2Dゲームを作成し、スプライトやアニメーションの実装方法を学びます。具体的な例として、キャラクターが画面内を移動するシンプルなゲームを作成します。

スプライトの準備

まず、ゲーム内で使用するスプライト(キャラクターや背景画像)を準備します。スプライトは、2Dグラフィックスを表現するための基本的な要素です。

  1. 必要な画像ファイル(例:キャラクター.png、背景.png)をプロジェクトのリソースフォルダに追加します。
  2. スプライトを読み込むための関数を定義します。
   ID3D11ShaderResourceView *LoadTexture(const wchar_t *filename) {
       ID3D11ShaderResourceView *texture;
       D3DX11CreateShaderResourceViewFromFile(dev, filename, NULL, NULL, &texture, NULL);
       return texture;
   }

スプライトの描画

次に、スプライトを画面に描画するためのコードを追加します。

  1. 頂点バッファを設定し、スプライトの頂点データを定義します。
   struct VERTEX {
       FLOAT X, Y, Z;
       D3DXCOLOR Color;
   };

   VERTEX OurVertices[] = {
       { -0.5f, -0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) },
       {  0.5f, -0.5f, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f) },
       {  0.5f,  0.5f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f) },
       { -0.5f,  0.5f, 0.0f, D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f) },
   };

   D3D11_BUFFER_DESC bd;
   ZeroMemory(&bd, sizeof(bd));
   bd.Usage = D3D11_USAGE_DYNAMIC;
   bd.ByteWidth = sizeof(VERTEX) * 4;
   bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
   bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

   ID3D11Buffer *pVBuffer;
   dev->CreateBuffer(&bd, NULL, &pVBuffer);

   D3D11_MAPPED_SUBRESOURCE ms;
   devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
   memcpy(ms.pData, OurVertices, sizeof(OurVertices));
   devcon->Unmap(pVBuffer, NULL);
  1. スプライトを描画するシェーダーを設定します。
   // シェーダーの設定コードは省略
  1. 描画関数でスプライトを画面に表示します。
   void RenderFrame(void) {
       devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

       UINT stride = sizeof(VERTEX);
       UINT offset = 0;
       devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);

       devcon->Draw(4, 0);

       swapChain->Present(0, 0);
   }

キャラクターの移動とアニメーション

キャラクターが画面内を移動し、アニメーションするようにします。

  1. キーボード入力を処理するためのコードを追加します。
   void ProcessInput() {
       if (GetAsyncKeyState(VK_UP) & 0x8000) {
           // キャラクターを上に移動
       }
       if (GetAsyncKeyState(VK_DOWN) & 0x8000) {
           // キャラクターを下に移動
       }
       if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
           // キャラクターを左に移動
       }
       if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
           // キャラクターを右に移動
       }
   }
  1. アニメーションフレームを切り替えるためのタイマーを設定します。
   void UpdateAnimation() {
       static DWORD lastTime = timeGetTime();
       DWORD currentTime = timeGetTime();
       if (currentTime - lastTime > 100) {
           // アニメーションフレームを更新
           lastTime = currentTime;
       }
   }
  1. メインループで入力処理とアニメーション更新を呼び出します。
   while (TRUE) {
       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           if (msg.message == WM_QUIT)
               break;
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
       else {
           ProcessInput();
           UpdateAnimation();
           RenderFrame();
       }
   }

これで、基本的な2Dゲームが完成しました。次は、ユーザー入力の処理を詳しく見ていきます。

ユーザー入力の処理

ここでは、キーボードやマウスからの入力を処理し、ゲームに反映させる方法を解説します。ユーザーの入力を受け取ってキャラクターを動かしたり、アクションを実行したりする基本的な手法を学びます。

キーボード入力の処理

キーボード入力を取得して、キャラクターの移動や操作を行います。

  1. キーボード入力を処理する関数を定義します。
   void ProcessKeyboardInput() {
       if (GetAsyncKeyState(VK_UP) & 0x8000) {
           // キャラクターを上に移動
           characterY -= 5;
       }
       if (GetAsyncKeyState(VK_DOWN) & 0x8000) {
           // キャラクターを下に移動
           characterY += 5;
       }
       if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
           // キャラクターを左に移動
           characterX -= 5;
       }
       if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
           // キャラクターを右に移動
           characterX += 5;
       }
   }
  1. メインループ内でキーボード入力の処理を呼び出します。
   while (TRUE) {
       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           if (msg.message == WM_QUIT)
               break;
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
       else {
           ProcessKeyboardInput();
           UpdateAnimation();
           RenderFrame();
       }
   }

マウス入力の処理

次に、マウス入力を処理して、ゲームに反映させる方法を説明します。

  1. マウス入力を処理するための関数を定義します。
   void ProcessMouseInput() {
       POINT cursorPos;
       GetCursorPos(&cursorPos);
       ScreenToClient(hWnd, &cursorPos);

       if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) {
           // 左クリックされた場合の処理
           characterX = cursorPos.x;
           characterY = cursorPos.y;
       }
   }
  1. メインループ内でマウス入力の処理を呼び出します。
   while (TRUE) {
       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           if (msg.message == WM_QUIT)
               break;
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
       else {
           ProcessKeyboardInput();
           ProcessMouseInput();
           UpdateAnimation();
           RenderFrame();
       }
   }

入力の統合とゲームへの反映

キーボードとマウス入力を統合し、ゲーム内のキャラクターの動きやアクションに反映させます。

  1. キーボードおよびマウスの入力を統合する関数を作成します。
   void ProcessInput() {
       ProcessKeyboardInput();
       ProcessMouseInput();
   }
  1. メインループ内で統合された入力処理を呼び出します。
   while (TRUE) {
       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           if (msg.message == WM_QUIT)
               break;
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
       else {
           ProcessInput();
           UpdateAnimation();
           RenderFrame();
       }
   }

これで、ユーザー入力を処理し、キャラクターの動きやゲーム内のアクションに反映させることができました。次は、ゲームにサウンドを追加し、効果音やBGMの再生方法を説明します。

サウンドの追加

ここでは、ゲームにサウンドを追加し、効果音やBGM(背景音楽)の再生方法を説明します。DirectXを使用してサウンドを管理する方法を学びます。

DirectSoundの初期化

まず、DirectSoundを初期化し、サウンドデバイスをセットアップします。

  1. DirectSoundヘッダーファイルをインクルードします。
   #include <dsound.h>
  1. DirectSoundオブジェクトとサウンドバッファーを定義します。
   IDirectSound8* pDirectSound;
   IDirectSoundBuffer* pPrimaryBuffer;
   IDirectSoundBuffer* pSecondaryBuffer;
  1. DirectSoundを初期化し、プライマリバッファーを設定します。
   bool InitDirectSound(HWND hWnd) {
       if (FAILED(DirectSoundCreate8(NULL, &pDirectSound, NULL))) {
           return false;
       }
       if (FAILED(pDirectSound->SetCooperativeLevel(hWnd, DSSCL_PRIORITY))) {
           return false;
       }

       DSBUFFERDESC bufferDesc;
       ZeroMemory(&bufferDesc, sizeof(DSBUFFERDESC));
       bufferDesc.dwSize = sizeof(DSBUFFERDESC);
       bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
       bufferDesc.dwBufferBytes = 0;
       bufferDesc.lpwfxFormat = NULL;

       if (FAILED(pDirectSound->CreateSoundBuffer(&bufferDesc, &pPrimaryBuffer, NULL))) {
           return false;
       }

       WAVEFORMATEX waveFormat;
       ZeroMemory(&waveFormat, sizeof(WAVEFORMATEX));
       waveFormat.wFormatTag = WAVE_FORMAT_PCM;
       waveFormat.nSamplesPerSec = 44100;
       waveFormat.wBitsPerSample = 16;
       waveFormat.nChannels = 2;
       waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
       waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;

       if (FAILED(pPrimaryBuffer->SetFormat(&waveFormat))) {
           return false;
       }

       return true;
   }

サウンドファイルの読み込み

次に、サウンドファイルを読み込み、セカンダリバッファーに設定します。

  1. WAVファイルを読み込む関数を定義します。
   bool LoadWaveFile(const char* filename, IDirectSoundBuffer8** buffer) {
       FILE* filePtr;
       WaveHeaderType waveFileHeader;
       WAVEFORMATEX waveFormat;
       DSBUFFERDESC bufferDesc;
       IDirectSoundBuffer* tempBuffer;
       unsigned char* waveData;
       unsigned char* bufferPtr;
       unsigned long bufferSize;

       if (fopen_s(&filePtr, filename, "rb") != 0) {
           return false;
       }

       fread(&waveFileHeader, sizeof(WaveHeaderType), 1, filePtr);

       if ((waveFileHeader.format[0] != 'W') || (waveFileHeader.format[1] != 'A') || 
           (waveFileHeader.format[2] != 'V') || (waveFileHeader.format[3] != 'E')) {
           return false;
       }

       waveFormat.wFormatTag = WAVE_FORMAT_PCM;
       waveFormat.nSamplesPerSec = waveFileHeader.sampleRate;
       waveFormat.wBitsPerSample = waveFileHeader.bitsPerSample;
       waveFormat.nChannels = waveFileHeader.numChannels;
       waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
       waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
       waveFormat.cbSize = 0;

       bufferDesc.dwSize = sizeof(DSBUFFERDESC);
       bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME;
       bufferDesc.dwBufferBytes = waveFileHeader.dataSize;
       bufferDesc.lpwfxFormat = &waveFormat;

       if (FAILED(pDirectSound->CreateSoundBuffer(&bufferDesc, &tempBuffer, NULL))) {
           return false;
       }

       if (FAILED(tempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&*buffer))) {
           return false;
       }

       tempBuffer->Release();
       tempBuffer = 0;

       fseek(filePtr, sizeof(WaveHeaderType), SEEK_SET);

       waveData = new unsigned char[waveFileHeader.dataSize];
       fread(waveData, 1, waveFileHeader.dataSize, filePtr);

       fclose(filePtr);

       (*buffer)->Lock(0, waveFileHeader.dataSize, (void**)&bufferPtr, (DWORD*)&bufferSize, NULL, 0, 0);
       memcpy(bufferPtr, waveData, waveFileHeader.dataSize);
       (*buffer)->Unlock((void*)bufferPtr, bufferSize, NULL, 0);

       delete[] waveData;
       waveData = 0;

       return true;
   }

サウンドの再生

サウンドバッファーを再生するためのコードを追加します。

  1. サウンドバッファーを再生する関数を定義します。
   void PlayWaveFile() {
       pSecondaryBuffer->SetCurrentPosition(0);
       pSecondaryBuffer->SetVolume(DSBVOLUME_MAX);
       pSecondaryBuffer->Play(0, 0, 0);
   }
  1. サウンドを再生するタイミングをメインループに追加します。
   while (TRUE) {
       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           if (msg.message == WM_QUIT)
               break;
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
       else {
           ProcessInput();
           UpdateAnimation();
           RenderFrame();

           // サウンドの再生
           if (condition_for_playing_sound) {
               PlayWaveFile();
           }
       }
   }

これで、ゲームにサウンドを追加し、効果音やBGMの再生が可能になりました。次は、3Dゲームの基礎を学び、3D空間でのオブジェクトの表示や操作方法について説明します。

3Dゲームの基礎

ここでは、3D空間でのオブジェクトの表示や操作方法について基本を学びます。簡単な3Dモデルを表示し、カメラの設定や基本的な操作を実装します。

Direct3Dの初期化

3D描画のためにDirect3Dを初期化します。必要なデバイスやスワップチェインを設定し、基本的なレンダリング環境を整えます。

  1. Direct3Dデバイスとスワップチェインの設定を行います。
   IDXGISwapChain *swapChain;
   ID3D11Device *dev;
   ID3D11DeviceContext *devcon;
   ID3D11RenderTargetView *backbuffer;
   ID3D11DepthStencilView *zbuffer;

   void InitD3D(HWND hWnd) {
       DXGI_SWAP_CHAIN_DESC scd;
       ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

       scd.BufferCount = 1;
       scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
       scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
       scd.OutputWindow = hWnd;
       scd.SampleDesc.Count = 4;
       scd.Windowed = TRUE;
       scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

       D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL, D3D11_SDK_VERSION, &scd, &swapChain, &dev, NULL, &devcon);

       ID3D11Texture2D *pBackBuffer;
       swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
       dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
       pBackBuffer->Release();

       D3D11_TEXTURE2D_DESC depthStencilDesc;
       depthStencilDesc.Width = 800;
       depthStencilDesc.Height = 600;
       depthStencilDesc.MipLevels = 1;
       depthStencilDesc.ArraySize = 1;
       depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
       depthStencilDesc.SampleDesc.Count = 4;
       depthStencilDesc.SampleDesc.Quality = D3D11_STANDARD_MULTISAMPLE_PATTERN;
       depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
       depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
       depthStencilDesc.CPUAccessFlags = 0;
       depthStencilDesc.MiscFlags = 0;

       ID3D11Texture2D *pDepthStencil;
       dev->CreateTexture2D(&depthStencilDesc, NULL, &pDepthStencil);
       dev->CreateDepthStencilView(pDepthStencil, NULL, &zbuffer);
       pDepthStencil->Release();

       devcon->OMSetRenderTargets(1, &backbuffer, zbuffer);
   }

簡単な3Dモデルの表示

3Dモデルを表示するための頂点データを定義し、シェーダーを使ってレンダリングします。

  1. 頂点データを定義します。
   struct VERTEX {
       FLOAT X, Y, Z;
       D3DXCOLOR Color;
   };

   VERTEX OurVertices[] = {
       { 0.0f, 1.0f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) },
       { 1.0f, -1.0f, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f) },
       { -1.0f, -1.0f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f) },
   };

   D3D11_BUFFER_DESC bd;
   ZeroMemory(&bd, sizeof(bd));
   bd.Usage = D3D11_USAGE_DYNAMIC;
   bd.ByteWidth = sizeof(VERTEX) * 3;
   bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
   bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

   ID3D11Buffer *pVBuffer;
   dev->CreateBuffer(&bd, NULL, &pVBuffer);

   D3D11_MAPPED_SUBRESOURCE ms;
   devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
   memcpy(ms.pData, OurVertices, sizeof(OurVertices));
   devcon->Unmap(pVBuffer, NULL);
  1. シェーダーを作成し、頂点を描画します。
   // シェーダーコードは省略
  1. レンダリング関数を更新し、3Dモデルを描画します。
   void RenderFrame(void) {
       devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
       devcon->ClearDepthStencilView(zbuffer, D3D11_CLEAR_DEPTH, 1.0f, 0);

       UINT stride = sizeof(VERTEX);
       UINT offset = 0;
       devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
       devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

       devcon->Draw(3, 0);

       swapChain->Present(0, 0);
   }

カメラの設定

カメラを設定し、3D空間を自由に移動できるようにします。

  1. カメラのビュー行列とプロジェクション行列を定義します。
   D3DXMATRIX matView;
   D3DXMATRIX matProjection;

   D3DXMatrixLookAtLH(&matView, &D3DXVECTOR3(0.0f, 2.0f, -5.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f));
   D3DXMatrixPerspectiveFovLH(&matProjection, (FLOAT)D3DX_PI * 0.5f, 800.0f / 600.0f, 0.1f, 100.0f);
  1. シェーダーで使用する行列バッファを作成します。
   struct MATRIX_BUFFER {
       D3DXMATRIX matWorld;
       D3DXMATRIX matView;
       D3DXMATRIX matProjection;
   };

   D3D11_BUFFER_DESC matrixBufferDesc;
   ZeroMemory(&matrixBufferDesc, sizeof(matrixBufferDesc));
   matrixBufferDesc.Usage = D3D11_USAGE_DEFAULT;
   matrixBufferDesc.ByteWidth = sizeof(MATRIX_BUFFER);
   matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
   matrixBufferDesc.CPUAccessFlags = 0;

   ID3D11Buffer* pMatrixBuffer;
   dev->CreateBuffer(&matrixBufferDesc, NULL, &pMatrixBuffer);
  1. レンダリング関数で行列をシェーダーに設定します。
   void RenderFrame(void) {
       devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
       devcon->ClearDepthStencilView(zbuffer, D3D11_CLEAR_DEPTH, 1.0f, 0);

       D3DXMATRIX matWorld;
       D3DXMatrixRotationY(&matWorld, (FLOAT)timeGetTime() / 1000.0f);

       MATRIX_BUFFER matrices;
       matrices.matWorld = matWorld;
       matrices.matView = matView;
       matrices.matProjection = matProjection;

       devcon->UpdateSubresource(pMatrixBuffer, 0, NULL, &matrices, 0, 0);
       devcon->VSSetConstantBuffers(0, 1, &pMatrixBuffer);

       UINT stride = sizeof(VERTEX);
       UINT offset = 0;
       devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
       devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

       devcon->Draw(3, 0);

       swapChain->Present(0, 0);
   }

これで、簡単な3Dモデルを表示し、カメラの設定を行う基本的な3Dゲームの基礎が完成しました。次は、ゲーム開発の応用例や理解を深めるための演習問題を提供します。

応用例と演習問題

ここでは、ゲーム開発の応用例をいくつか紹介し、理解を深めるための演習問題を提供します。これらの例と問題を通じて、DirectXを用いたゲーム開発のスキルをさらに向上させましょう。

応用例

1. 衝突判定の実装

ゲームにおいて、オブジェクト同士の衝突判定は重要な要素です。以下のコード例では、矩形の衝突判定を実装します。

bool CheckCollision(float x1, float y1, float width1, float height1, float x2, float y2, float width2, float height2) {
    return !(x1 > x2 + width2 || x1 + width1 < x2 || y1 > y2 + height2 || y1 + height1 < y2);
}

この関数を使用して、キャラクターが障害物に衝突したかどうかを判定します。

2. パーティクルシステムの実装

パーティクルシステムは、爆発や煙、魔法のエフェクトなどを表現するために使用されます。以下は簡単なパーティクルシステムの例です。

struct Particle {
    float x, y, z;
    float vx, vy, vz;
    float life;
};

Particle particles[MAX_PARTICLES];

void UpdateParticles() {
    for (int i = 0; i < MAX_PARTICLES; ++i) {
        particles[i].x += particles[i].vx;
        particles[i].y += particles[i].vy;
        particles[i].z += particles[i].vz;
        particles[i].life -= 0.1f;
    }
}

void RenderParticles() {
    for (int i = 0; i < MAX_PARTICLES; ++i) {
        if (particles[i].life > 0) {
            // パーティクルの描画コード
        }
    }
}

3. 簡単なAIの実装

敵キャラクターに簡単なAIを追加し、プレイヤーを追跡するようにします。

void UpdateEnemyAI(float playerX, float playerY) {
    if (enemyX < playerX) enemyX += 0.5f;
    if (enemyX > playerX) enemyX -= 0.5f;
    if (enemyY < playerY) enemyY += 0.5f;
    if (enemyY > playerY) enemyY -= 0.5f;
}

演習問題

問題1: 簡単なゲームの作成

上記の例を参考にして、以下の要素を持つ簡単なゲームを作成してください。

  • プレイヤーキャラクターの操作
  • 敵キャラクターの追跡AI
  • アイテムの収集
  • 衝突判定

問題2: パーティクルエフェクトの強化

パーティクルシステムを改良し、よりリアルなエフェクトを作成してください。色の変化や重力の影響を加えることで、エフェクトを豊かにします。

問題3: サウンドのイベント駆動

ゲーム内のイベントに応じてサウンドを再生する機能を追加してください。例えば、プレイヤーがアイテムを収集したときや敵を倒したときに効果音を再生します。

問題4: 3Dモデルの読み込みとアニメーション

外部の3Dモデルを読み込み、アニメーションさせる機能を実装してください。3Dモデルのフォーマット(例:OBJファイル)のパーサーを作成し、モデルデータをDirect3Dでレンダリングします。

これらの応用例と演習問題を通じて、DirectXを用いたゲーム開発の理解を深め、実践的なスキルを身につけてください。

まとめ

この記事では、C言語を用いたDirectXを使ったゲーム開発の基礎から応用までを解説しました。基本的な設定から始まり、2Dおよび3Dゲームの開発、サウンドの追加、ユーザー入力の処理、そして応用例や演習問題を通じて、実践的な知識を学んでいただきました。今後も継続して学習し、さらに高度なゲーム開発に挑戦してみてください。

ゲーム開発は複雑で多岐にわたるスキルを要求しますが、その分やりがいも大きいです。このガイドを活用して、あなたのゲーム開発スキルをさらに磨き、素晴らしいゲームを作成してください。次のステップとして、さらに高度なテクニックや最適化、ネットワークプレイの実装などを学んでいくことをお勧めします。

コメント

コメントする

目次