Skip to main content

当たり判定

Unityのコライダーのおかげで、当たり判定は自動的に計算され、「OnCollisionXXX」「OnTriggerXXX」で衝突があった時にお知らせを受け取る、処理ができるが、実際に裏側でなにを行っているのか理解してみましょう。

球体同士

まず、一番楽なのは、球体同士の当たり判定である。以下の図を確認し、考えてみましょう

image.png

円「C」(中心点 (cx, cy)、半径 cr )と「D」(中心点 (dx, dy)、半径 dr )があるとして、中心の間の距離は、中心の差のノルムである:

 

image.png

 この距離は半径の和よりも小さければ、2つの円が衝突しているといえる。

image.png

image.png

つまり

image.png

練習

Unityでコライダーなしの2つの球体を作成し、MySphereCollider を作成し、半径を設定できるようにしてください(注意:コライダーの半径とメッシュの半径を合わせてください)

つぎ、SphereCollisionCheck のスクリプトを作成し

  • すべての MySphereCollider を取得
  • コライダー同士の距離を計算
  • 半径よりも小さければ、色を変える
MySphereCollider スクリプト
// 当たり判定:球体のコライダー
public class MySphereCollider : MonoBehaviour
{
	// 半径
	[SerializeField] 
	private float radius;

	// 半径を返す
	public float GetRadius()
	{
		return radius;
	}

	// 色を変える
	public void ChangeColor(Color color)
	{
		// MeshRendererの色を変える
		MeshRenderer mr = GetComponent<MeshRenderer>();
		mr.material.color = color;
	}
}
SphereCollisionCheck スクリプト
// コライダー同士の当たり判定を確認
public class SphereCollisionCheck : MonoBehaviour
{
	// すべてのコライダー
	private MySphereCollider[] colliders;

	private void Start()
	{
		// FindObjectsByType = 「このスクリプトを付いているオブジェクトのすべて下さい」
		colliders = FindObjectsByType<MySphereCollider>(FindObjectsSortMode.None);
	}

	void Update()
	{
		// 初期値はすべて「false」
		bool[] isCollision = new bool[colliders.Length];

		// 全部 vs 全部を比較
		for (int i = 0; i < colliders.Length - 1; i++)
		{
			for (int j = i + 1; j < colliders.Length; j++)
			{
				MySphereCollider iObje = colliders[i];
				MySphereCollider jObje = colliders[j];

				// 中心と中心の距離を計算
				float distance = (iObje.transform.position - jObje.transform.position).magnitude;

				// 2つの半径の足し算を計算
				float radiusSum = iObje.GetRadius() + jObje.GetRadius();

				// 当たり判定
				if (distance - radiusSum <= 0)
				{
					isCollision[i] = true;
					isCollision[j] = true;
				}
			}
		}

		// 衝突しているコライダーだけの色を変える
		for (int i = 0; i < isCollision.Length; i++)
		{
			if (isCollision[i])
				colliders[i].ChangeColor(Color.red);
			else
				colliders[i].ChangeColor(Color.white);
		}
	}
}

箱同士(回転なし)

BoxCollider の処理を把握しましょう。2つの箱「C」と「D」があり、それぞれが「最小X」「最大X」「最小Y」「最大Y」の数値を持っている。

image.png

この場合は:

  • ある箱の「最大X」は他の箱の「最小X」よりも小さければ → 衝突しない
  • ある箱の「最大Y」は他の箱の「最小Y」よりも小さければ → 衝突しない
  • それ以外、衝突する

3次元の場合はZも含める。

練習

Unityでコライダーなしの2つの立方体を作成し、MyBoxCollider を作成し、幅、高さ、深さを設定できるようにしてください(注意:コライダーの半径とメッシュの半径を合わせてください)

つぎ、BoxCollisionCheck のスクリプトを作成し

  • すべての MyBoxCollider を取得
  • 各軸の「最大」と「最小」を比較
  • 衝突したら、色を変える
MyBoxCollider スクリプト
// 箱コライダー
public class MyBoxCollider : MonoBehaviour
{
    // サイズ(幅、高さ、深さ)
    [SerializeField]
    private Vector3 size;

    // 各軸の最大位置を返す
    public Vector3 GetMax()
    {
        return transform.position + size / 2;
    }
    
    // 各軸の最小位置を返す
    public Vector3 GetMin()
    {
        return transform.position - size / 2;
    }
    
    // 色を変える
    public void ChangeColor(Color color)
    {
        // MeshRendererの色を変える
        MeshRenderer mr = GetComponent<MeshRenderer>();
        mr.material.color = color;
    }
}
BoxCollisionCheck スクリプト
// MyBoxCollider同士の当たり判定を確認
public class BoxCollisionCheck : MonoBehaviour
{
    // すべてのコライダー
    private MyBoxCollider[] colliders;

    private void Start()
    {
        // FindObjectsByType = 「このスクリプトを付いているオブジェクトのすべて下さい」
        colliders = FindObjectsByType<MyBoxCollider>(FindObjectsSortMode.None);
    }

    void Update()
    {
        // 初期値はすべて「false」
        bool[] isCollision = new bool[colliders.Length];

        // 全部 vs 全部を比較
        for (int i = 0; i < colliders.Length - 1; i++)
        {
            for (int j = i + 1; j < colliders.Length; j++)
            {
                // 最大と最小位置を取得
                Vector3 iMax = colliders[i].GetMax();
                Vector3 iMin = colliders[i].GetMin();
                Vector3 jMax = colliders[j].GetMax();
                Vector3 jMin = colliders[j].GetMin();
                
                // 軸ずつを確認
                if (iMax.x < jMin.x) continue;
                if (iMax.y < jMin.y) continue;
                if (iMax.z < jMin.z) continue;
                
                // 逆の比較も
                if (jMax.x < iMin.x) continue;
                if (jMax.y < iMin.y) continue;
                if (jMax.z < iMin.z) continue;   
                
                // 当たった!
                isCollision[i] = true;
                isCollision[j] = true;
            }
        }

        // 衝突しているコライダーだけの色を変える
        for (int i = 0; i < isCollision.Length; i++)
        {
            if (isCollision[i])
                colliders[i].ChangeColor(Color.red);
            else
                colliders[i].ChangeColor(Color.white);
        }
    }
}

球体と箱

SphereCollider と BoxCollider の当たり判定もあるので、その処理を学ぼう