スポンサーリンク
Unity C#・スクリプト実装

【Unity】ジャンプ実装完全ガイド|通常ジャンプ・二段ジャンプ・壁ジャンプまで解説

Unity C#・スクリプト実装

Unityでアクションゲームを作っていて、「ジャンプってどうやって実装すればいいんだろう」と手が止まった経験はありませんか。キー入力を受け取って上に飛ばすだけ、と思いきや、実際に手を動かしてみると意外とつまずきやすいポイントがたくさんあります。

この記事では、Rigidbodyを使った基本のジャンプ実装から、無限に跳べてしまう問題の防ぎ方、そして二段ジャンプや壁ジャンプへの発展方法まで、順を追って解説していきます。


Unityでジャンプを実装する基本手順

結論からいうと、ジャンプの基本はRigidbody.AddForceにForceMode.Impulseを組み合わせる方法で実装します。ここではその考え方と、実際に手を動かして「まず跳ぶ」状態を作るまでの手順を見ていきましょう。

AddForceとForceModeの選び方

ジャンプの処理では、キーを押した瞬間に上方向へ力を加える必要があります。このとき使うのがRigidbody.AddForceというメソッドですが、力の加え方を決めるForceModeの指定を間違えると、思ったような跳び方になりません。

ForceMode.Forceは、押している間ずっと力を加え続けるモードです。一方のForceMode.Impulseは、瞬間的に一気に力を加えるモードになります。車を押すときにゆっくり押し続けるか、勢いよく一度だけ突き飛ばすかの違いをイメージすると分かりやすいかもしれません。

ジャンプは「押した瞬間にポンと跳ねる」動きなので、基本的にはForceMode.Impulseを使います。もしForceMode.Forceを使ってしまうと、力がじわじわ加わるため、ジャンプが重たく感じられる原因になります。

実装手順とスクリプト例

それでは、実際にプレイヤーが跳べる状態を作っていきます。まずは土台となるオブジェクトとコンポーネントを準備しましょう。

  1. Hierarchyウィンドウで右クリックし、「3D Object」→「Capsule」を選択してプレイヤー用のオブジェクトを作成し、名前をPlayerに変更する
  2. 同じくHierarchyウィンドウで「3D Object」→「Plane」を選択し、床となるオブジェクトを作成してGroundに名前を変更する
  3. Playerを選択し、Inspectorの「Add Component」から「Rigidbody」を追加する
  4. RigidbodyのUse Gravityにチェックが入っていることを確認する
  5. PlayerにCapsule Collider(または Box Collider)が、GroundにはMesh ColliderまたはBox Colliderがアタッチされていることを確認する

ColliderはPlayerとGroundの両方に必要です。どちらかが欠けていると、物理的な衝突が発生せずキャラクターが床をすり抜けて落ちていってしまいます。

次に、ジャンプの動きを実際に書いていきます。Projectウィンドウで右クリックして「Create」→「C# Script」を選び、スクリプト名をPlayerJumpにしましょう。

using UnityEngine;

public class PlayerJump : MonoBehaviour
{
    public float jumpPower = 5f;
    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        // スペースキーが押された瞬間に上方向へ力を加える
        if (Input.GetKeyDown(KeyCode.Space))
        {
            rb.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
        }
    }
}

書き終えたら、このスクリプトをHierarchy上のPlayerオブジェクトへドラッグ&ドロップしてアタッチします。ゲームを再生し、スペースキーを押してキャラクターが上に跳ねれば成功です。

ここまでの手順通りに設定しても跳ばない場合は、Rigidbodyやコンポーネントの設定漏れが原因になっていることが多いです。そんなときは、こちらのチェックリストで原因を切り分けてみてください。

先に、Rigidbodyまわりのつまずきをまとめた記事も参考にしてみてください。




何度も跳べてしまう問題の直し方

基本のジャンプを実装できたら、次にぶつかりやすいのが「スペースキーを押すたびに、空中でも何度も跳べてしまう」という現象です。これは無限ジャンプと呼ばれる状態で、ゲームとして成立させるには必ず直しておきたいポイントになります。

原因はシンプルで、キャラクターが地面にいるかどうかをスクリプト側で判断していないことにあります。地面にいるときだけジャンプを許可する仕組みを、これから作っていきましょう。

まず、キャラクターが地面に触れているかどうかをisGroundedというbool型の変数で管理します。地面に立っているときはtrue、空中にいるときはfalseにしておくイメージです。

このフラグをジャンプの入力条件に加えることで、地面にいないときはジャンプできないように制限できます。

private bool isGrounded;

private void OnCollisionEnter(Collision collision)
{
    // 地面オブジェクトに「Ground」タグを設定しておく
    if (collision.gameObject.CompareTag("Ground"))
    {
        isGrounded = true;
    }
}

private void OnCollisionExit(Collision collision)
{
    if (collision.gameObject.CompareTag("Ground"))
    {
        isGrounded = false;
    }
}

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
    {
        rb.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
    }
}

ここでのポイントは、地面のオブジェクトにあらかじめGroundというタグを設定しておくことです。タグが設定されていないと、CompareTagでの判定が正しく動かず、isGroundedがずっとfalseのままになってしまいます。

段差に乗り上げたときや、床の端に接触したときに、OnCollisionExitが意図せず呼ばれてisGroundedがfalseに戻ってしまうことがあります。地面に立っているはずなのにジャンプできない場合は、この判定の不安定さが原因になっていないか確認してみてください。

この方法はコライダー同士の衝突イベントを使って接地を判定していますが、実はUnityには接地判定のやり方がほかにも複数あります。どの方法を選べばいいか迷ったときは、こちらもあわせて参考にしてみてください。




接地判定はどの方法を選ぶべきか

先ほどはコライダーの衝突イベントを使った接地判定を紹介しましたが、実はUnityで地面との接触を調べる方法はほかにも存在します。それぞれ得意なことが違うので、自分のゲームに合った方法を選べるようにしておきましょう。

3方式の比較表と選定基準

接地判定には、主に3つのアプローチがあります。実装のしやすさと、地面の端に立ったときの安定性のバランスがそれぞれ異なります。

判定方法実装難易度特徴とおすすめ用途
Collision判定低い記述がシンプルで理解しやすい。学習用や小規模なゲームに向いている
Raycast判定中くらい判定の距離や方向を細かく調整できる。アクションゲーム全般で標準的に使われる
CheckSphere判定中くらい地面の端でも安定して検知できる。操作感を重視したい本格的なゲーム向け

Raycast判定は、キャラクターの足元から下方向に見えない光線を飛ばして、地面のコライダーに当たっているかどうかで接地を判断する方法です。判定に使う距離やレイヤーを細かく指定できるので、応用の幅が広いのが魅力になります。

// キャラクターの中心から下方向へ1.1メートルの距離に光線を飛ばす
isGrounded = Physics.Raycast(transform.position, Vector3.down, 1.1f);

// デバッグ用にSceneビュー上に赤い線を描画する
Debug.DrawRay(transform.position, Vector3.down * 1.1f, Color.red);

ただし、線1本だけで判定しているぶん、キャラクターが地面の端ギリギリに立つと光線が外れてしまい、「空中にいる」と誤判定されることがあります。この弱点を補うのがCheckSphere判定です。

// groundCheckは足元に配置した空のGameObjectのTransform
// groundLayerは地面専用のLayerMask
isGrounded = Physics.CheckSphere(groundCheck.position, 0.2f, groundLayer);

CheckSphere判定は、足元を球状の範囲でカバーするイメージの方法です。点や線ではなく面で調べるため、足場の端に立ったときでも接地が外れにくく、プレイヤーにとってストレスの少ない操作感につながります。

ワンポイント

どれを選べばいいか迷ったら、まずはCollision判定で接地判定の基本的な考え方を理解し、慣れてきたらRaycast判定にステップアップしていくのがおすすめです。ゲームの操作感にこだわりたくなったタイミングで、CheckSphere判定への切り替えを検討するとスムーズです。

Raycast判定は接地判定だけでなく、壁の検出や射撃の当たり判定など応用範囲が広い機能です。当たらない・反応しないといったトラブルも起きやすいので、詳しい実装や原因の切り分けはこちらの記事を参考にしてみてください。




二段ジャンプの作り方

接地判定ができるようになったら、次は空中でもう一度ジャンプできる「二段ジャンプ」に挑戦してみましょう。二段ジャンプと聞くと通常ジャンプとは別の新しい処理が必要に感じるかもしれませんが、実際はそうではありません。

「空中であと何回ジャンプできる権利が残っているか」を、フラグで管理する仕組みを通常ジャンプの処理に追加するだけで実現できます。

doubleJumpフラグによる制御フロー

ここでは、doubleJumpというbool型のフラグを用意して、空中ジャンプの権利を管理していきます。流れとしては、次の3つの状態を意識すると理解しやすくなります。

  • 地面にいるとき:通常ジャンプが可能。ジャンプすると空中に移り、二段ジャンプの権利が有効になる
  • 空中にいるとき(一度目のジャンプ後):スペースキーを押すと二段ジャンプが実行され、同時に権利を消費してフラグを無効にする
  • 着地したとき:isGroundedが地面への接触を検知すると、フラグが初期状態にリセットされる

コードにすると、通常ジャンプと二段ジャンプの切り替えは次のような構成になります。

if (Input.GetKeyDown(KeyCode.Space))
{
    if (isGrounded)
    {
        // 地面にいる場合は通常ジャンプを実行し、空中ジャンプの権利を有効化
        rb.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
        doubleJump = true;
    }
    else if (doubleJump)
    {
        // 空中にいて、かつ権利が残っている場合は二段ジャンプを実行し、権利を消費
        rb.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
        doubleJump = false;
    }
}

// 着地時のリセット処理
if (isGrounded)
{
    doubleJump = false;
}

通常ジャンプの分岐に「空中かつ権利あり」という条件を1つ足しているだけ、という点に気づくと、意外とシンプルな仕組みだと感じられるはずです。

二段ジャンプでよくある失敗と対策

二段ジャンプを実装したときに起こりやすい失敗は、だいたい3パターンに絞られます。自分の状況と照らし合わせながらチェックしてみてください。

  • 無限にジャンプできてしまう:空中ジャンプを実行したあとにdoubleJumpfalseへ戻し忘れているケースです。二段目のAddForceを呼び出した直後に、必ずフラグを消費する処理を入れましょう
  • 着地したのに二段ジャンプが使えなくなる:isGroundedがうまく地面への着地を検知できておらず、内部的にはずっと空中扱いになっているケースです。接地判定の距離や範囲、タグ・レイヤーの設定を見直してみてください
  • 二段目の高さがバラバラ、または高すぎる:上昇中や落下中にAddForceを加えると、そのときの移動速度(慣性)が力に上乗せされてしまうケースです。二段ジャンプを実行する直前に、一度Rigidbodyの速度をリセットしてから力を加えると高さが安定します

特に3つ目のパターンは見落としやすいポイントです。ジャンプボタンを押すタイミングによって跳ぶ高さが変わる場合は、この慣性の影響を疑ってみるとよいでしょう。




壁ジャンプの作り方

壁を蹴って反対方向へ飛び上がる「壁ジャンプ」は、難易度が高そうに見えるかもしれません。ですが実は、ここまで作ってきた接地判定のしくみを応用するだけで実現できます。

「地面にいるかどうか」の判定を「壁に触れているかどうか」の判定に置き換え、力を加える方向を斜め上に変える、これが壁ジャンプの基本的な考え方です。

壁検知とジャンプ方向の計算

壁ジャンプを作るときは、次の3つの要素を順番に用意していきます。

  • キャラクターの右側・左側にRaycastを飛ばして、壁オブジェクトが近くにあるかを調べる
  • 地面やキャラクター自身を壁と誤認しないよう、壁専用のレイヤー(例:Wall)を用意してLayerMaskで絞り込む
  • 右に壁があるときは左上方向へ、左に壁があるときは右上方向へ力を加える

この3つを組み合わせると、左右の壁検知とジャンプ方向の計算は次のようなコードになります。

// 右側と左側にそれぞれRayを飛ばして壁レイヤー(wallLayer)の接触を調べる
bool wallRight = Physics.Raycast(transform.position, transform.right, 0.6f, wallLayer);
bool wallLeft = Physics.Raycast(transform.position, -transform.right, 0.6f, wallLayer);

if (Input.GetKeyDown(KeyCode.Space) && !isGrounded)
{
    if (wallRight)
    {
        // 右に壁がある場合、左上方向(Vector3.up + Vector3.left)に力を加える
        Vector3 jumpDirection = (Vector3.up + Vector3.left).normalized;
        rb.AddForce(jumpDirection * jumpPower, ForceMode.Impulse);
    }
    else if (wallLeft)
    {
        // 左に壁がある場合、右上方向(Vector3.up + Vector3.right)に力を加える
        Vector3 jumpDirection = (Vector3.up + Vector3.right).normalized;
        rb.AddForce(jumpDirection * jumpPower, ForceMode.Impulse);
    }
}

接地していない(!isGrounded)ことを条件に加えているのは、地面にいるときに壁ジャンプが誤発動しないようにするためです。通常ジャンプと処理がぶつからないよう、条件を分けて整理しておきましょう。

壁ジャンプでよくある失敗と対策

壁ジャンプは接地判定よりも条件が増えるぶん、うまく動かないときの原因も少し複雑になりがちです。よくある3パターンを確認してみましょう。

  • 壁に接触しているのにジャンプが発動しない:壁オブジェクトに適切なレイヤーが設定されておらず、スクリプト側のwallLayerと一致していないケースです。Inspector上で壁のLayerを確認し、設定を合わせましょう
  • 壁に密着しているはずなのに検知できない:Raycastの照射距離が短すぎて、キャラクターのコライダーの外まで光線が届いていないケースです。キャラクターの幅に合わせてRayの長さを調整してみてください
  • 壁を蹴っても真上にしか飛ばない:横方向へ押し出す力の比率が弱い、またはRigidbodyの横方向の抵抗が強すぎるケースです。ジャンプ方向のベクトルで横方向の割合を増やすと改善しやすくなります

2つ目のパターンで迷ったときは、Debug.DrawRayを使ってSceneビュー上に光線を表示させると、どこまで届いているかが目で見て確認できるのでおすすめです。




ジャンプの高さを調整する方法

「ジャンプの高さを変えたいときは、jumpPowerの数値を大きくすればいい」と思われがちですが、実はそれだけでは思い通りの高さにならないことがあります。

到達する高さは、jumpPowerだけでなく複数のパラメータが組み合わさって決まるためです。どこを調整すればよいのか、順番に見ていきましょう。

高さに影響する4つのパラメータ

ジャンプの高さに関わる要素は、主に次の4つです。

パラメータ影響の仕方
jumpPower値を大きくすると高く、小さくすると低く跳ぶ。跳ぶ力の大元になる数値
Mass(質量)値が大きいほど、同じjumpPowerでも跳びにくくなる
Drag(空気抵抗)値が大きいほど上昇時の減速が早くなり、跳ぶ勢いが弱まる
Gravity(重力)プロジェクトの重力設定。値が大きいほど、上がってからすぐに落ちてくる

たとえば「jumpPowerを上げたのにあまり高く跳ばない」と感じたときは、Massが大きすぎたり、Dragが強く設定されていたりすることが原因かもしれません。数値をいきなり大きく変えるのではなく、1つずつ調整して変化を確かめてみると、原因が特定しやすくなります。

ワンポイント

フワッと浮くようなジャンプにしたいのか、キレのある素早いジャンプにしたいのかによって、調整すべきパラメータの優先順位は変わってきます。狙いたい操作感をイメージしながら数値をいじってみると、意図した動きに近づけやすくなります。

なお、Unity 6以降ではRigidbody.velocityが非推奨となり、代わりにRigidbody.linearVelocityが使われるようになっています。

速度を直接書き換える処理を組む場合は、この名称の変更を頭に入れておくとスムーズです。




CharacterControllerを使う場合の注意点

ここまではRigidbodyを使ったジャンプの作り方を紹介してきましたが、Unityにはもう1つ、キャラクター制御によく使われるCharacterControllerというコンポーネントがあります。

「CharacterControllerでもAddForceを使えるのでは」と思われることがあるのですが、結論からいうと、CharacterControllerにAddForceは使えません

なぜAddForceが使えないのか

CharacterControllerは、物理演算にもとづいた制御を行わない、独自の移動管理クラスです。これまで紹介してきたAddForceは、あくまでRigidbodyが物理演算の力を計算するためのメソッドなので、内部にRigidbodyを持たないCharacterControllerには適用できません。

CharacterControllerでジャンプを作りたい場合は、代わりに次のような考え方で実装します。

  • 重力による落下速度と、ジャンプによる上昇速度を、スクリプト内で毎フレーム自分で計算する
  • 計算した速度をベクトルとしてまとめ、CharacterController.Moveメソッドに渡してキャラクターを直接動かす

Rigidbodyのように「力を加えたら物理演算が勝手に動きを計算してくれる」わけではなく、動きそのものをスクリプト側で組み立てる点が、Rigidbodyとの大きな違いになります。

CharacterControllerを使ったキャラクター操作や、ジャンプを含めた発射機能の具体的な実装は、こちらの記事で詳しく解説しています。




よくある質問

Q
2Dゲームの場合もこの記事の実装(Rigidbody)で同じように作れますか?
A
考え方の大枠は同じですが、2Dゲームの場合はRigidbodyではなくRigidbody2Dを使う点が異なります。ForceModeについても、3D用のForceModeではなく、2D専用のForceMode2Dを指定する必要があります。使うコンポーネントの名前が変わるだけで、AddForceで力を加える・isGroundedで接地を管理するといった基本の流れ自体は共通していますので、置き換えながら試してみてください。
Q
ジャンプ中に横移動ができなくなるのはなぜですか?
A
AddForceで縦方向の力だけを加えているつもりでも、横移動用のスクリプトと処理がぶつかっていたり、Rigidbodyの空気抵抗(Drag)が強すぎて横方向の動きが打ち消されていたりすることがあります。ジャンプ用のスクリプトと移動用のスクリプトで、同じ軸の速度を上書きし合っていないか見直してみると、原因が見つかりやすくなります。
Q
二段ジャンプをさらに増やして三段・四段ジャンプにできますか?
A
可能です。doubleJumpのようなbool型のフラグではなく、残りジャンプ回数をint型の変数で管理する形に変えると対応しやすくなります。ジャンプするたびに回数を1減らし、着地したタイミングで最大回数にリセットする、という考え方は二段ジャンプの仕組みをそのまま応用できます。

※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク