点と円の当たり


概要

点と円の判定は当たり判定の中で簡単な部類に入ります。
ゲーム内ではマウスポインタやスマホのタップした座標と
ボタンの当たり判定などで使用します。

判定方法

判定方法は点の座標と円の中心座標との距離を測り、
その距離が円の半径より小さければ当たり、
半径よりも大きければ当たっていないとなります。

当たってる

collision_0010

当たってない

collision_0009

計算方法

円の判定で必要となる計算は点と円の距離の算出のみです。

2点間の距離を測る計算式

2点間の距離を測る計算式は以下のように単純な式となっています。

collision_0006

円の距離を測る

距離を測るには点と円のX軸とY軸の長さが必要です。
これは片方の座標からもう一つの座標で引き算を行うことで求められるので、
赤点と青円の座標を以下のように考えます。

collision_0011

この座標を2点間の距離を割り出す式に当てはめてます。

collision_0012

距離と半径を比較する

2点間の距離を測る計算によって点と円の距離が算出されました。
この距離と円の半径を比較して、距離の方が小さければ当たりと判断し、
大きければ当たってないと判断します。

collision_0013

※.上の画像で√の中にc2を入れているのは計算ではa2 + b2 = c2となっており、
    c2のままでは求めたい長さではないので二乗を打ち消すために√を使用しています。

実装

以下でプログラムで実装する方法の例を紹介します。
※紹介する実装方法が絶対ではありません。

単純な実装

プログラムで点と円の当たりを実装するには「点の座標(X、Yともに必要)」
「円の座標(X、Yともに必要)」「半径」が必要なので、
それらを変数として用意して判定を行います。

#include <stdio.h>
#include <math.h>

void main(void)
{
	// 点の情報
	float point_pos_x = 20.0f;
	float point_pos_y = 40.0f;
			
	// 円の情報
	float circle_pos_x = 25.0f;
	float circle_pos_y = 30.0f;
	float circle_radius = 15.0f;

	float a = point_pos_x - circle_pos_x;
	float b = point_pos_y - circle_pos_y;
	float c = sqrt(a * a + b * b);

	if (c <= circle_radius)
	{
		printf("a = %.2f b = %.2f c = %.2f\n", a, b, c);
		printf("%.2f <= %.2f\n", c, circle_radius);
		printf("当たってる\n");
	}
	else
	{
		printf("当たってない\n");
	}

	getchar();
}

当たり情報をまとめる

当たり判定は敵の数分など判定対象が複数用意されることが基本です。
そのため、先ほどの例のように毎回点や円の当たり判定情報を用意するために
変数を宣言するのは非常に手間になっていきます。
そこで、構造体やクラスにまとめて管理することで変数宣言の手間を省きます。

#include <stdio.h>
#include <math.h>
	
// 円データ
typedef struct
{
	float m_PosX;		// X座標
	float m_PosY;		// Y座標
	float m_Radius;		// 半径
} Circle;

// 点データ
typedef struct
{
	float m_PosX;		// X座標
	float m_PosY;		// Y座標
} Point;

void main(void)
{
	// 点の情報
	Point point = {
		20.0f,
		40.0f,
	};
			
	// 円の情報
	Circle circle = {
		25.0f,
		30.0f,
		15.0f,
	};

	float a = point.m_PosX - circle.m_PosX;
	float b = point.m_PosY - circle.m_PosY;
	float c = sqrt(a * a + b * b);

	if (c <= circle.m_Radius)
	{
		printf("a = %.2f b = %.2f c = %.2f\n", a, b, c);
		printf("%.2f <= %.2f\n", c, circle.m_Radius);
		printf("当たってる\n");
	}
	else 
	{
		printf("当たってない\n");
	}

	getchar();
}

関数化する

当たり判定は様々な場所で使用する可能性があります。
その時に毎回上のコードを書いていたら手間なので
関数化しておいて簡単に使えるようにしておいた方が作業時間を短縮できます。

OnCollisionPointAndCircle
内容 pointとcircleのデータを使用し、当たり判定チェックをする
この判定で点と円が当たっていればtrue、当たていなければfalseを返す
戻り値 bool型で当ったていればtrue、
当たっていなければfalseを返す
引数の型 説明
Point 判定用点データ
Circle 判定用円データ

定義

bool OnCollisionPointAndCircle(Point point, Circle circle)
{
	float a = point.m_PosX - circle.m_PosX;
	float b = point.m_PosY - circle.m_PosY;
	float c = sqrt(a * a + b * b);

	if (c <= circle.m_Radius)
	{
		return true;
	}
	return false;
}

使用例

#include <stdio.h>
#include <math.h>

// 円データ
typedef struct
{
	float m_PosX;		// X座標
	float m_PosY;		// Y座標
	float m_Radius;		// 半径
} Circle;

// 点データ
typedef struct
{
	float m_PosX;		// X座標
	float m_PosY;		// Y座標
} Point;

bool OnCollisionPointAndCircle(Point point, Circle circle)
{
	float a = point.m_PosX - circle.m_PosX;
	float b = point.m_PosY - circle.m_PosY;
	float c = sqrt(a * a + b * b);

	if (c <= circle.m_Radius)
	{
		return true;
	}
	return false;
}

void main(void)
{
	// 点の情報
	Point point = {
		20.0f,
		40.0f,
	};
			
	// 円の情報
	Circle circle = {
		25.0f,
		30.0f,
		15.0f,
	};

	if (OnCollisionPointAndCircle(point, circle) == true)
	{
		printf("当たってる\n");
	}
	else
	{
		printf("当たってない\n");
	}

	getchar();
}

高速化

点と円の当たり判定は若干ですが、処理速度を速めることができます。
それはsqrt関数を使用しないことです。
sqrtは平方根を求める関数で、距離を正確に求めるためには必要な計算ですが、
パフォーマンスが良い関数とは言えません。
この当たり判定では正確な距離は必要なく、半径とどちらが大きいかの比較で
使用するだけなので以下のように変更しても問題ありません。
		
float a = point.m_PosX - circle.m_PosX;
float b = point.m_PosY - circle.m_PosY;
float c = a * a + b * b;

if (c <= circle.m_Radius * circle.m_Radius)
{
	return true;
}


上の例ではsqrtは使用せず、半径を2乗しています。
このような方法でも問題なく、判定は行えます。
これでsqrtを使用しなくなったので、処理コストが軽減しました。