円と円の当たり

■概要

円と円の判定は当たり判定の中で簡単な部類に入りますが、
シューティングの当たり判定や、処理コストの高い判定を行うかを判定するための
判定処理に使われるなど、ゲーム内で高頻度で使用されることが多い判定です。

■判定方法

判定方法は二つの円の中心からの距離を測り、その距離が二つの円の半径の和以下ならば当たり、
半径の和よりも大きければ当たっていないとなります。

●当たってる
	hit_0004
	hit_0004

●当たってない
	hit_0003
	hit_0003

■計算方法

円の判定で必要となる計算は二つの円の距離の算出と半径同士の足し算です。
足し算は問題ないと思いますので、円の距離の算出方法のみを記述します。

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

	hit_0006
	hit_0006

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

	hit_0005
	hit_0005

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

	hit_0007
	hit_0007

●距離と半径の和を比較する
	2点の座標を図る計算から二つの円の距離が算出されました。
	この距離と二つの円の半径の和を比較して、距離の方が小さければ当たりと判断し、
	大きければ当たってないと判断します。

	hit_0008
	hit_0008

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

■プログラム

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

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

	void main(void)
	{
		// 円1の情報
		float circle1_pos_x = 20.0f;
		float circle1_pos_y = 40.0f;
		float circle1_radius = 5.0f;
			
		// 円2の情報
		float circle2_pos_x = 25.0f;
		float circle2_pos_y = 30.0f;
		float circle2_radius = 8.0f;

		float a = circle1_pos_x - circle2_pos_x;
		float b = circle1_pos_y - circle2_pos_y;
		float c = sqrt(a * a + b * b);

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

		getchar();
	}

●当たり情報をまとめる
	当たり判定は基本的に弾の数分、敵の数分など数が必要となります。
	そのため、先ほどの例のように1つの円の当たり判定情報を用意するために
	変数を3つ宣言していては大変面倒です。
	そこで、構造体やクラスにまとめて管理することで変数宣言の手間を省きます。

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

	void main(void)
	{
		// 円1の情報
		Circle circle1 = {
			20.0f,
			40.0f,
			5.0f
		};
			
		// 円2の情報
		Circle circle2 = {
			25.0f,
			30.0f,
			8.0f,
		};

		float a = circle1.m_PosX - circle2.m_PosX;
		float b = circle1.m_PosY - circle2.m_PosY;
		float c = sqrt(a * a + b * b);
		float sum_radius = circle1.m_Radius + circle2.m_Radius;

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

		getchar();
	}

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

	・関数仕様
		関数名:
			IsCircleHit

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

		引数:
			Circle circle1 => 判定用円データその1

			Circle circle2 => 判定用円データその2

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

	・定義
		bool IsCircleHit(Circle circle1, Circle circle2)
		{
			float a = circle1.m_PosX - circle2.m_PosX;
			float b = circle1.m_PosY - circle2.m_PosY;
			float c = sqrt(a * a + b * b);
			float sum_radius = circle1.m_Radius + circle2.m_Radius;

			if (c <= sum_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;

	bool IsCircleHit(Circle circle1, Circle circle2)
	{
		float a = circle1.m_PosX - circle2.m_PosX;
		float b = circle1.m_PosY - circle2.m_PosY;
		float c = sqrt(a * a + b * b);
		float sum_radius = circle1.m_Radius + circle2.m_Radius;

		printf("a = %.2f b = %.2f c = %.2f\n", a, b, c);
		printf("%.2f <= %.2f\n", c, sum_radius);
			
		if (c <= sum_radius)
		{
			return true;
		}
		return false;
	}

	void main(void)
	{
		// 円1の情報
		Circle circle1 = {
			20.0f,
			40.0f,
			5.0f
		};
			
		// 円2の情報
		Circle circle2 = {
			25.0f,
			30.0f,
			8.0f,
		};

		if (IsCircleHit(circle1, circle2) == true)
		{
			printf("当たってる\n");
		} else {
			printf("当たってない\n");
		}

		getchar();
	}

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

		if (c <= sum_radius * sum_radius)
		{
			return true;
		}

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