【Unity】キャラクターをジャンプさせる自然な動きを実装

当サイトで紹介する商品・サービス等の外部リンクは、アフィリエイト広告を含む場合があります。
スポンサーリンク
本記事を読むと以下の実行ができます

Rigidbodyを使ったキャラクターのジャンプ実装と、SphereCastによる正確な接地判定を組み合わせて、二段ジャンプや無限ジャンプを起こさない自然なジャンプ動作

Unityでキャラクターをジャンプさせることはゲーム開発での基礎ですが、「ボタンを押しても反応しない」「空中で何度もジャンプできてしまう」といった問題によくぶつかります。

これは多くの場合、接地判定(キャラクターが地面に着いているかどうかの判定)が不十分なことが原因です。

本記事では、RigidbodyとPhysics.SphereCastを使い、CapsuleColliderの実際のサイズに連動した接地判定と、目標到達高さから逆算したジャンプ初速の計算方法をあわせて解説します。

本記事は次の人におすすめ
  • Unityでキャラクターのジャンプ処理を実装したい人
  • AddForceやvelocity直接指定でジャンプを作ったが、二段ジャンプ・無限ジャンプが直らず困っている人
  • タグ判定やisGroundedだけの簡易な接地判定から、より正確な判定方法にステップアップしたい人
  • ジャンプの高さを感覚ではなく数値(目標到達高さ)から計算して設定したい人
  • UpdateとFixedUpdateの役割分担を理解して、物理挙動が絡む処理を正しく設計したい人
スポンサーリンク

ジャンプ実装に必要な要素(SphereCast)とは?

Unityでジャンプ実装を検索すると、「unity rigidbody ジャンプ」「unity 接地判定」といったキーワードで多くの記事がヒットしますが、実装方式は大きく2つの系統に分かれます。

  1. ジャンプそのものの実装(AddForceやvelocityでY方向に力・速度を加える)
  2. 接地判定の実装(タグ判定・Raycast・CheckSphere・SphereCast・isGroundedなど)

この2つは別々に解説されがちですが、実際に「ちゃんと動くジャンプ」を作るには両方を組み合わせる必要があります。

特に接地判定が甘いと、空中にいるのにジャンプ入力を受け付けてしまい、二段ジャンプ・無限ジャンプというよくあるバグにつながります。

本記事で採用するPhysics.SphereCastは、球(Sphere)を指定方向に飛ばして衝突を検出するUnityの物理判定です。

単純なRaycast(線を1本飛ばすだけ)と違い、球という「太さ」を持つ判定なので、地面のわずかな段差や傾斜でも安定して接地を検出できます。

SphereCastを使ったジャンプ処理

今回のジャンプ実装は、以下の4ステップで構成されています。

ジャンプ処理の実装
  • step1
    入力で、ジャンプボタンが押されたかどうかをUpdateで検知する

  • step2
    SphereCastを使い接地判定を行う
  • step3
    地面以外のコライダーやトリガーを誤検知しないようにする

ソースコード

using UnityEngine;

[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider), typeof(PlayerInputHandler))]
public class PlayerJump : MonoBehaviour
{
    [SerializeField] private float jumpHeight = 1.5f;
    [SerializeField] private LayerMask groundMask = ~0;

    private Rigidbody rb;
    private CapsuleCollider capsule;
    private PlayerInputHandler input;
    private bool jumpRequested;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
        capsule = GetComponent<CapsuleCollider>();
        input = GetComponent<PlayerInputHandler>();
    }

    private void Update()
    {
        if (input.JumpWasPressedThisFrame)
        {
            jumpRequested = true;
        }
    }

    private void FixedUpdate()
    {
        Vector3 velocity = rb.linearVelocity;

        if (jumpRequested && IsGrounded())
        {
            velocity.y = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);
        }
        jumpRequested = false;

        rb.linearVelocity = velocity;
    }

    private bool IsGrounded()
    {
        Vector3 origin = transform.TransformPoint(capsule.center);
        float castRadius = capsule.radius * 0.9f;
        float castDistance = capsule.height * 0.5f + 0.15f;
        return Physics.SphereCast(origin, castRadius, Vector3.down, out _, castDistance, groundMask, QueryTriggerInteraction.Ignore);
    }
}

解説

  • 入力の検知とFixedUpdateでの物理処理を分離する設計

ジャンプボタンの入力検知をUpdate、実際の速度変更をFixedUpdateに分けています。

  • Updateはフレームごとに呼ばれる(フレームレートに依存する)ため、入力の取りこぼしを防ぐのに向いています。
  • FixedUpdateは一定の時間間隔で呼ばれる(物理エンジンの更新タイミングに同期する)ため、Rigidbodyを操作する処理はここで行うのが基本です。

Update内で直接Rigidbodyの速度を書き換えてしまうと、フレームレートによって物理挙動が不安定になることがあります。そこで、UpdateではjumpRequestedというフラグを立てるだけにとどめ、実際の速度変更はFixedUpdateでまとめて行うように設計しています。

FixedUpdateの先頭でjumpRequestedを判定した後、必ずfalseにリセットしている点もポイントです。これにより、1回のボタン入力に対してジャンプが1回だけ発生するようになります。

  • SphereCastでCapsuleColliderに連動した接地判定を行う

IsGrounded()メソッドが接地判定の中心部分です。単純に固定の半径・距離を指定するのではなく、CapsuleColliderのプロパティから動的に計算しています。

Vector3 origin = transform.TransformPoint(capsule.center);
float castRadius = capsule.radius * 0.9f;
float castDistance = capsule.height * 0.5f + 0.15f;
  • origin:capsule.center(ローカル座標)をTransformPointでワールド座標に変換し、キャラクターの中心からキャストを開始します。
  • castRadius:コライダーの半径そのままだと壁際でわずかに引っかかることがあるため、0.9fを掛けて少し小さめに設定しています。
  • castDistance:カプセルの半分の高さ(中心から底面まで)に、地面との間に余裕を持たせる0.15fを加えています。

この値をキャラクターのサイズ(CapsuleCollider)から計算する設計にしておくことで、キャラクターモデルを差し替えてCapsuleColliderのサイズが変わっても、接地判定のコードを書き直す必要がありません。

  • ジャンプの初速を目標到達高さから逆算する

ジャンプの高さを「なんとなくの数値」で調整すると、意図した高さにならず何度も微調整することになります。本実装では、到達させたい高さ(jumpHeight)から必要な初速を逆算しています。

velocity.y = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);

これは物理の等加速度運動の公式 v0 = √(2gH) をそのままコードにしたものです。Physics.gravity.yは通常マイナスの値(デフォルトは-9.81)なので、符号を合わせるために-2fを掛けています。

  • H:到達させたい高さ(jumpHeight、インスペクターで調整可能)
  • g:重力加速度(Physics.gravity.yの絶対値)
  • v0:地面を離れる瞬間に必要な初速(Y方向の速度)

インスペクター上でjumpHeightの値を1.5から2.0に変えるだけで、「ちょうど2m飛び上がるジャンプ」を数値ベースで作れるようになります。感覚での調整に頼らず、狙った高さを確実に再現できるのがこの計算式の利点です。

  • レイヤーマスクとQueryTriggerInteraction.Ignoreで誤検知を防ぐ

SphereCastはデフォルトのままだと、地面以外のコライダー(アイテムの当たり判定やエフェクト用のトリガーなど)にも反応してしまうことがあります。これを防ぐために2つの仕組みを併用しています。

  • groundMask(レイヤーマスク): SphereCastの判定対象を、あらかじめ設定した「地面」レイヤーのみに絞り込みます。プレイヤー自身や敵、アイテムなどのレイヤーを対象外にできます。
  • QueryTriggerInteraction.Ignore: IsTriggerが有効なコライダー(当たり判定はあるが物理的にすり抜けるコライダー)を接地判定の対象から除外します。これを指定しないと、トリガーに触れただけで「地面に着地した」と誤判定されることがあります。

この2つを組み合わせることで、地面用のコライダーだけを確実に検出できるようになります。レイヤーマスクの活用に触れていない競合記事も多いため、意図しない誤検知を防ぎたい場合は必ず設定しておきましょう。

実演

Rigidbody・CapsuleCollider・PlayerInputHandlerを設定済みのプレイヤーオブジェクトにアタッチします(RequireComponentにより、この3つが未設定のオブジェクトにはアタッチできません)。

アタッチの流れ
  • STEP1
    プレイヤーオブジェクトにRigidbody・CapsuleColliderを追加

  • STEP2
    同じオブジェクトにスクリプトをアタッチする
  • STEP3
    InspectorでJump Height(到達させたい高さ)を設定する
  • Step 4
    InspectorでGround Maskに地面用のレイヤーを指定する

まとめ

本記事では、Rigidbodyを使ったUnityのジャンプ実装を、以下の4つのポイントに分けて解説しました。

  • 入力検知(Update)と物理処理(FixedUpdate)を分離する設計
  • CapsuleColliderの実サイズに連動したSphereCast接地判定
  • 目標到達高さから初速を逆算するv0 = √(2gH)の計算式
  • レイヤーマスクとQueryTriggerInteraction.Ignoreによる誤検知防止


これらを組み合わせることで、二段ジャンプ・無限ジャンプといった典型的なバグを防ぎつつ、狙った高さに正確に跳ぶジャンプ処理を作れます。

まずは本記事のコードをそのまま自分のプロジェクトに組み込み、jumpHeightやgroundMaskの値を自分のキャラクターに合わせて調整するところから始めてみてください。

接地判定の考え方は壁ジャンプや二段ジャンプなど、他のアクションを追加する際の土台にもなります。

タイトルとURLをコピーしました