Unityを触り始めた頃は、つい「とりあえず全部を1つのPlayerスクリプトのUpdateに書く」という実装をしがちですよね。移動、アニメーション、ジャンプ、入力、エフェクト…すべてが1クラスに詰め込まれていくと、少し仕様を変えたいだけでも地獄のような修正作業になります。

たとえば「2段ジャンプを追加したい」「ジャンプ時にパーティクルを出したい」といった要件が出たとき、巨大なPlayerスクリプトに if 文を足しまくると、あっという間にカオスになります。

そこで今回は、「ジャンプ処理だけを担当する小さなコンポーネント」として、2段ジャンプ付きの DoubleJump コンポーネントを作っていきます。
Rigidbody ベースのシンプルなプレイヤーにポン付けできて、空中で1回だけ追加ジャンプ、さらにジャンプ時にパーティクルを再生できるようにしてみましょう。

【Unity】2段ジャンプで操作感アップ!「DoubleJump」コンポーネント

フルソースコード


using UnityEngine;

/// <summary>
/// シンプルな2段ジャンプコンポーネント。
/// - Rigidbody を使ったジャンプ力の付与
/// - 地面判定
/// - 空中で1回だけ追加ジャンプ
/// - ジャンプ時のパーティクル再生
///
/// 入力は「Jump」ボタン(旧InputManagerのSpaceなど)を想定。
/// 新InputSystemを使う場合は、入力部分を書き換えてください。
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class DoubleJump : MonoBehaviour
{
    [Header("ジャンプ設定")]
    [SerializeField] private float jumpForce = 7.0f;          // 1回のジャンプに与える上方向の力
    [SerializeField] private float extraJumpForceScale = 1.0f; // 2段ジャンプ時の力倍率(1.0で同じ)

    [Header("地面判定設定")]
    [SerializeField] private Transform groundCheck;           // 足元の位置(空オブジェクトなど)
    [SerializeField] private float groundCheckRadius = 0.2f;  // 地面判定の半径
    [SerializeField] private LayerMask groundLayer;           // 地面とみなすレイヤー

    [Header("パーティクル設定")]
    [SerializeField] private ParticleSystem jumpParticle;     // ジャンプ時に再生するパーティクル

    [Header("デバッグ")]
    [SerializeField] private bool drawGizmos = true;          // シーンビューに地面判定を表示するか

    // コンポーネント内部状態
    private Rigidbody rb;
    private bool isGrounded;
    private bool canDoubleJump;

    // 入力の立ち上がり検出用
    private bool jumpButtonHeldLastFrame = false;

    private void Awake()
    {
        // 必須コンポーネントの取得
        rb = GetComponent<Rigidbody>();

        // groundCheck が未設定の場合は、自分のTransformを使う(足元に別オブジェクトを置かない場合)
        if (groundCheck == null)
        {
            groundCheck = this.transform;
        }
    }

    private void Update()
    {
        // 毎フレーム、地面に接しているかどうかを更新
        UpdateGroundedStatus();

        // 入力の立ち上がり(ボタンを押した瞬間)を検出
        bool jumpButtonDown = GetJumpButtonDown();

        if (jumpButtonDown)
        {
            TryJump();
        }
    }

    /// <summary>
    /// 地面に接しているかどうかを Physics.CheckSphere で判定。
    /// </summary>
    private void UpdateGroundedStatus()
    {
        // 指定した位置と半径で、groundLayer に属するコライダーがあるかチェック
        isGrounded = Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundLayer, QueryTriggerInteraction.Ignore);

        if (isGrounded)
        {
            // 地面に着地した瞬間に、2段ジャンプ権をリセット
            canDoubleJump = true;
        }
    }

    /// <summary>
    /// ジャンプボタンの「押した瞬間」を取得する簡易実装。
    /// 旧InputManagerの "Jump" を利用。
    /// </summary>
    /// <returns>このフレームでジャンプボタンが押されたか</returns>
    private bool GetJumpButtonDown()
    {
        // 現在のボタン状態を取得
        bool isHeld = Input.GetButton("Jump"); // 旧InputManagerの "Jump"(デフォルトでSpaceキー)

        // 前フレームは離されていて、今フレーム押されている => 立ち上がり
        bool isDown = !jumpButtonHeldLastFrame && isHeld;

        // 状態を保存
        jumpButtonHeldLastFrame = isHeld;

        return isDown;
    }

    /// <summary>
    /// 実際にジャンプを試みる処理。
    /// - 地上なら通常ジャンプ
    /// - 空中かつ canDoubleJump が true なら2段ジャンプ
    /// </summary>
    private void TryJump()
    {
        if (isGrounded)
        {
            // 地上ジャンプ
            PerformJump(jumpForce);
        }
        else if (canDoubleJump)
        {
            // 空中で1回だけ許可される2段ジャンプ
            float doubleJumpForce = jumpForce * extraJumpForceScale;
            PerformJump(doubleJumpForce);

            // 2段ジャンプ権を消費
            canDoubleJump = false;
        }
        else
        {
            // 地上でもなく、2段ジャンプ権もない => 何もしない
        }
    }

    /// <summary>
    /// 実際にRigidbodyに上方向の速度を与える共通処理。
    /// </summary>
    /// <param name="force">上方向に与える力(速度)</param>
    private void PerformJump(float force)
    {
        // 現在の速度を取得
        Vector3 velocity = rb.velocity;

        // 上方向の速度だけを上書きすることで、落下中でもちゃんと跳ねる
        velocity.y = 0f;
        rb.velocity = velocity;

        // 上方向にインパルスを与える
        rb.AddForce(Vector3.up * force, ForceMode.VelocityChange);

        // パーティクル再生(設定されている場合のみ)
        PlayJumpParticle();
    }

    /// <summary>
    /// ジャンプ時のパーティクル再生処理。
    /// null チェック付きなので、未設定でもエラーにはならない。
    /// </summary>
    private void PlayJumpParticle()
    {
        if (jumpParticle == null)
        {
            return;
        }

        // すでに再生中でも、位置を足元に合わせてから再生し直す
        jumpParticle.transform.position = groundCheck.position;

        // Stop -> Play で確実に再生をリスタート
        jumpParticle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
        jumpParticle.Play();
    }

    /// <summary>
    /// シーンビューで地面判定の範囲を可視化する。
    /// レベルデザイン時にかなり便利。
    /// </summary>
    private void OnDrawGizmosSelected()
    {
        if (!drawGizmos || groundCheck == null)
        {
            return;
        }

        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
    }
}

使い方の手順

ここでは「プレイヤーキャラクターに2段ジャンプを付ける」ケースを例にします。敵キャラや動く足場などにも同じ手順で使い回せます。

  1. プレイヤーに Rigidbody と Collider を用意する
    • 空の GameObject を作成し、名前を Player にします。
    • Rigidbody コンポーネントを追加します。
      • Use Gravity: チェックあり
      • Constraints: 回転を固定したい場合は Freeze Rotation X/Z などをオン
    • CapsuleColliderBoxCollider を追加して当たり判定を付けます。
  2. DoubleJump コンポーネントを追加する
    • Player オブジェクトに、上記の DoubleJump スクリプトをアタッチします。
    • インスペクターで以下を設定します。
      • Jump Force: 7〜10 くらいから調整
      • Extra Jump Force Scale: 1.0 なら1段目と同じ高さ、1.2 などにすると2段目が少し高くなります
  3. 地面判定用のオブジェクトとレイヤーを設定する
    • Player の子として空の GameObject を作成し、名前を GroundCheck にします。
    • GroundCheck を足元(キャラクターの底辺あたり)に移動します。
    • DoubleJumpGround Check に、この GroundCheck をドラッグ&ドロップします。
    • 地面となるオブジェクト(床や地形)に、共通のレイヤー(例: Ground)を設定します。
    • DoubleJumpGround Layer に、その Ground レイヤーを指定します。
  4. ジャンプ用パーティクルを設定する
    • ヒエラルキーで右クリック → Effects > Particle System を作成し、名前を JumpParticle にします。
    • 見た目を調整します(例: 小さな砂煙・キラキラなど)。
    • JumpParticlePlay On Awake のチェックを外しておきます(スクリプトから再生するため)。
    • DoubleJumpJump Particle に、この JumpParticle をドラッグ&ドロップします。
    • ゲームを再生し、Space キー(InputManager の “Jump”)を押してジャンプすると、1段目・2段目の両方でパーティクルが再生されます。

このコンポーネントは「ジャンプの制御とエフェクト再生」に責務を限定しているので、移動処理(左右移動、カメラ追従など)は別スクリプトとして分けておくと、さらに保守しやすくなります。

メリットと応用

DoubleJump をコンポーネントとして切り出しておくと、次のようなメリットがあります。

  • プレハブの再利用性が高い
    プレイヤー用のプレハブにこのコンポーネントを付けておけば、敵キャラや協力プレイ用の2Pキャラにも、そのままコピーして2段ジャンプを有効化できます。必要なら敵側では extraJumpForceScale を変えて挙動を差別化するだけです。
  • レベルデザインが楽になる
    「このステージでは2段ジャンプを禁止したい」といったギミックも、ステージ開始時に enabled を切り替えるだけで実現できます。スクリプト内部を触らずに、インスペクターやトリガーから制御できるのが嬉しいですね。
  • Godクラス化を防げる
    移動・攻撃・ジャンプ・エフェクトを全部1つの Player スクリプトに書くのではなく、「DoubleJump はジャンプだけを見る」ように分割しておけば、デザイナーや他のプログラマが安心して手を入れられます。

さらに、改造案として「空中で2段ジャンプした瞬間に、少しだけ横方向の速度もブーストする」ような演出も簡単に追加できます。例えば、以下のようなメソッドを追加して PerformJump から呼び出すのもアリです。


/// <summary>
/// 2段ジャンプ時に、移動方向へ少しだけスピードブーストを与える例。
/// (Input.GetAxisRaw を使った簡易実装)
/// </summary>
private void ApplyDoubleJumpHorizontalBoost(float boostSpeed)
{
    // 入力方向を取得(左右キーやA/Dキー)
    float horizontal = Input.GetAxisRaw("Horizontal");

    // 入力がない場合は何もしない
    if (Mathf.Approximately(horizontal, 0f))
    {
        return;
    }

    Vector3 velocity = rb.velocity;
    velocity.x = horizontal * boostSpeed;
    rb.velocity = velocity;
}

このように、小さなコンポーネントとして作っておけば、「2段ジャンプ時だけ横ブースト」「3段ジャンプに拡張」「ジャンプ回数をUIに表示」など、責務を保ちながら機能追加していきやすくなります。ぜひ自分のプロジェクト用にアレンジしてみてください。