矩形を使ったゲームの当たり判定の実装法
1. はじめに
RPGのゲームをDxLib(C#)で作成している。この際、矩形を使用した当たり判定の実装方法について勉強したので、その内容をまとめる。
2. 当たり判定とは
当たり判定とは、物体と物体が接触したかどうか判定する処理のことである。例えば、「プレイヤーと壁が接触すると、壁より先に進めない」「プレイヤーと敵が接触するとダメージを受ける」などである(図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は、オブジェクトに矩形を作成するイメージを示したものである。それぞれに矩形を作成し、重なった場合は、当たりとみなす。これを、バウンディングボックスとも呼ばれるそうだ。
4.2 当たり判定のパターン
この方法においての当たり判定は、全部で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パターンで当たり判定ができていることを確認できた。
6. おわりに
今回は、矩形を使ったゲームの当たり判定を行う例題を作成した。配列のデータで判断する方法もよいが、矩形を使って行う方法もある。状況に合わせて使い分けるとよいだろう。