移動ベクトルを使った矩形とマップチップの当たり判定

unity_chan_logo

概要

最終更新日:2020/03/13

移動ベクトルを使って矩形とマップチップの当たり判定について書いた記事です。
主に以下の内容を知りたい方に向けて書いています。
  • 壁ギリギリまでオブジェクトを移動させたい
  • 重力を導入したい
  • 壁に当たりながらジャンプがしたい

注意点

この記事内の座標軸はX軸「右が正、左が負」Y軸「下が正、上が負」で書いています。

また、マップチップや矩形とマップチップの判定を知っている前提で書いていますので、
知らない方はマップチップについてはこちら、矩形とマップチップの当たりについては
こちらに記事を書いていますのでご確認ください。

サンプル

サンプルはここからダウンロードでき、環境については以下の内容となっています。

開発環境
VSのバージョン VisualStudio 2019
DirectXのバージョン DirectX9
説明 マップチップ上をキャラクターが移動します。
上下左右の矢印:上下左右移動
A:ジャンプ


矩形とマップチップの当たり判定の問題点

ベクトル情報と矩形を使ってマップチップの当たり判定を行う理由は
通常の矩形とマップチップの当たり判定で以下の問題が発生する可能性があるからです。
  • チップのギリギリの位置に到達できない
この問題を回避するためにベクトル情報を追加して判定を行います。

チップのギリギリの位置に到達できない

通常の当たり判定は移動後の座標が当たり用のチップの位置にあれば
移動をキャンセルします。

collision_0061

上の画像では、オブジェクトの移動後の位置がチップに到達しているので、
現在の位置から移動できず、これ以上オブジェクトとチップの距離を詰めれません。
この問題の解決策として、ベクトルを使用する方法があります。

どの辺に当たったかを決める

チップの位置ギリギリに移動するためにはオブジェクトがマップチップの
どの辺に当たったかを決める必要があります。
辺の決め方は以下のようにベクトルの成分を使って行います。

X成分 Y成分 移動方向 当たるチップの辺
0より大きい 0 左辺
0より小さい 0 右辺
0 0より大きい 上辺
0 0より小さい 下辺
collision_0062 決定した辺の特徴は移動ベクトルの方向とは反対の方向になっていることです。 右に移動した場合、左辺と右辺では左辺の方に当たり、 下に移動した場合、上辺と下辺では上辺の方に当たります。

各軸で判定を行う

移動ベクトルを使用した判定はX軸とY軸の各軸ごとに判定を行います。
なぜかというと、XとYの両方の成分を使用したベクトルを使うと、
正しい辺を求められないことがあるからです。

collision_0063

上の図ではベクトルは上辺と左辺に当たっています。
ベクトルのX軸方向は左なので、当たった辺は右辺として判断しますが、
実際に当たっているの左辺です。

このように成分を二つ含んでしまうと、ベクトルで決めた辺と
実際に当たっている辺が異なり、正しい判定が出来なくなることがあります。

軸を分けることのメリット

軸を分けることで得られるメリットもあります。
それは片方の軸がチップに当たっていても、もう片方の軸の移動は可能になることです。

collision_0064

上の図のようにX軸のベクトル(青の矢印)はチップに当たっていますが、
Y軸のベクトル(緑の矢印)は当たっていません。
その為、Y軸の移動は問題なくできます。

オブジェクトに重力を反映する場合は常に地面と当たっているため
この軸を分ける方法などを使って、移動できるようにしなければいけません。

判定方法

判定は以下の手順で行います。
  1. 矩形の辺のチップ位置を割り出す
  2. ベクトルに対応した辺だけ判定に使う
  3. 当たり判定
  4. チップのどの辺と当たっているかを決める
  5. 辺情報などを返す
矩形の辺とチップの辺の二つの辺情報があるので注意してください。

矩形の辺のチップ位置を割り出す

矩形の情報から矩形の各辺のチップ位置を割り出します。

collision_0065

上の図の各辺は次の通りです。

チップ位置
左辺(ピンク) 4F 5F 6F 7F
右辺(オレンジ) 4I 5I 6I 7I
上辺(緑) 4F 4G 4H 4I
下辺(紫) 7F 7G 7H 7I
割り出した辺情報は判定で使うので保存しておきます。

ベクトルに対応した辺だけ判定に使う

四つの辺のチップ位置を割り出しましたが、使用するのは移動ベクトルの辺だけです。
例えば、ベクトルが(x, y) = (1, 0)の場合は右辺、(0, 1)の場合は下辺です。
これでも問題なく判定ができるので、使用する辺と使用しない辺の判別をして、
有効な辺だけで判定をします。

当たり判定

辺の位置とマップチップの位置で判定を行います。
判定は通常の矩形とマップチップの判定と同じです。

チップのどの辺と当たっているかを決める

当たっていると判定された場合はどの辺に当たっているかを決めます。
辺を決めるルールは「どの辺に当たったかを決める」で書いた内容です。

辺情報などを返す

どの辺に当たったか決まったら情報を返します。
返す情報はゲームの仕様によって色々あると思いますが、
今回のサンプルでは以下の情報を返しています。
  • 判定結果
  • 当たったマップチップの辺
  • 辺の座標
    移動ベクトルがX軸ならX座標、Y軸ならY座標のみ
情報を返すのではなく、引数でオブジェクトの座標を取得して
関数内で変更する方法などもありますが、判定関数は当たりの真偽と
判定で得られた情報を通知するものと考えているので、
サンプルではその方法で実装していません。

実装

基本は矩形とマップチップの判定と同じです。
異なる部分だけを抜粋して説明します。

まずは引数に移動ベクトルと結果用の引数を追加しました。

// 辺を使用した矩形とマップ値の当たり
bool CollisionRectAndMapchipEdgeVersion(RectF& src_rect, Vec2 vector, EdgeType& contact_edge, float& contact_edge_position)

次に矩形座標に移動ベクトルを加算します。
ベクトルの加算は呼び出し前に行っても問題ありませんが、
移動ベクトルの方向が分かる情報は必ず必要です。

RectF rect = src_rect;

// 矩形にベクトルを加算する
rect.Bottom += vector.Y;
rect.Top += vector.Y;
rect.Right += vector.X;
rect.Left += vector.X;

矩形の範囲を決める処理が終わったら、各辺の範囲に絞ります。

// 各辺の検索範囲
Vec2I edge_list[(int)EdgeType::EdgeTypeMax][2]
{
	// 左辺
	{
		Vec2I(width_range_ids[start], height_range_ids[start]),
		Vec2I(width_range_ids[start], height_range_ids[end])
	},

	// 右辺
	{
		Vec2I(width_range_ids[end], height_range_ids[start]),
		Vec2I(width_range_ids[end], height_range_ids[end])
	},
	// 上辺
	{
		Vec2I(width_range_ids[start], height_range_ids[start]),
		Vec2I(width_range_ids[end], height_range_ids[start])
	},

	// 下辺
	{
		Vec2I(width_range_ids[start], height_range_ids[end]),
		Vec2I(width_range_ids[end], height_range_ids[end])
	},
};

検索範囲の設定が終了したら、判定で使用する辺を割り出します。
割り出しは移動ベクトルの成分を使います。

// 判定で使用する辺を決める
bool is_check_edge[(int)EdgeType::EdgeTypeMax]
{
	vector.X < 0.0f ? true : false,
	vector.X > 0.0f ? true : false,
	vector.Y < 0.0f ? true : false,
	vector.Y > 0.0f ? true : false,
};

辺の割り出しが済んだら、判定を開始します。

// 判定
for (int i = 0; i < (int)EdgeType::EdgeTypeMax; i++)
{
	// 使用しない軸ならcontinue
	if (is_check_edge[i] == false)
	{
		continue;
	}

	for (int y = edge_list[i][start].Y; y <= edge_list[i][end].Y; y++)
	{
		for (int x = edge_list[i][start].X; x <= edge_list[i][end].X; x++)
		{
			if (Map[y][x] == 1)
			{
				// 当たり
			}
		}
	}
}

当たっていた場合はチップの辺情報と座標を返します。

// 接触情報の取得
void GetContactParameter(EdgeType rect_edge, int chip_id_x, int chip_id_y, EdgeType& contact_edge, float& contact_position)
{
	// チップの座標計算
	Vec2 chip_pos = Vec2((float)chip_id_x * CHIP_SIZE, (float)chip_id_y * CHIP_SIZE);

	switch (rect_edge)
	{
	case EdgeTypeLeft:
		contact_edge = EdgeType::EdgeTypeRight;
		contact_position = chip_pos.X + CHIP_SIZE;
		break;
	case EdgeTypeRight:
		contact_edge = EdgeType::EdgeTypeLeft;
		contact_position = chip_pos.X;
		break;
	case EdgeTypeTop:
		contact_edge = EdgeType::EdgeTypeBottom;
		contact_position = chip_pos.Y + CHIP_SIZE;
		break;
	case EdgeTypeBottom:
		contact_edge = EdgeType::EdgeTypeTop;
		contact_position = chip_pos.Y;
		break;
	}
}

呼び出し側に返している辺情報はマップチップの辺情報なので、
矩形の移動ベクトルとは逆になるので注意してください。

これで判定の実装は終了です。
あとは、呼び出し側で関数の結果情報を使用して処理を進めていきます。