いちばんやさしいゲームの作り方

文系の人でも、数理学がわからない人でもゲームプログラミングをマスターできるブログ

ゲーム用のウィンドウ( Windows フォーム)を作るサンプルです。

決して誇れるソースではありませんが、学習目的にはよいかと。

/**
 * @file
 * @brief ゲーム用 Windows アプリケーションの雛形
 *
 * Windows 用のゲームを作成する際の雛形ファイル。
 *
 * @author Byerkut
 * @see http://www.game-create.com/menu/downloads
 * @date 2008/08/23
 * @version $Id: $
 */

// 国際化する
#ifndef _UNICODE

#define _UNICODE

#endif
#ifndef UNICODE

#define UNICODE

#endif

// 依存ライブラリをリンカに教える
#pragma comment(lib, "kernel32.lib")

#pragma comment(lib, "user32.lib")

#pragma comment(lib, "gdi32.lib")

#pragma comment(lib, "winmm.lib")


// Win32API の開発環境をインポート
#include <windows.h>

#include <tchar.h>

#include <mmsystem.h>


/*======================================================================
 * アプリケーションの設定となる変数
 *======================================================================*/

/// ウィンドウクラス名
static LPCTSTR g_lpClassName      = TEXT("GameForm");

/// タイトルバーの文字列
static LPCTSTR g_lpTitleBar       = TEXT("タイトルバーの文字列");

/// クライアントサイズの幅
static const int g_nClientWidth   = 640;

/// クライアントサイズの高さ
static const int g_nClientHeight  = 480;

/// フレームレート
static const int g_nFPS           = 60;

/// 最大フレームスキップ数
static const int g_nMaxFrameSkip  = 4;

/// フレームスキップ時に描画処理を実行するかの真偽値
static const BOOL g_bDrawWhenSkip = FALSE;

/*======================================================================
 * ゲームのロジック
 *======================================================================*/

/**
 * @brief ゲームの初期化処理
 *
 * @param[in] hWindow     ウィンドウハンドル
 * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル
 */
void InitializeGame(HWND hWindow, HDC hBackBuffer)
{
}

/**
 * @brief ゲームの終了処理
 *
 * @param[in] hWindow     ウィンドウハンドル
 * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル
 */
void FinalizeGame(HWND hWindow, HDC hBackBuffer)
{
}

/**
 * @brief ゲームの更新処理
 *
 * @param[in] hWindow     ウィンドウハンドル
 * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル
 */
void UpdateGame(HWND hWindow, HDC hBackBuffer)
{
}

/**
 * @brief ゲームの描画処理
 *
 * @param[in] hWindow     ウィンドウハンドル
 * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル
 */
void DrawGame(HWND hWindow, HDC hBackBuffer)
{
}

/**
 * @brief ゲームがフォーカスを得たときの処理
 *
 * @param[in] hWindow     ウィンドウハンドル
 * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル
 */
void ActivateGame(HWND hWindow, HDC hBackBuffer)
{
}

/**
 * @brief ゲームがフォーカスを失ったときの処理
 *
 * @param[in] hWindow     ウィンドウハンドル
 * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル
 */
void DeactivateGame(HWND hWindow, HDC hBackBuffer)
{
}

/**
 * @brief ゲームが閉じられようとしているときの処理
 *
 * @param[in] hWindow ウィンドウハンドル
 *
 * @return            ゲームを終了して良いかの真偽値
 */
BOOL CloseGame(HWND hWindow)
{
  int result;
  result = MessageBox(hWindow, TEXT("ゲームを終了してよろしいですか?"),
                      TEXT("ゲームの終了確認"), MB_YESNO);
  return (result == IDYES) ? TRUE: FALSE;
}

/*======================================================================
 * 内部で使うグローバル変数
 *======================================================================*/

/// バックバッファのデバイスコンテキスト
static HDC g_hBackBuffer     = NULL;

/// バックバッファビットマップ
static HBITMAP g_hBitmap     = NULL;
static HBITMAP g_hOldBitmap  = NULL;

/// バックバッファポインタ
static LPVOID g_lpBackBuffer = NULL;

/*======================================================================
 * 内部で使う関数
 *======================================================================*/

/**
 * @brief ウィンドウプロシージャ用のコールバック関数
 *
 * @param[in] hWindow ウィンドウハンドル
 * @param[in] message メッセージ内容
 * @param[in] wParam  補助パラメータ
 * @param[in] lParam  補助パラメータ
 *
 * @return            メッセージ処理の結果
 */
LRESULT CALLBACK WindowProcedure(HWND hWindow, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {
  case WM_CREATE:
    // バックバッファの構築
    {
      BITMAPINFO bmpInfo = { 0 };
      HDC hdc;

      // DIBSection の設定情報
      ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));
      bmpInfo.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
      bmpInfo.bmiHeader.biWidth       = g_nClientWidth;
      bmpInfo.bmiHeader.biHeight      = -g_nClientHeight;
      bmpInfo.bmiHeader.biPlanes      = 1;
      bmpInfo.bmiHeader.biBitCount    = 32;
      bmpInfo.bmiHeader.biCompression = BI_RGB;

      // DIBSection の構築
      g_hBitmap = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS,
                                   (LPVOID*)(&g_lpBackBuffer), NULL, 0);

      // バックバッファの HDC を取得して DIBSection を割り付ける
      hdc           = GetDC(hWindow);
      g_hBackBuffer = CreateCompatibleDC(hdc);
      g_hOldBitmap  = static_cast<HBITMAP>(SelectObject(g_hBackBuffer, g_hBitmap));
      ReleaseDC(hWindow, hdc);
    }
    // ゲームシステムの初期化
    {
      InitializeGame(hWindow, g_hBackBuffer);
    }
    break;
  case WM_ACTIVATE:
    if (wParam == WA_ACTIVE) {
      // ゲームシステムのアクティブ化
      ActivateGame(hWindow, g_hBackBuffer);
    } else {
      // ゲームシステムの非アクティブ化
      DeactivateGame(hWindow, g_hBackBuffer);
    }
    break;
  case WM_PAINT:
    // バックバッファのフリップ(でも本当はただのコピー)
    {
      PAINTSTRUCT ps = { 0 };
      HDC hdc = BeginPaint(hWindow, &ps);
      BitBlt(hdc, 0, 0, g_nClientWidth, g_nClientHeight, g_hBackBuffer, 0, 0, SRCCOPY);
      EndPaint(hWindow, &ps);
    }
    break;
  case WM_CLOSE:
    // ゲームの終了確認
    if (!CloseGame(hWindow)) {
      break;
    }
    DestroyWindow(hWindow);
    break;
  case WM_DESTROY:
    // ゲームシステムの終了
    {
      FinalizeGame(hWindow, g_hBackBuffer);
    }
    // バックバッファの解放
    {
      SelectObject(g_hBackBuffer ,g_hOldBitmap);
      DeleteObject(g_hBitmap);
      DeleteDC(g_hBackBuffer);
    }
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hWindow, message, wParam, lParam);
  }

  return 0;
}

/**
 * @brief アプリケーションのエントリーポイント
 *
 * @param[in] hInstance         現在のインスタンスハンドル
 * @param[in] hPreviousInstance 過去のインスタンスハンドル
 * @param[in] lpCommandLine     コマンドライン引数
 * @param[in] nCommandShow      ウィンドウ状態
 *
 * @return アプリケーションの終了コード
 */
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreviousInstance,
                     LPTSTR lpCommandLine, int nCommandShow)
{
  // 多重起動の検出
  HANDLE hMutex = CreateMutex(NULL, TRUE, g_lpClassName);
  if (GetLastError() == ERROR_ALREADY_EXISTS) {
    return 0;
  }

  // ウィンドウクラスの登録
  {
    WNDCLASSEX wc = { 0 };

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WindowProcedure;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         =
      static_cast<HICON>(LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION),
                                   IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
    wc.hCursor       =
      static_cast<HCURSOR>(LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW),
                                     IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_lpClassName;
    wc.hIconSm       =
      static_cast<HICON>(LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION),
                                   IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));

    if (!RegisterClassEx(&wc)) {
      return 0;
    }
  }

  // ウィンドウの作成
  HWND hWindow;
  {
    unsigned int nWindowWidth = g_nClientWidth
      + GetSystemMetrics(SM_CXEDGE)
      + GetSystemMetrics(SM_CXBORDER)
      + GetSystemMetrics(SM_CXFIXEDFRAME);
    unsigned int nWindowHeight = g_nClientHeight
      + GetSystemMetrics(SM_CYEDGE)
      + GetSystemMetrics(SM_CYBORDER)
      + GetSystemMetrics(SM_CYFIXEDFRAME)
      + GetSystemMetrics(SM_CYCAPTION);

    hWindow = CreateWindow(g_lpClassName, g_lpTitleBar,
                           WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
                           GetSystemMetrics(SM_CXSCREEN) / 2 - nWindowWidth / 2,
                           GetSystemMetrics(SM_CYSCREEN) / 2 - nWindowHeight / 2,
                           nWindowWidth, nWindowHeight, NULL, NULL, hInstance, NULL);
    
    if (!hWindow) {
      return 0;
    }

    ShowWindow(hWindow, nCommandShow);
    UpdateWindow(hWindow);
  }

  // メッセージループ
  MSG msg = { 0 };
  {
    DWORD    frame    = 0;
    DWORD    interval = 1000 / g_nFPS;
    DWORD    last     = 0;
    DWORD    skipped  = 0;
    DWORD    passage  = 0;
    TIMECAPS caps     = { 0 };

    timeGetDevCaps(&caps, sizeof(TIMECAPS));
    timeBeginPeriod(caps.wPeriodMin);

    while (msg.message != WM_QUIT) {
      if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      } else {
        frame = timeGetTime() / interval;
        passage = frame - last;
        if ((passage == 1) ||
            (1 <passage && g_nMaxFrameSkip <= skipped)) {
          // 1フレームごとの処理
          // またはフレームスキップ数が限界値を超えたときの処理
          UpdateGame(hWindow, g_hBackBuffer); // ゲームの更新処理
          DrawGame(hWindow, g_hBackBuffer);   // ゲームの描画処理
          InvalidateRect(hWindow, NULL, TRUE);
          UpdateWindow(hWindow);
          skipped = 0;
          last = frame;
        } else if (1 <passage) {
          // 前のフレームに時間がかかりすぎて
          // フレームスキップが必要な場合の処理
          UpdateGame(hWindow, g_hBackBuffer); // ゲームの更新処理
          if (g_bDrawWhenSkip) {
            DrawGame(hWindow, g_hBackBuffer); // ゲームの描画処理
          }
          skipped++;
          last = frame;
        } else {
          Sleep(1);
        }
      }
    }

    timeEndPeriod(caps.wPeriodMin);
  }

  CloseHandle(hMutex);

  return msg.wParam;
}

// EOF

まず、先頭のグローバル変数を制作するゲームにあわせて変更します。

/*======================================================================
 * アプリケーションの設定となる変数
 *======================================================================*/

/// ウィンドウクラス名
static LPCTSTR g_lpClassName      = TEXT("GameForm");

/// タイトルバーの文字列
static LPCTSTR g_lpTitleBar       = TEXT("タイトルバーの文字列");

/// クライアントサイズの幅
static const int g_nClientWidth   = 640;

/// クライアントサイズの高さ
static const int g_nClientHeight  = 480;

/// フレームレート
static const int g_nFPS           = 60;

/// 最大フレームスキップ数
static const int g_nMaxFrameSkip  = 4;

/// フレームスキップ時に描画処理を実行するかの真偽値
static const BOOL g_bDrawWhenSkip = FALSE;

各グローバル変数の意味は次の表の通りです。

変数名 意味
g_lpClassName ウィンドウクラスの名前です。よくわからない人はゲームの名前を英語表記で設定してください。ちなみに変更しなくても動きます。
g_lpTitleBar ウィンドウのタイトルバーに表示される文字列です。
g_nClientWidth ウィンドウの内部の横幅です。 640×480 の解像度のゲームを作る場合は 640 と指定します。
g_nClientHeight ウィンドウの内部の高さです。 640×480 の解像度のゲームを作る場合は 480 と指定します。
g_nFPS フレームレートです。1秒間に画面を書き換える回数です。 30 から 60 くらいが良いと思います。
g_nMaxFrameSkip 連続でフレームスキップする最高のフレーム数です。
g_bDrawWhenSkip フレームスキップ時に描画処理を実行するかの真偽値です。

次に、あらかじめ用意されている空の関数にゲームの具体的な処理を実装していきます。ちなみにデフォルトの状態でもプログラムは動作しますので、不要な処理は無理に実装する必要はありません。

void InitializeGame(HWND hWindow, HDC hBackBuffer)
{
}

ゲームの初期化時に実行したい処理を記述します。

void FinalizeGame(HWND hWindow, HDC hBackBuffer)
{
}

ゲームの終了時に実行したい処理を記述します。

void UpdateGame(HWND hWindow, HDC hBackBuffer)
{
}

ゲームの更新に関する処理を記述します。

void DrawGame(HWND hWindow, HDC hBackBuffer)
{
}

ゲームの描画に関する処理を記述します。

void ActivateGame(HWND hWindow, HDC hBackBuffer)
{
}

ウィンドウがフォーカスを得たときの処理を記述します。

void DeactivateGame(HWND hWindow, HDC hBackBuffer)
{
}

ウィンドウがフォーカスを失ったときの処理を記述します。

BOOL CloseGame(HWND hWindow)
{
  int result;
  result = MessageBox(hWindow,
                      TEXT("ゲームを終了してよろしいですか?"),
                      TEXT("ゲームの終了確認"), MB_YESNO);
  return (result == IDYES) ? TRUE: FALSE;
}

閉じるボタンが押されたときの処理を記述します。もし、終了したくない場合は FALSE を返してください。デフォルトでは確認メッセージを表示して「はい」が押されたときはゲームを終了し、「いいえ」が押されたときはゲームを続行します。

Contributions




Trackback URL

メルマガ登録・解除
 
挫折不可能!初級ゲームプログラミング完全マニュアル
RSS track feedいちばんやさしいゲームの作り方 TECHNORATI お気に入りに追加する
フィードメーター - いちばんやさしいゲームの作り方 ブログSEO対策:track word カウンター
Firefox meter あわせて読みたい SEO STATUS
このページの先頭へ