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

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

だらだらとタイル表示に時間を割いていてはもったいないですよね。…というワケで最低限の処理を全て記述しました。

template.cpp
/**
 * @file
 * @brief Windows アプリケーションの雛形
 *
 * Windows アプリケーションを作成する際の雛形ファイル。
 * コンパイルオプション /EHsc が必要。
 * ライブラリ kernel32.lib user32.lib gdi32.lib winmm.lib が必要。
 *
 * @author Byerkut
 * @date 2007-09-22
 * @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 <mmsystem.h>


#include "tiles.h"


/*======================================================================
 * プロトタイプ宣言
 *======================================================================*/

/// ウィンドウプロシージャ(コールバック関数)
LRESULT CALLBACK WindowProcedure(HWND hWnd,
                                 UINT msg,
                                 WPARAM wp,
                                 LPARAM lp);

/*======================================================================
 * 定数
 *======================================================================*/

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

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

/*======================================================================
 * グローバル変数
 *======================================================================*/

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

/// タイトルバーの文字列
static LPCTSTR szTitleBar = TEXT("15ぱずりゃー");

/// インスタンスのハンドル
static HINSTANCE hInstance = NULL;

/// メインフォームのハンドル
static HWND hWindow = NULL;

/// バックバッファ
static HDC hBackBuffer = NULL;

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

/*======================================================================
 * 関数
 *======================================================================*/

/**
 * アプリケーションのエントリーポイント
 *
 * @param[in] hInstance 現在のインスタンスハンドル
 * @param[in] hPreviousInstance 過去のインスタンスハンドル
 * @param[in] lpsCommandLine コマンドライン引数
 * @param[in] nCommandShow ウィンドウ状態
 *
 * @return アプリケーションの終了コード
 */
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPreviousInstance,
                   LPSTR lpsCommandLine,
                   int nCommandShow)
{
  HANDLE hMutex;
  MSG msg;

  // インスタンスハンドルを保存する

  ::hInstance = hInstance;

  // ミューテックスによる多重起動の検出
  hMutex = ::CreateMutex(NULL, TRUE, szClassName);
  if (::GetLastError() == ERROR_ALREADY_EXISTS) {
    return 0;
  }

  // アプリケーション初期化
  {
    // ウィンドウクラスを定義する
    WNDCLASSEX wc;

    // 構造体のサイズ
    wc.cbSize = sizeof(WNDCLASSEX);
    // クラスのスタイル
    wc.style = CS_HREDRAW | CS_VREDRAW;
    // ウィンドウプロシージャ名
    wc.lpfnWndProc = ::WindowProcedure;
    // 補助メモリ
    wc.cbClsExtra = 0;
    // 補助メモリ
    wc.cbWndExtra = 0;
    // インスタンスハンドル
    wc.hInstance = ::hInstance;
    // アイコン
    wc.hIcon = (HICON)::LoadImage(NULL,
                                  MAKEINTRESOURCE(IDI_APPLICATION),
                                  IMAGE_ICON,
                                  0,
                                  0,
                                  LR_DEFAULTSIZE | LR_SHARED);
    // カーソル
    wc.hCursor = (HCURSOR)::LoadImage(NULL,
                                      MAKEINTRESOURCE(IDC_ARROW),
                                      IMAGE_CURSOR,
                                      0,
                                      0,
                                      LR_DEFAULTSIZE | LR_SHARED);
    // 背景ブラシ
    wc.hbrBackground = NULL;
    // メニュー名
    wc.lpszMenuName = NULL;
    // クラス名
    wc.lpszClassName = ::szClassName;
    // 小さいアイコン
    wc.hIconSm = (HICON)::LoadImage(NULL,
                                    MAKEINTRESOURCE(IDI_APPLICATION),
                                    IMAGE_ICON,
                                    0,
                                    0,
                                    LR_DEFAULTSIZE | LR_SHARED);

    // ウィンドウクラスを登録する
    if (!::RegisterClassEx(&wc))
      return FALSE;
  }

  // インスタンス初期化
  {
    // ウィンドウサイズを計算する
    unsigned int windowWidth = iClientWidth
      + ::GetSystemMetrics(SM_CXEDGE)
      + ::GetSystemMetrics(SM_CXBORDER)
      + ::GetSystemMetrics(SM_CXDLGFRAME);
    unsigned int windowHeight = iClientHeight
      + ::GetSystemMetrics(SM_CYEDGE)
      + ::GetSystemMetrics(SM_CYBORDER)
      + ::GetSystemMetrics(SM_CYDLGFRAME)
      + ::GetSystemMetrics(SM_CYCAPTION);

    // ウィンドウを作成する

    ::hWindow = ::CreateWindow(// クラス名

                               ::szClassName,
                               // タイトルバーの文字列

                               ::szTitleBar,
                               // ウィンドウの種類
                               WS_OVERLAPPEDWINDOW &
                               ~WS_THICKFRAME &
                               ~WS_MAXIMIZEBOX,
                               // x 座標
                               GetSystemMetrics(SM_CXSCREEN)/2 - windowWidth/2,
                               // y 座標
                               GetSystemMetrics(SM_CYSCREEN)/2 - windowHeight/2,
                               // 幅
                               windowWidth,
                               // 高さ
                               windowHeight,
                               // 親ウィンドウのハンドル
                               // 親を作るときは NULL
                               NULL,
                               // メニューハンドル
                               // クラスメニューを作るときは NULL
                               NULL,
                               // インスタンスハンドル
                               hInstance,
                               // ウィンドウ作成データ
                               NULL);

    if (!::hWindow) {
      return FALSE;
    }

    // ウィンドウを表示する

    ::ShowWindow(::hWindow, nCommandShow);

    // ウィンドウを再描画する

    ::UpdateWindow(::hWindow);
  }

  // バックバッファの構築
  {
    HDC hdc = ::GetDC(::hWindow);
    ::hBackBuffer = ::CreateCompatibleDC(hdc);
    ::hBitmap = ::CreateCompatibleBitmap(hdc,
                                         iClientWidth,
                                         iClientHeight);
    ::ReleaseDC(::hWindow, hdc);
    ::SelectObject(::hBackBuffer, ::hBitmap);
  }

  // メッセージループ
  {
    FLOAT const frame = 16.6666f;
    DWORD enterTime = ::timeGetTime();
    DWORD previousTime = ::timeGetTime();
    DWORD afterGameUpdateTime = ::timeGetTime();

    ::ZeroMemory(&msg, sizeof(MSG));

    while (msg.message != WM_QUIT) {
      if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {

        ::TranslateMessage(&msg);
        // メッセージをウィンドウプロシージャに送信

        ::DispatchMessage(&msg);

      } else {

        // ゲームシステムのメインループ
        enterTime = ::timeGetTime();
        if (frame <(enterTime - previousTime)) {
          // ゲームの中核処理は必ず実行する

          ::UpdateTiles();
          afterGameUpdateTime = ::timeGetTime();
          if ((afterGameUpdateTime - enterTime) <frame) {
            // 同じフレーム内に UpdateGame() がすんだ場合は
            // 描画処理を実行する

            ::InvalidateRect(::hWindow, NULL, TRUE);
            ::UpdateWindow(::hWindow);
            previousTime = ::timeGetTime();
            ::Sleep(1);           // 少し待つ
          } else {
            // 次のフレームに食い込んでしまった場合は
            // 強制的に次フレームの更新を実行する
            previousTime = enterTime - (DWORD)frame;
          }
        }

      }
    }
  }

  // バックバッファの解放

  ::DeleteObject(::hBitmap);
  ::DeleteDC(::hBackBuffer);

  // ミューテックスを閉じる

  ::CloseHandle(hMutex);

  // PostQuitMessage 関数によってポストされた
  // WM_QUIT メッセージの wParam を終了コードとして返す
  return (int)msg.wParam;
}

/**
 * ウィンドウプロシージャ用のコールバック関数
 *
 * @param[in] hWnd ウィンドウハンドル
 * @param[in] msg メッセージ内容
 * @param[in] wp 補助パラメータ
 * @param[in] lp 補助パラメータ
 *
 * @return メッセージ処理の結果
 */
LRESULT CALLBACK WindowProcedure(HWND hWnd,
                                 UINT msg,
                                 WPARAM wp,
                                 LPARAM lp)
{
  switch (msg)
    {
    case WM_CREATE:
      // ゲームシステムの初期化
      {
        ::InitializeTiles(hWindow);
      }
      break;
    case WM_ACTIVATE:
      if (wp == WA_ACTIVE) {
        // ゲームシステムのアクティブ化
      } else {
        // ゲームシステムの非アクティブ化
      }
      break;
    case WM_PAINT:
      {
        HDC hdc;
        PAINTSTRUCT ps;
        hdc = ::BeginPaint(hWnd, &ps);
        // ゲームの描画処理を書く

        ::DrawTiles(::hBackBuffer);
        ::BitBlt(hdc, 0, 0, iClientWidth, iClientHeight,
                 ::hBackBuffer, 0, 0, SRCCOPY);
        ::EndPaint(hWnd, &ps);
      }
      break;
    case WM_CLOSE:
      // 閉じる操作が行われた時 WM_DESTROY をポストする

      ::DestroyWindow(hWnd);
      break;
    case WM_DESTROY:
      // ゲームシステムの終了
      {
      }
      // WM_QUIT をポスト(引数は終了コード)

      ::PostQuitMessage(0);
      break;
    default:
      return ::DefWindowProc(hWnd, msg, wp, lp);
    }

  // 自分で処理した場合は必ず 0 を返す
  return 0;
}
tiles.h
#ifndef __TILES_H__

#define __TILES_H__


#include <windows.h>

#include <stdlib.h>


#define SCREEN_WIDTH 640

#define SCREEN_HEIGHT 480

#define TILE_NUM_X 4

#define TILE_NUM_Y 4

#define TILE_WIDTH (SCREEN_WIDTH / TILE_NUM_X)

#define TILE_HEIGHT (SCREEN_HEIGHT / TILE_NUM_Y)

#define TILE_MOVE_SPEED 8


#define BITMAP_FILE "sample.bmp"


typedef struct tagTile {
  int idxX;
  int idxY;
  int posX;
  int posY;
} TILE, *PTILE;

void InitializeTiles(HWND);
void UpdateTiles();
void DrawTiles(HDC);

#endif
tiles.cpp
#include "tiles.h"


static HDC hMemory;
static HBITMAP hBitmap;
static HBRUSH hBGColor;

static TILE tiles[TILE_NUM_X][TILE_NUM_Y];

void InitializeTiles(HWND hWindow)
{
  int x, y;

  // ビットマップファイルの読み込み
  HDC hdc = ::GetDC(hWindow);
  ::hMemory = ::CreateCompatibleDC(hdc);
  ::hBitmap = (HBITMAP)::LoadImage(NULL,
                                   TEXT(BITMAP_FILE),
                                   IMAGE_BITMAP,
                                   0,
                                   0,
                                   LR_LOADFROMFILE);
  ::SelectObject(::hMemory, ::hBitmap);
  ::ReleaseDC(hWindow, hdc);

  // 各タイルの座標をセット
  for (y = 0; y <TILE_NUM_Y; y++) {
    for (x = 0; x <TILE_NUM_X; x++) {
      ::tiles[x][y].idxX = x;
      ::tiles[x][y].idxY = y;
      ::tiles[x][y].posX = x * TILE_WIDTH;
      ::tiles[x][y].posY = y * TILE_HEIGHT;
    }
  }

  // 塗りつぶしブラシ

  ::hBGColor = CreateSolidBrush(RGB(0, 0, 0));
}

void UpdateTiles()
{
  int x, y;
  int targetX, targetY;

  // タイルの座標修正
  for (y = 0; y <TILE_NUM_Y; y++) {
    for (x = 0; x <TILE_NUM_X; x++) {
      targetX = x * TILE_WIDTH;
      targetY = y * TILE_HEIGHT;
      if (::tiles[x][y].posX <targetX) {
        ::tiles[x][y].posX += TILE_MOVE_SPEED;
      }
      if (targetX <::tiles[x][y].posX) {
        ::tiles[x][y].posX -= TILE_MOVE_SPEED;
      }
      if (::tiles[x][y].posY <targetY) {
        ::tiles[x][y].posY += TILE_MOVE_SPEED;
      }
      if (targetY <::tiles[x][y].posY) {
        ::tiles[x][y].posY -= TILE_MOVE_SPEED;
      }
    }
  }
}

void DrawTiles(HDC hdc)
{
  int x, y;

  ::SelectObject(hdc, ::hBGColor);
  ::Rectangle(hdc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  // タイル描画
  for (y = 0; y <TILE_NUM_Y; y++) {
    for (x = 0; x <TILE_NUM_X; x++) {
      ::BitBlt(hdc,
               ::tiles[x][y].posX,
               ::tiles[x][y].posY,
               TILE_WIDTH,
               TILE_HEIGHT,
               ::hMemory,
               ::tiles[x][y].idxX * TILE_WIDTH,
               ::tiles[x][y].idxY * TILE_HEIGHT,
               SRCCOPY);
    }
  }
}

ポイントは「 15 パズルを作る(第9夜) – 空白のマスに隣接するタイルをスライドさせる」にあることを実装した点だと思います。回転や拡大縮小はまた後日。

Contributions




Trackback URL

メルマガ登録・解除
 
挫折不可能!初級ゲームプログラミング完全マニュアル
RSS track feedいちばんやさしいゲームの作り方 カウンター
ブログSEO対策:track word あわせて読みたい SEO STATUS