画面からはみ出さないようにするには?(当たり判定1)

考え方

1で作成したプログラムでは、キャラクタ(ポリゴン)を移動させていくと画面をはみ出してしまう。では、画面内の決められた範囲内しか移動できないようにするにはどうしたら良いのだろうか。

何度も説明したが、ポリゴンは4つの頂点座標を持つ。よって、各頂点が指定範囲内を超えているかどうかを判断すれば、指定範囲を超えたかどうかが分かる。

実際のプログラム

では、この判断文をどこで実行すればよいだろうか?これは、自分が作成するゲームによっていろいろ考えられるので、「ここでこのように行うべき」というものではない。よって、考えられるパターンを紹介する。

前準備

どの範囲を移動可能領域とするかを決めなければならない。今回は、画面内(640×480)すべてを移動可能領域と考える。

// マクロの定義
#define WINMODE FALSE // ウィンドウモードの指定(TRUE:ウィンドウモード/FALSE:フルスクリーン)
#define SCREEN_WIDTH	640	// ウィンドウの幅
#define SCREEN_HEIGHT	480	// ウィンドウの高さ
/* ゲームの状態を識別する(フレーム番号) */
#define START_INIT      0
#define START_FRAME     1
#define GAME_INIT       10
#define GAME_FRAME      11
/* 頂点フォーマット(基本形)*/
#define FVF_TLVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1)
/* 移動可能領域 */
#define MOVE_LEFT      0
#define MOVE_TOP       0
#define MOVE_RIGHT     640
#define MOVE_BOTTOM    480

《POINT》

例1)移動処理関数内でチェックする

移動処理を行うMove関数の中で、チェックを行う。指定範囲まで移動したら、それ以上移動しないように制御することにより、指定範囲をはみ出さないようにする。

//----------------------------------------------------------------------------------------
// 関数名 : Move()
// 機能概要: 各頂点に対し、与えられた移動量を加算する
//----------------------------------------------------------------------------------------
void Move(LPTLVERTEX v, float x, float y)
{
    int i;

    // 移動処理
    for ( i=0 ; i<4 ; i++ ) {
        v[i].x += x;
        v[i].y += y;
    }

    // 指定範囲を超えないようにする
    if ( v[0].x < MOVE_LEFT ) Move(v, MOVE_LEFT - v[0].x, 0.0f);
    if ( v[0].y < MOVE_TOP ) Move(v, 0.0f, MOVE_TOP - v[0].y);
    if ( MOVE_RIGHT < v[2].x ) Move(v, MOVE_RIGHT - v[2].x , 0.0f);
    if ( MOVE_BOTTOM < v[2].y ) Move(v, 0.0f, MOVE_BOTTOM - v[2].y);
}

《POINT》

例2)移動処理関数実行後にチェックする

移動処理関数内ではなく、実行後にチェックを行うようプログラムする。Move関数内から指定範囲のチェックを削除すること。

//-----------------------------------------------------------------------------
// 関数名 : StartFrame()
// 機能概要: スタート画面処理
//-----------------------------------------------------------------------------
void StartFrame(void)
{
    float mx, my;

    /* 描画処理 */
    // キャラクタ1を描画
    gl_lpD3ddev->SetTexture(0, gl_Texture1);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, VertexDataTbl1, sizeof(TLVERTX));
    // キャラクタ2を描画
    gl_lpD3ddev->SetTexture(0, gl_Texture2);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, VertexDataTbl2, sizeof(TLVERTX));

    /* 移動処理 */
    mx = my = 0.0;
    if ( gl_KeyTbl[VK_LEFT] & 0x80 )  mx = -2.0f;
    if ( gl_KeyTbl[VK_UP] & 0x80 )    my = -2.0f;
    if ( gl_KeyTbl[VK_RIGHT] & 0x80 ) mx =  2.0f;
    if ( gl_KeyTbl[VK_DOWN] & 0x80 )  my =  2.0f;
    Move(VertexDataTbl1, mx, my);

    /* 指定範囲を超えないようにする */
    if ( VertexDataTbl1[0].x < MOVE_LEFT ) Move(VertexDataTbl1, MOVE_LEFT - VertexDataTbl1[0].x, 0.0f);
    if ( VertexDataTbl1[0].y < MOVE_TOP ) Move(VertexDataTbl1, 0.0f, MOVE_TOP - VertexDataTbl1[0].y);
    if ( MOVE_RIGHT < VertexDataTbl1[2].x ) Move(VertexDataTbl1, MOVE_RIGHT - VertexDataTbl1[2].x , 0.0f);
    if ( MOVE_BOTTOM < VertexDataTbl1[2].y ) Move(VertexDataTbl1, 0.0f, MOVE_BOTTOM - VertexDataTbl1[2].y);

    // リターンキーが押されたら、ゲーム開始
    if ( gl_KeyTbl[VK_RETURN] & 0x80 ) g_FrameNo = GAME_INIT;

}

《POINT》

※例1、例2どちらのやり方でも、キャラクタがはみ出さないことを確認する。

関数化を考える

実際のゲーム製作では、両方のテクニックを同時に使うことが考えられる。例えば移動処理関数を自キャラと敵キャラ、ボスキャラで使い分けたりするような場合である。その場合、指定範囲のチェック処理が複数の場所で行われてしまうため、効率が悪い。やはり関数化するべきだろう。

移動制限を行う関数の作成

ポリゴン情報を与え、指定範囲を超えないように調整する関数は次のように作成する。プロトタイプ宣言も行うこと

//----------------------------------------------------------------------------------------
// 関数名 : MoveCheck()
// 機能概要: 移動可能領域を超えないようにする
//----------------------------------------------------------------------------------------
void MoveCheck(LPTLVERTEX v)
{
    if ( v[0].x < MOVE_LEFT ) Move(v, MOVE_LEFT - v[0].x, 0.0f);
    if ( v[0].y < MOVE_TOP ) Move(v, 0.0f, MOVE_TOP - v[0].y);
    if ( MOVE_RIGHT < v[2].x ) Move(v, MOVE_RIGHT - v[2].x , 0.0f);
    if ( MOVE_BOTTOM < v[2].y ) Move(v, 0.0f, MOVE_BOTTOM - v[2].y);
}

移動制限関数の使用・その1

後は、この関数を使いたいところで使えばよい。例えばStartFrame関数内で実行すると、次のようになる。

//-----------------------------------------------------------------------------
// 関数名 : StartFrame()
// 機能概要: スタート画面処理
//-----------------------------------------------------------------------------
void StartFrame(void)
{
    float mx, my;

    /* 描画処理 */
    // キャラクタ1を描画
    gl_lpD3ddev->SetTexture(0, gl_Texture1);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, VertexDataTbl1, sizeof(TLVERTX));
    // キャラクタ2を描画
    gl_lpD3ddev->SetTexture(0, gl_Texture2);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, VertexDataTbl2, sizeof(TLVERTX));

    /* 移動処理 */
    mx = my = 0.0;
    if ( gl_KeyTbl[VK_LEFT] & 0x80 )  mx = -2.0f;
    if ( gl_KeyTbl[VK_UP] & 0x80 )    my = -2.0f;
    if ( gl_KeyTbl[VK_RIGHT] & 0x80 ) mx =  2.0f;
    if ( gl_KeyTbl[VK_DOWN] & 0x80 )  my =  2.0f;
    Move(VertexDataTbl1, mx, my);

    /* 指定範囲を超えないようにする */
    MoveCheck(VertexDataTbl1);

    // リターンキーが押されたら、ゲーム開始
    if ( gl_KeyTbl[VK_RETURN] & 0x80 ) g_FrameNo = GAME_INIT;

}

移動制限関数の使用・その2

また、移動処理関数内で実行すると、次のようになる。

//----------------------------------------------------------------------------------------
// 関数名 : Move()
// 機能概要: 各頂点に対し、与えられた移動量を加算する
//----------------------------------------------------------------------------------------
void Move(LPTLVERTEX v, float x, float y)
{
    int i;

    // 移動処理
    for ( i=0 ; i<4 ; i++ ) {
        v[i].x += x;
        v[i].y += y;
    }

    // 指定範囲を超えないようにする
    MoveCheck(v);
}

このように、関数化しておけば使いたいところで自由に使えるため、ミスを減らすことができる。

移動可能領域の設定を考える

今回は画面全体を移動可能領域として設定したが、シーンや状態によっては移動可能領域を変更することも考えられる。それらの変更に柔軟に対応するためには、領域をdefine文で宣言するのは好ましくない。では、どのように変更すればよいかを考える。

移動可能領域の設定を変える

移動可能領域の変更に対応するため、defineを削除し、次のように設定する。

// グローバル変数
HWND hWnd;                              // ウィンドウハンドル
BOOL g_appActive = FALSE;               // ウィンドウの状態
char szWinName[] = "Exer006";           // ウィンドウクラス用文字列
char szWinTitle[] = "キャラクタの移動と当たり判定"; // ウィンドウクラス用文字列
LPDIRECT3D8 gl_lpD3d = NULL;            // Direct3D8インターフェイス
LPDIRECT3DDEVICE8 gl_lpD3ddev = NULL;   // Direct3DDevice8インターフェイス
D3DPRESENT_PARAMETERS gl_d3dpp;         // ディスプレイパラメータ
LPDIRECT3DTEXTURE8 gl_Texture1;         // テクスチャ・オブジェクト1
LPDIRECT3DTEXTURE8 gl_Texture2;         // テクスチャ・オブジェクト2
BYTE g_FrameNo = START_INIT;            // フレーム選択用
BYTE gl_KeyTbl[256];                    // キーボードの状態を格納
RECT gl_rcMove;                         // 移動可能領域
TLVERTX VertexDataTbl1[4];              // キャラクタ1用頂点情報配列
TLVERTX VertexDataTbl2[4];              // キャラクタ2用頂点情報配列
char *gl_szCharFileName1 = "sample1.bmp"; // キャラクタ1用画像ファイル名
char *gl_szCharFileName2 = "sample2.bmp"; // キャラクタ2用画像ファイル名
・
・

//-----------------------------------------------------------------------------
// 関数名 : StartInit()
// 機能概要: スタート画面初期化処理
//-----------------------------------------------------------------------------
void StartInit(void)
{
    HRESULT ret;

    //--------------------------------------------------- 各変数の初期化
    // 移動可能領域の設定
    SetRect(&gl_rcMove, 0, 0, 640, 480);
    // スタート画面で使用するテクスチャの作成
    CreateStartTexture();
    // 頂点データを格納する
    InitVertex(VertexDataTbl1, 100.0f, 100.0f, 164.0f, 164.0f);
    InitVertex(VertexDataTbl2, 400.0f, 300.0f, 496.0f, 396.0f);

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

}

《POINT》

移動可能領域判定処理

移動可能領域を超えたかどうかの判定は、MoveCheck関数で行っている。この関数を次のように修正する。プロトタイプ宣言も修正すること。

//----------------------------------------------------------------------------------------
// 関数名 : MoveCheck()
// 機能概要: 移動可能領域を超えないようにする
//----------------------------------------------------------------------------------------
void MoveCheck(RECT rc, LPTLVERTEX v)
{
    if (v[0].x < rc.left) Move(v, rc.left - v[0].x, 0.0f);
    if (v[0].y < rc.top) Move(v, 0.0f, rc.top - v[0].y);
    if (rc.right < v[2].x) Move(v, rc.right - v[2].x , 0.0f);
    if (rc.bottom < v[2].y) Move(v, 0.0f, rc.bottom - v[2].y);
}

《POINT》

移動制限関数の実行

MoveCheck関数を上記のように修正した場合、関数の呼び出しは次のようになる。

MoveCheck(gl_rcMove, VertexDataTbl1);

このように作っておけば、どんなゲームでも比較的容易に対応できるだろう。

※MoveCheck関数を修正し、正しく実行できることを確認する。また、移動可能領域の大きさを変化させ、大きさに対応しているかどうかを確認する。


BACK(移動処理) NEXT(キャラクタ同士の衝突を調べる)