点と円の当たり

■概要

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

■判定方法

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

●当たってる
	hit_0010
	hit_0010

●当たってない
	hit_0009
	hit_0009

■計算方法

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

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

	hit_0006
	hit_0006

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

	hit_0011
	hit_0011

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

	hit_0012
	hit_0012

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

	hit_0013
	hit_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();
		}

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

	・関数仕様
		関数名:
			IsPointAndCircleHit

		戻り値:
			bool
				true => 当たり
				false => 当たってない

		引数:
			Point point => 判定用点データ

			Circle circle => 判定用円データ

		内容:
			pointとcircleのデータを使用し、当たり判定チェックをする
			この判定で点と円が当たっていればtrue、当たていなければfalseを返す

	・定義
		bool IsPointAndCircleHit(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 IsPointAndCircleHit(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 (IsPointAndCircleHit(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を使用しなくなったので、処理コストが軽減しました。