【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させています。
(辻褄合わせのために距離にもオフセットを考慮させている)
最後に
いかがでしたでしょうか。
前回の記事から追記しただけですので、基本こちらのスクリプトを活用ください。
以上、だらはでした。
ディスカッション
コメント一覧
まだ、コメントがありません