第11章 ゲーム制作における画像データの使い方を学ぶ

11-2 面が変わるたびにオフスクリーン・サーフェイスの画像を切り替える

 ビデオ・メモリはそれほど容量がないため、画像を多く使うゲームでは、いかに少ないビデオ・メモリで多くの画像を扱うかが問題になる。最近のノートパソコンでは、少ないビデオ・メモリを補うため、メイン・メモリの一部をビデオ・メモリとして割り当てる製品が多くなったが、メイン・メモリ上に作成された画像データは、転送速度が遅くなるため、ビデオ・メモリのみで対応できるようにプログラムするべきである。
 そこで、ステージで必要な背景画像のみをビデオ・メモリにロードし、ステージが切り替わったら、別の背景画像をロードし直すというプログラムを考える。

サンプルプログラム

 ステージごとにロードする画像を切り替えてオフスクリーン・サーフェイスを再生するサンプルプログラムを紹介する。ダウンロードし、プロジェクトを作成後、ビルドして実行する。
 このサンプルでは、ゲーム開始時は1面用の背景を表示している。背景画像はF1、F2、F3キーを押すと、それぞれ切り替わるようプログラムされている。

プログラム解説

このサンプルプログラムがどのように作られているかを解説する。

1. DirectDrawオブジェクト初期化処理

今まではDirectDrawオブジェクトの初期化処理で、「スタート画面用サーフェイス(g_pDDSStart)」「ゲーム画面背景用サーフェイス(g_pDDSGame)」「ゲーム画面キャラクタ用サーフェイス(g_pDDSChara)」と3つのオフスクリーン・サーフェイスを作成していたが、ゲーム画面背景用サーフェイスの生成を削除した。これは、ステージ毎にゲーム画面背景用サーフェイスを生成するためである。

2. ゲーム初期化処理

ゲーム初期化処理を「ゲーム開始時初期化処理(GameInit関数)」と「ステージ変更時初期化処理(GameStargeInit関数)」に分割した。これは、ゲームが最初から始まる場合と、ステージが変わる場合では初期化するべき変数の値が変わるためである。

//-----------------------------------------------------------------------------
// 関数名 : GameInit()
// 機能概要: ゲーム開始時初期化処理
//-----------------------------------------------------------------------------
void GameInit(HWND hWnd)
{
    //------------------------------------------------------- 各変数の初期化
    /* ステージ番号の初期化 */
    StargeNumber = 0;
    /* キャラクタ情報の初期化(スコア、レベルなど) */
	
    //------------------------------------------------------- フレームナンバーセット
    g_FrameNo = GAME_STARGE_INIT;

}

//-----------------------------------------------------------------------------
// 関数名 : GameStargeInit()
// 機能概要: ステージ別初期化処理
//-----------------------------------------------------------------------------
void GameStargeInit(HWND hWnd)
{
    //------------------------------------------------------- 各変数の初期化
    /* ステージ番号によって、ロードする背景リソースを変える */
    CreateGameSurface(StargeNumber);
	
    /* 自キャラ(ステージ毎に初期化するべきもの) */
    StatusInit(&MyChara, 0, 0, 40, 40, 310, 230, 4, 4);
    /* ゴキブリ(動かない) */
    StatusInit(&Goki, 64, 394, 96, 426, 400, 300, 0, 0);

    /* ステージ別初期化処理 */
    switch (StargeNumber)
    {
        case 0:
            // 1面での初期化
            break;
        case 1:
            // 2面での初期化
            break;
        case 2:
            // 3面での初期化
            break;
    }

    //------------------------------------------------------- フレームナンバーセット
    g_FrameNo = GAME_FRAME;

}

 GameStargeInit関数の最初で、CreateGameSurface関数を呼び出している。この関数はmyDraw.cppに作成した関数で、引数に与えられた値に対応するリソースから、オフスクリーン・サーフェイスを生成する。CreateGameSurface関数については後で解説する。
 また、ステージ別に初期化が変わる場合のため、switch文を付けておいた。

3. ゲームループ処理の変更

ゲーム開始時の初期化と、ステージが切り替わる毎の初期化があるため、ゲームループ処理(WinMain.cppのUpdateFrame関数)を、次のように変更している。

//-----------------------------------------------------------------------------
// 関数名 : UpdateFrame()
// 機能概要: 画面更新処理
//-----------------------------------------------------------------------------
static void UpdateFrame(HWND hWnd)
{

    /* 現在のキー情報を取得 */
    if (!GetKeyboardState(KeyTbl))
        return;

    /* 処理の振り分け */
    switch (g_FrameNo)
    {
        case START_INIT:
            StartInit(hWnd);
        case START_FRAME:
            StartFrame(hWnd);
            break;
        case GAME_INIT:
            GameInit(hWnd);
        case GAME_STARGE_INIT:
            GameStargeInit(hWnd);
        case GAME_FRAME:
            GameFrame(hWnd);
            break;
        default:
            OutputDebugString("g_FrameNoの値が例外です。\n");
    }

    /* フリップ処理 */
    g_pDDSPrimary->Flip(NULL, 0);

}

breakを付けないことにより、スタート画面からゲームが開始された場合は「GameInit関数」→「GameStargeInit関数」→「GameFrame関数」と流れ、ステージが切り替わったら「GameStargeInit関数」→「GameFrame関数」と流れる。

4. ステージの切り替え

本来はステージをクリアしてから切り替えを行うべきであるが、今回はサンプルのため、ファンクションキーを押したら、該当するステージに切り替わるようにした。

//-----------------------------------------------------------------------------
// 関数名 : GameFrame()
// 機能概要: ゲーム画面更新処理
//-----------------------------------------------------------------------------
void GameFrame(HWND hWnd)
{
    BackDraw();	// 背景描画処理
    CharDraw();	// キャラクタ描画処理
    CharMove();		// キャラクタ移動処理
    ScreenHitCheck();	// キャラクタがゲーム画面をはみ出しているときの処理
    CharHitCheck();	// キャラクタ同士の当たり判定&処理

    // ステージ切り替え(削除予定)
    if (KeyTbl[VK_F1] & 0x80) /* F1キーが押されたら、1面からスタート */
    {
        StargeNumber = 0;
        g_FrameNo = GAME_STARGE_INIT;
        return;
    }
    else if (KeyTbl[VK_F2] & 0x80) /* F2キーが押されたら、2面からスタート */
    {
        StargeNumber = 1;
        g_FrameNo = GAME_STARGE_INIT;
        return;
    }
    else if (KeyTbl[VK_F3] & 0x80) /* F3キーが押されたら、3面からスタート */
    {
        StargeNumber = 2;
        g_FrameNo = GAME_STARGE_INIT;
        return;
    }

}

g_FrameNo を GAME_STARGE_INIT に変更することにより、次のループでステージ別の初期化が行われ、ゲームが再開する。

5. ゲーム背景用サーフェイス作成処理

ステージ番号を引数にし、その引数に対応するリソースからゲーム背景用サーフェイスを生成する関数を myDraw.cpp 内に作成した。

//-----------------------------------------------------------------------------
// 関数名 : CreateGameSurface()
// 機能概要: ゲーム背景画像用サーフェイスを作成する
//-----------------------------------------------------------------------------
bool CreateGameSurface(int StargeNumber)
{
    /* すでに生成済みの場合、一度開放する */
    if (g_pDDSGame != NULL)
    {
        g_pDDSGame->Release();
        g_pDDSGame = NULL;
    }
    /* ゲーム背景画像用サーフェイスを作成する */
    g_pDDSGame = DDLoadBitmap(g_pDD, szGameBmp[StargeNumber], 0, 0);
    if (g_pDDSGame == NULL)
        return false;
    else
        return true;

}

 ロードに成功すると true を、失敗すると false を返すようにしているため、本来はこの関数を実行するときは、戻り値による処理を付けるべきである。
 また、ゲーム背景画像用のリソース名は、次のように宣言している。

static char szGameBmp[3][10] = { // ゲーム背景画像リソース名
  "BACK1BMP", /* 1面用 */
  "BACK2BMP", /* 2面用 */
  "BACK3BMP" /* 3面用 */
};

11章2練習問題1(必須問題)

キャラクタ用画像はゲーム画面でしか使わない。よって、キャラクタ画像用サーフェイスはDirectDrawオブジェクト作成時ではなく、ゲームが始まるときに作成すればよいことが分かる。キャラクタ画像用サーフェイス(g_pDDSChara)の生成を myDraw.cpp 内の「InitializeDraw関数」ではなく、Game.cpp内の「GameInit関数」で行うよう、プログラムを修正しなさい。

11章2練習問題2(自由問題)

このプログラムでは、スタート画面背景用サーフェイスとゲーム画面背景用サーフェイスをそれぞれ定義しているため、無駄である。スタート画面背景でもゲームが面背景でも同じサーフェイスを使うことにより、オフスクリーン・サーフェイスを1つ減らすよう、プログラムを修正しなさい。


[ TOP ]