矩形を使ったゲームの当たり判定の実装法

1. はじめに

 RPGのゲームをDxLib(C#)で作成している。この際、矩形を使用した当たり判定の実装方法について勉強したので、その内容をまとめる。

2. 当たり判定とは

 当たり判定とは、物体と物体が接触したかどうか判定する処理のことである。例えば、「プレイヤーと壁が接触すると、壁より先に進めない」「プレイヤーと敵が接触するとダメージを受ける」などである(図1)。

図1 キャラクターと壁の当たり判定

 図1は、プレイヤーキャラクターと壁との当たり判定のイメージである。キャラクターを上下左右に移動し、壁がある場合は、先に進ませないようにする必要がある。もし、当たり判定がない場合、フィールド外に進んでしまうため、プログラムが意図しない動作を起こす。

3. 当初の当たり判定の実装方法

 当初の当たり判定の実装方法は、配列に壁か進行可能かを示す数値を入れて置き、当たりか判定する方法を考えていた。

//0: 進行可能なエリア
//1: 壁
int[,] mapdata = {
  {1, 1, 1, 1},
  {1, 0, 0, 1},
  {1, 0, 0, 1},
  {1, 1, 1, 1}
};

  上記は、0が進行可能で1が壁(進行不可)を表している。このデータを使って下記の処理を行う。

(1) 上下左右に進める。
(2) 進行方向が0の場合は、進行方向にキャラクターを移動する。
(3) 進行方向が1の場合は、キャラクターの移動はしない。

 この方法でも問題ないが、異なるアプローチが存在する。

4. 矩形を使った当たり判定

4.1 概要

 配列を使ったやり方だけではなく、それぞれのオブジェクトから透過な矩形を作成し、当たり判定をする方法がある。下記にイメージを示す(図2)。

図2 オブジェクトに矩形を作るイメージ

 図2は、オブジェクトに矩形を作成するイメージを示したものである。それぞれに矩形を作成し、重なった場合は、当たりとみなすというものである。どうやらバウンディングボックスとも呼ばれるそうだ。

4.2 当たり判定のパターン

 2Dにおける当たり判定は、全部で4パターンある。

当たり判定の4パターン

 この判定の4パターンを移動時に判定することで当たり判定を行うことができる。

5. 実装方法

 下記に実装方法(DxLib C#)を示す。

class Program {
    static void Main(string[] args) {
        // ウィンドウモードで起動するように設定
        DX.ChangeWindowMode(DX.TRUE);

        // Dxlib の初期化
        DX.DxLib_Init();

        // 描画先を裏画面に設定
        DX.SetDrawScreen(DX.DX_SCREEN_BACK);

        //実行パスを取得する
        string currentDirectory = Directory.GetCurrentDirectory();

        //画像を取得する
        int characterImage = DX.LoadGraph(currentDirectory + "\\..\\..\\img\\Image.png");
        int wallImage = DX.LoadGraph(currentDirectory + "\\..\\..\\img\\Wall.png");

        //キーの状態変数
        byte[] keyState = new byte[256];

        //X, Y座標
        int x = 350;
        int y = 50;

        //スピード
        float speed = 1.0f;

        //0: 進行可能なエリア
        //1: 壁
        int[,] mapdata = {
            { 1, 1, 1, 1 },
            { 1, 0, 0, 1 },
            { 1, 0, 0, 1 },
            { 1, 1, 1, 1 },
        };

        
        // メインループ
        while (DX.ProcessMessage() == 0)
        {
            // 画面をクリア
            DX.ClearDrawScreen();

            //キー入力をチェック
            DX.GetHitKeyStateAll(keyState);

            //斜め押しをしている場合は速度を変える
            if (keyState[DX.KEY_INPUT_LEFT] == 1 || keyState[DX.KEY_INPUT_RIGHT] == 1) {
                if (keyState[DX.KEY_INPUT_UP] == 1 || keyState[DX.KEY_INPUT_DOWN] == 1) {
                    speed = 0.707f;
                } else {
                    speed = 1.0f;
                }
            } else {
                speed = 1.0f;
            }

            // 各方向の移動計算
            if (keyState[DX.KEY_INPUT_RIGHT] == 1) {
                x += (int)(3 * speed);
            }
            if (keyState[DX.KEY_INPUT_LEFT] == 1) {
                x -= (int)(3 * speed);
            }
            if (keyState[DX.KEY_INPUT_DOWN] == 1) {
                y += (int)(3 * speed);
            }
            if (keyState[DX.KEY_INPUT_UP] == 1) {
                y -= (int)(3 * speed);
            }

            //壁から50ピクセルの矩形を作成
            Rectangle wallRect = new Rectangle(100, 100, 50, 50);

            //青の矩形を表示する
            DX.DrawBox(100, 100, 150, 150, DX.GetColor(0,0,255), DX.TRUE);

            //現在座標から32ピクセル * 32ピクセルの矩形を作成
            Rectangle playerRect = new Rectangle(x, y, 32, 32);

            //当たり判定
            bool flg = wallRect.Intersects(playerRect);

            //当たっている場合
            if (flg)
            {
                //緑色の矩形を表示する
                DX.DrawBox(100, 100, 150, 150, DX.GetColor(0, 255, 0), DX.TRUE);
            }

            DX.DrawGraph(x, y, characterImage, DX.TRUE);

            // 裏画面の内容を表画面に反映
            DX.ScreenFlip();

        }

        // Dxlib の終了処理
        DX.DxLib_End();
    }
}
public class Rectangle
{
    public double X { get; set; }
    public double Y { get; set; }
    double Height { get; set; }
    double Width { get; set; }

    public Rectangle(double x, double y, double height, double width)
    {
        X = x;
        Y = y;
        Height = height;
        Width = width;
    }
    public bool Intersects(Rectangle Player)
    {
        // この矩形の右端が、他の矩形の左端よりも右にあり、
        // この矩形の左端が、他の矩形の右端よりも左にあり、
        // この矩形の上端が、他の矩形の下端よりも上にあり、
        // そして、この矩形の下端が、他の矩形の上端よりも下にある場合
        if (X + Width > Player.X &&
            X < Player.X + Player.Width &&
            Y < Player.Y + Player.Height &&
            Y + Height > Player.Y)
        {
            return true; // 重なっている
        }

        // 上記の条件のいずれかが満たされない場合は、重なっていない
        return false;
    }

}

 上記は、50*50の矩形と32*32のプレイヤーキャラクターを表示し、当たり判定が成立する場合は、緑色の矩形に変更するというものである。

 大事な部分は、下記である。

    public bool Intersects(Rectangle Player)
    {
        // この矩形の右端が、他の矩形の左端よりも右にあり、
        // この矩形の左端が、他の矩形の右端よりも左にあり、
        // この矩形の上端が、他の矩形の下端よりも上にあり、
        // そして、この矩形の下端が、他の矩形の上端よりも下にある場合
        if (X + Width > Player.X &&
            X < Player.X + Player.Width &&
            Y < Player.Y + Player.Height &&
            Y + Height > Player.Y)
        {
            return true; // 重なっている
        }

        // 上記の条件のいずれかが満たされない場合は、重なっていない
        return false;
    }

 ここで、先ほどの4パターンを判定し、true/falseを返している。動作を確認すると、下記のようになる。

 このように、4パターンで当たり判定ができていることを確認できた。

この記事が気に入ったらサポートをしてみませんか?