LoginSignup
0
1

More than 1 year has passed since last update.

地面と接触しているかを判定する [Unity2D]

Posted at

Unityで2Dゲームを実装する際に、地面に立っているか・地面に触れているかどうかを判定するスクリプトを実装します。

実装

GroundCheck.cs
using Sirenix.OdinInspector;
using UnityEngine;
using Updates;

namespace Movements
{
    [DisallowMultipleComponent]
    public class GroundCheck : MonoBehaviour
    {
        [Title("初期値設定")] public float GroundLimitAngle = 30f;
        public float FootThreshold = 0.4f;
        // 検知できる衝突の最大数
        [SerializeField] private int maxCollisionCount = 8;
        [Title("デバッグ")] [SerializeField] [ReadOnly]
        private bool isGrounded = true;
        public bool IsGrounded => isGrounded;
        [SerializeField] [ReadOnly] private bool touchGround = false;
        public bool TouchGround => touchGround;
        [SerializeField] [ReadOnly] private Vector2 normal;
        public Vector2 Normal => normal;
        // 地面として扱わないゲームオブジェクト
        [ReadOnly] public GameObject NotGroundObject;

        private ContactPoint2D[] collisionContacts;
        private int currentContactCount = 0;
        private Transform tf;

        private void Awake()
        {
            tf = transform;
        }

        private void Start()
        {
            collisionContacts = new ContactPoint2D[maxCollisionCount];
        }

        public void FixedUpdate()
        {
            CheckGrounded();

            // フラグを下ろす
            currentContactCount = 0;
        }

        private void CheckGrounded()
        {
            // 最初にいくつあるかを数える
            // 衝突が1つだけなら、angleの判定を行う
            if (currentContactCount <= 0)
            {
                isGrounded = false;
                touchGround = false;
                return;
            }

            touchGround = true;

            if (currentContactCount == 1)
            {
                // 足元の判定を行う
                if ((collisionContacts[0].point - (Vector2)tf.position).y >= FootThreshold)
                {
                    isGrounded = false;
                    return;
                }

                var targetNormal = collisionContacts[0].normal;
                var angle = Vector2.Angle(Vector2.up, targetNormal);
                // 衝突対象がBoxCollider2Dで、頂点に衝突した場合
                // 前回の状態を維持する
                if (collisionContacts[0].collider is BoxCollider2D boxCollider)
                {
                    var colliderAngle = Vector2.Angle(boxCollider.transform.up, targetNormal);
                    if (!Mathf.Approximately(colliderAngle % 90f, 0f))
                    {
                        return;
                    }
                }

                if (angle > GroundLimitAngle)
                {
                    isGrounded = false;
                }
                else
                {
                    isGrounded = true;
                    normal = targetNormal;
                }

                return;
            }
            else
            {
                var averageNormal = Vector2.zero;
                var minNormal = Vector2.zero;
                var minAngle = float.MaxValue;
                var normalCount = 0;
                for (var i = 0; i < currentContactCount; i++)
                {
                    // 足元の判定を行う
                    if ((collisionContacts[i].point - (Vector2)tf.position).y >= FootThreshold)
                    {
                        continue;
                    }

                    var n = collisionContacts[i].normal;
                    var a = Vector2.Angle(Vector2.up, n);
                    if (a < minAngle)
                    {
                        minNormal = n;
                        minAngle = a;
                    }

                    averageNormal += n;
                    normalCount++;
                }

                averageNormal /= normalCount;
                var averageAngle = Vector2.Angle(Vector2.up, averageNormal);
                Vector2 currentNormal;
                var currentAngle = 0f;
                if (minAngle < averageAngle)
                {
                    currentNormal = minNormal;
                    currentAngle = minAngle;
                }
                else
                {
                    currentNormal = averageNormal;
                    currentAngle = averageAngle;
                }

                if (currentAngle > GroundLimitAngle)
                {
                    isGrounded = false;
                }
                else
                {
                    isGrounded = true;
                    normal = currentNormal;
                }
            }
        }

        private void OnCollisionStay2D(Collision2D col)
        {
            TryGetGround(col);
        }

        private void TryGetGround(Collision2D col)
        {
            if (NotGroundObject != null && col.gameObject == NotGroundObject) return;

            collisionContacts[currentContactCount] = col.contacts[0];
            currentContactCount++;
        }
    }
}

使い方

接触判定を行いたいGameObjectにアタッチして使います。
IsTouchGroundはどこでかを問わず他の物に接触しているかどうかを、IsGroundプロパティは実際に地面に立っているかどうかを返します。
Normalプロパティは自分が今立っている地面の法線ベクトルを返します。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1