【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させています。
(辻褄合わせのために距離にもオフセットを考慮させている)

最後に

いかがでしたでしょうか。
前回の記事から追記しただけですので、基本こちらのスクリプトを活用ください。

以上、だらはでした。

スポンサーリンク

応用

Posted by daraha_gm