【Unity3D】ジャンプ後の接地確認はPhysics.SphereCastを使う

どうも、だらはです。
今回は、ジャンプ後の接地確認で使うPhysics.SphereCastの使い方をまとめたいと思います。
接地確認は、一般的にはコライダーは使わない
接地確認するには、接地確認用のコライダーを足に接地する方法がありますが、接地判定が不正確になる場合があるため推奨されないそうです。
適当なモデルを作成して検証しましたが再現できず、ChatGPTに聞いてみましたが具体例を得られませんでした。
ただし、Physics.SphereCastは高速に動作するそうで業界のセオリーだそうなのでこちらを使った方がよさそうです。(誰か具体例を教えて下さい~)
◆本記事のスクリプトでこんな感じになります

接地確認のスクリプト
早速ですがスクリプトを記載します。
以前まとめたFPSコントローラの記事のスクリプトに追記する形を取りたいと思います。
◆参考
主な追記分は、「//接地確認」と記載されている行以降です。
Physics.SphereCastを用いて接地確認をしています。
using UnityEngine;
using UnityEngine.EventSystems;
public class Player : MonoBehaviour
{
    //移動操作を受け付けるタッチエリア
    [SerializeField] DragHandler _moveController;
    //移動速度(m/秒)
    [SerializeField] float _movePerSecond;
    //移動操作としてタッチ開始したスクリーン座標
    Vector2 _movePointerPosBegin;
    Vector3 _moveVector;
    [SerializeField] Camera _camera;
    //カメラ操作を受け付けるタッチエリア
    [SerializeField] DragHandler _lookController;
    //カメラ速度(°/px)
    [SerializeField] float _angularPerPixel = 1f;
    //カメラ操作として前フレームにタッチしたキャンバス上の座標
    Vector2 _lookPointerPosPre;
    //アニメーター制御用
    [SerializeField] GameObject objPlayerModel;
    Animator animPlayer;
    //微小移動判断のためのドラッグ量閾値
    float lenDragThreshold = 0.05f;
    //微小移動判断のための長さ初期化
    float lenScreen = 1f;
    float lenDrag = 1f;
    //接地確認用Physics.SphereCastのパラメタ
    [SerializeField] float groundCheckRadius = 0.2f;        //半径
    [SerializeField] float groundCheckDistance = 0.1f;      //距離
    [SerializeField] float groundCheckDistanceOfst = 1f;    //Ray始点調整用
    bool isGrounded = false;
    private void Awake()
    {
        _moveController.OnBeginDragEvent += OnBeginDragMove;
        _moveController.OnDragEvent += OnDragMove;
        _moveController.OnEndDragEvent += OnEndDragMove;
        _lookController.OnBeginDragEvent += OnBeginDragLook;
        _lookController.OnDragEvent += OnDragLook;
    }
    void Start()
    {
        animPlayer = objPlayerModel.GetComponent<Animator>();
        lenScreen = (new Vector2(Screen.width, Screen.height)).magnitude;
    }
    void Update()
    {
        //接地確認
        CheckGroundStatus();
        //移動量算出
        UpdateMove(_moveVector);
    }
    //移動操作
    //ラッグ操作開始(移動用)
    void OnBeginDragMove(PointerEventData eventData)
    {
        //タッチ開始位置を保持
        _movePointerPosBegin = eventData.position;
    }
    //ドラッグ操作中(移動用)
    void OnDragMove(PointerEventData eventData)
    {
        //タッチ開始位置からのスワイプ量を移動ベクトルにする
        var vector = eventData.position - _movePointerPosBegin;
        _moveVector = new Vector3(vector.x, 0f, vector.y);
        //移動ベクトル量の長さを取得
        lenDrag = vector.magnitude;
    }
    void UpdateMove(Vector3 vector)
    {
        float spdGain = 1;
        float lenRatio = lenDrag/lenScreen;
        //対画面比でドラッグ量が少なければ微小移動
        if(lenRatio < lenDragThreshold)
        {
            //ドラッグ量に応じて減速量を調整
            spdGain = 1f/lenDragThreshold * lenRatio;
        }
        //現在向きを基準に、入力されたベクトルに向かって移動
        Vector3 nextPos = transform.rotation * vector.normalized * _movePerSecond * Time.deltaTime * spdGain;
        float tagP = Mathf.Pow(transform.position.x + nextPos.x, 2) + Mathf.Pow(transform.position.y + nextPos.y, 2) + Mathf.Pow(transform.position.z + nextPos.z, 2);
        float nowP = Mathf.Pow(transform.position.x, 2) + Mathf.Pow(transform.position.y, 2) + Mathf.Pow(transform.position.z, 2);
        //アニメーションにSpeedを渡す(平方根の計算が重いかも)
        animPlayer.SetFloat("Speed", Mathf.Sqrt((Mathf.Abs(tagP - nowP))));
        transform.position += nextPos;
    }
    //ドラッグ操作終了(移動用)
    void OnEndDragMove(PointerEventData eventData)
    {
        //移動ベクトルを解消
        _moveVector = Vector3.zero;
    }
    //カメラ操作
    void OnBeginDragLook(PointerEventData eventData)
    {
        _lookPointerPosPre = _lookController.GetPositionOnCanvas(eventData.position);
    }
    void OnDragLook(PointerEventData eventData)
    {
        var pointerPosOnCanvas = _lookController.GetPositionOnCanvas(eventData.position);
        //キャンバス上で前フレームから何px操作したかを計算
        var vector = pointerPosOnCanvas - _lookPointerPosPre;
        //操作量に応じてカメラを回転
        LookRotate(new Vector2(-vector.y, vector.x));
        _lookPointerPosPre = pointerPosOnCanvas;
    }
    void LookRotate(Vector2 angles)
    {
        Vector2 deltaAngles = angles * _angularPerPixel;
        transform.eulerAngles += new Vector3(0f, deltaAngles.y);
        //縦方向の回転角度制限
        Vector3 mXAxiz = _camera.transform.localEulerAngles;
        var x = mXAxiz.x + deltaAngles.x;
        if (x >= 180)
        {
            x = x - 360;
        }
        if (x >= -60 && x <= 60)
        {
            mXAxiz.x = x;
            _camera.transform.localEulerAngles = mXAxiz;
        }
    }
    //接地確認
    void CheckGroundStatus()
    {
        RaycastHit hit;
        if (Physics.SphereCast(
            transform.position + (new Vector3(0, groundCheckDistanceOfst, 0)),
            groundCheckRadius,
            Vector3.down,
            out hit,
            groundCheckDistance + groundCheckDistanceOfst,
            LayerMask.GetMask("Default")))
        {
            isGrounded = true;
            Debug.Log("接地してる");
        }
        else
        {
            isGrounded = false;
            Debug.Log("接地してない");
        }
        //球状のRayまでの距離を可視化
        Debug.DrawRay(transform.position + (new Vector3(0, groundCheckDistanceOfst, 0)),
            Vector3.down * (groundCheckDistance + groundCheckDistanceOfst), Color.red);
    }
    //可視化
    void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position + Vector3.down * groundCheckDistance, groundCheckRadius);
    }
}
Physics.SphereCastの引数は以下の通りです。
- transform.position~:始点
 - groundCheckRadius:半径
 - Vector3.down:方向
 - groundCheckDistance~:距離
 - LayerMask:接地判定したいレイヤー
 
注意点として、Physics.SphereCastの始点が半径内にあると機能してくれません。
対策として、本スクリプトではプレイヤーの基点をオフセットした位置からPhysics.SphereCastさせています。
(辻褄合わせのために距離にもオフセットを考慮させている)
最後に
いかがでしたでしょうか。
前回の記事から追記しただけですので、基本こちらのスクリプトを活用ください。
以上、だらはでした。







ディスカッション
コメント一覧
まだ、コメントがありません