第4章 モジュール分割

4-6 処理単位ごとに関数化する

例えばゲーム処理で表示するキャラクタを増やすなどゲームを作りこんでいくと、ゲーム処理関数「GameFrame」が巨大化していく。

[現在のGameFrame関数]

//-----------------------------------------------------------------------------
// 関数名 : GameFrame()
// 機能概要: 画面更新処理
//-----------------------------------------------------------------------------
void GameFrame(HWND hWnd)
{
    HRESULT hRet;
    RECT r1, r2;

    //-------------------------------------------------------------------------
    // 描画処理
    //-------------------------------------------------------------------------
    /* ゲーム画面用背景 */
    hRet = g_pDDSBack->BltFast(0, 0, g_pDDSGame, &ScreenRect, DDBLTFAST_NOCOLORKEY);
    if (hRet != DD_OK)
        return;
    /* 天使 */
    hRet = g_pDDSBack->BltFast(AngelX, AngelY, g_pDDSGame, &AngelRect, DDBLTFAST_SRCCOLORKEY);
    if (hRet != DD_OK)
        return;
    /* ゴキブリ */
    hRet = g_pDDSBack->BltFast(GokiX, GokiY, g_pDDSGame, &GokiRect, DDBLTFAST_SRCCOLORKEY);
    if (hRet != DD_OK)
        return;

    //-------------------------------------------------------------------------
    // 移動処理
    //-------------------------------------------------------------------------
    /* 天使の移動と壁判定 */
    if (KeyTbl[VK_LEFT] & 0x80)
    {
        AngelX -= AngelMoveX;
        if (AngelX < ScreenRect.left)
            AngelX = ScreenRect.left;
    }
    if (KeyTbl[VK_RIGHT] & 0x80)
    {
        AngelX += AngelMoveX;
        if (AngelX > ScreenRect.right - AngelWidth)
            AngelX = ScreenRect.right - AngelWidth;
    }
    if (KeyTbl[VK_UP] & 0x80)
    {
        AngelY -= AngelMoveY;
        if (AngelY < ScreenRect.top)
            AngelY = ScreenRect.top;
    }
    if (KeyTbl[VK_DOWN] & 0x80)
    {
        AngelY += AngelMoveY;
        if (AngelY > ScreenRect.bottom - AngelHeight)
            AngelY = ScreenRect.bottom - AngelHeight;
    }
    /* 天使とゴキブリの衝突を調べ、当たっていたらスタート画面に戻る */
    SetRect(&r1, AngelX, AngelY, AngelX + AngelWidth, AngelY + AngelHeight);
    SetRect(&r2, GokiX, GokiY, GokiX + GokiWidth, GokiY + GokiHeight);
    if (HitCheck(r1, r2))
    {
        g_FrameNo = START_INIT;
        return;
    }

    // F1キーが押されたら、スタート画面に戻る
    if (KeyTbl[VK_F1] & 0x80)
    {
        g_FrameNo = START_INIT;
        return;
    }

}

2つのキャラクタを表示して当たり判定を行うだけでもこれだけ巨大化してしまう。どれだけ作りこんでもメンテナンスしやすくなるよう、GameFrame関数を改造する。

(a) ゲームメイン処理を処理単位別に分ける

C言語でのプログラム開発の基本は、処理単位ごとに関数化を行うことにある。では、ゲームメイン処理にはどのような処理があるだろうか?

1.画像の表示
背景画像、キャラクタ画像を表示する
2.キャラクタの移動処理
画面上を動くキャラクタに対して、移動処理を行う
3.キャラクタとゲーム画面の当たり判定
ゲーム画面内を動き回るキャラクタに対して、画面をはみ出させない処理を行う
4.キャラクタ同士の当たり判定
複数のキャラクタを表示する場合、キャラクタ同士の当たり判定と、衝突した場合の処理を行う

シューティング・ゲームなどで表示するキャラクタが多い場合、それぞれの場面で繰り返し文を使った処理を行わなければならないため、プログラム的には無駄の多いつくりになってしまうが、処理単位別に関数が作られるため、メンテナンスは容易になる。
まずはメンテナンスしやすい形でゲームをつくり、完成してから無駄を省くよう修正していくのがベスト!

[処理単位別に関数を実行するゲーム・メイン関数]

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

    // F1キーが押されたら、スタート画面に戻る(削除予定)
    if (KeyTbl[VK_F1] & 0x80)
    {
        g_FrameNo = START_INIT;
        return;
    }

}

(b) 処理単位別の関数を作成する

従来のGameFrame関数を元に、処理単位別の関数をそれぞれ作成する。プロトタイプ宣言も行うこと。

(1)プロトタイプ宣言

//-----------------------------------------------------------------------------
// プロトタイプ宣言
//-----------------------------------------------------------------------------
static void ScreenOut(void);
static void CharMove(void);
static void ScreenHitCheck(void);
static void CharHitCheck(void);

(2)ゲーム画像描画処理

//-----------------------------------------------------------------------------
// 関数名 : ScreenOut()
// 機能概要: ゲーム画像描画処理
//-----------------------------------------------------------------------------
static void ScreenOut(void)
{
    HRESULT hRet;

    /* ゲーム画面用背景 */
    hRet = g_pDDSBack->BltFast(0, 0, g_pDDSGame, &ScreenRect, DDBLTFAST_NOCOLORKEY);
    if (hRet != DD_OK)
        return;

    /* 天使 */
    hRet = g_pDDSBack->BltFast(AngelX, AngelY, g_pDDSGame, &AngelRect, DDBLTFAST_SRCCOLORKEY);
    if (hRet != DD_OK)
        return;

    /* ゴキブリ */
    hRet = g_pDDSBack->BltFast(GokiX, GokiY, g_pDDSGame, &GokiRect, DDBLTFAST_SRCCOLORKEY);
    if (hRet != DD_OK)
        return;

}

(3)キャラクタ移動処理

//-----------------------------------------------------------------------------
// 関数名 : CharMove()
// 機能概要: キャラクタ移動処理
//-----------------------------------------------------------------------------
static void CharMove(void)
{
    /* 天使の移動 */
    if (KeyTbl[VK_LEFT] & 0x80)
        AngelX -= AngelMoveX;
    if (KeyTbl[VK_RIGHT] & 0x80)
        AngelX += AngelMoveX;
    if (KeyTbl[VK_UP] & 0x80)
        AngelY -= AngelMoveY;
    if (KeyTbl[VK_DOWN] & 0x80)
        AngelY += AngelMoveY;

}

(4)キャラクタがゲーム画面をはみ出している時の処理

//-----------------------------------------------------------------------------
// 関数名 : ScreenCheck()
// 機能概要: キャラクタがゲーム画面をはみ出している時の処理
//-----------------------------------------------------------------------------
static void ScreenHitCheck(void)
{
    /* 天使とゲーム画面(画面全体)をチェック */
    if (AngelX < ScreenRect.left)
        AngelX = ScreenRect.left;
    else if (AngelX > ScreenRect.right - AngelWidth)
        AngelX = ScreenRect.right - AngelWidth;

    if (AngelY < ScreenRect.top)
        AngelY = ScreenRect.top;
    else if (AngelY > ScreenRect.bottom - AngelHeight)
        AngelY = ScreenRect.bottom - AngelHeight;

}

(5)キャラクタ同士の当たり判定と処理

//-----------------------------------------------------------------------------
// 関数名 : CharHitCheck()
// 機能概要: キャラクタ同士の当たり判定と処理
//-----------------------------------------------------------------------------
static void CharHitCheck(void)
{
    RECT r1, r2;

    /* 天使とゴキブリの衝突を調べ、当たっていたらスタート画面に戻る */
    SetRect(&r1, AngelX, AngelY, AngelX + AngelWidth, AngelY + AngelHeight);
    SetRect(&r2, GokiX, GokiY, GokiX + GokiWidth, GokiY + GokiHeight);
    if (HitCheck(r1, r2))
    {
        g_FrameNo = START_INIT;
        return;
    }

}

確認!!

ここまでやったらリビルドを行い、プログラムが正常に動くかどうか確かめよう。

第4章補足

4章ではモジュール分割、処理単位別関数の作成を学んだが、これらは必ずしも必要な作業ではないし、必ずこの通りに分割&関数化しなければならないわけではない
しかし、普段から効率のよいプログラムを作ることを練習しておけば、巨大なゲームを作るときでもデバッグに時間をとられることなく開発できるようになる。
つまり、これらの作業は「どこが原因で動きがおかしいのか分からない!!」と悩む時間(デバッグ時間)を少しでも減らすために行うのである。
どのように分割&関数化を行って作っていくと開発効率のいいプログラムになるかを常に意識しながらプログラムを作成し、自分のスタイルを確立しよう。


[ TOP ]