【Unity】衝突判定をゼロから実装 -球と球-

UnityはColliderをアタッチするだけでお手軽に衝突判定ができます。
しかし処理負荷を理解する意味でも、球といろんな形状との衝突判定くらいはやっておきたいものです。

というわけでゼロから実装する衝突判定シリーズ第一弾、球と球の衝突判定です。

考え方

球体と球体の衝突判定は非常に簡単で処理負荷も小さいです。
二つの球の中心点の間の距離を測り、それが二つの球の半径の合計以下であれば衝突しているということになります。

f:id:halya_11:20180816234822p:plain

インタフェイスと準備

まずインタフェイスを定義します。
球といろんなコライダーとの衝突判定を考えるため、
球体のインタフェイスISphereとコライダーのインタフェイスIColliderを作ります。

using UnityEngine;

public interface ICollider
{
    /// <summary>
    /// 球との当たり判定
    /// </summary>
    bool CheckSphere(ISphere collider);
}

public interface ISphere
{
    /// <summary>
    /// 中心のローカル座標
    /// </summary>
    Vector3 Center { get; }
    /// <summary>
    /// 半径
    /// </summary>
    float Radius { get; }
    /// <summary>
    /// 中心のワールド座標
    /// </summary>
    Vector3 WorldCenter { get; }
}

次にこれらを使うクラスを定義します。

using UnityEngine;

public class SampleCollideDetector : MonoBehaviour {

    [SerializeField]
    private GameObject _sphereObj;
    [SerializeField]
    private Transform _targetColliderRoot;

    private ISphere _sphere;
    private ICollider[] _colliders;
    private bool _didCollide;

    private void Awake()
    {
        _sphere = _sphereObj.GetComponent<ISphere>();
        _colliders = _targetColliderRoot.GetComponentsInChildren<ICollider>();
    }

    private void Update () {
        _didCollide = false;
        if (_sphere != null && _colliders != null)
        {
            for (int i = 0; i < _colliders.Length; i++)
            {
                // 衝突判定
                _didCollide |= _colliders[i].CheckSphere(_sphere);
            }
        }
    }

    private void OnDrawGizmos()
    {
        if (_didCollide)
        {
            // 衝突しているときのみSphereを描画する
            var color = Gizmos.color;
            Gizmos.color = Color.red;
            Gizmos.DrawSphere(_sphere.WorldCenter, _sphere.Radius);
            Gizmos.color = color;
        }
    }
}

毎フレームISphereとIColliderの衝突判定を行い、衝突していたら赤いSphereGizmoに描画します。
これであとは実際にコライダーを作るだけです。

コライダーのソースコード

上記二つのインタフェイスを定義したコライダーを作ります。

using UnityEngine;

public class ColliderSphere : MonoBehaviour, ICollider, ISphere {

    [SerializeField]
    private Vector3 _center = Vector3.zero;
    public Vector3 Center { get { return _center; } }
    [SerializeField]
    private float _radius = 0.5f;
    public float Radius { get { return _radius; }  }
    
    public Vector3 WorldCenter { get { return _transform.position + _center; } }
    
    private Transform _transform;

    private void Awake()
    {
        _transform = transform;
    }

    /// <summary>
    /// 球との当たり判定
    /// </summary>
    public bool CheckSphere(ISphere collider)
    {
        var collideDistance = Radius + collider.Radius;
        return (WorldCenter - collider.WorldCenter).sqrMagnitude <= collideDistance * collideDistance;
    }

    private void OnDrawGizmos()
    {
        var color = Gizmos.color;
        Gizmos.color = Color.blue;
        Gizmos.DrawWireSphere(transform.position + _center, _radius);
        Gizmos.color = color;
    }
}

衝突判定は上記で説明した通りの方法で行っています。

結果

ColliderSphereをアタッチしたGameObjectをいくつか作り、
SampleCollideDetectorのフィールドに適宜セットします。

正常に判定されていることが確認できました。