今回はUnity初心者の方向けの話題で、
をご紹介するという内容です。
Unityでアクションゲームを作っていると、おそらく多くの初心者の方はジャンプの挙動の作り方で行き詰ってしまうと思います。今や多くのゲームにはジャンプが当たり前のように実装されているので、「そんなの簡単に実装できるでしょw」と高をくくっていたら痛い目に遭った…という方も少なくないのではないでしょうか(※少なくとも私はそうでした)。
そしてネットで調べてみると「ジャンプ=ゲームでは当たり前の機能」として認識されているせいか、作り方があまり詳しく載っていないようなので今回その辺を丁寧に解説しておこうと思ったというわけです。
このような次第でここでは「ジャンプの挙動の作り方」をサンプルスクリプト付きで丁寧に説明していきますね。
今回作るジャンプの仕様
はじめに、今回作るジャンプの挙動の仕様について簡単に説明しておこうと思います。GIFをご用意したのでまずはどんな感じにジャンプするのかをご確認ください。
細かい仕様は後述のスクリプトを見て頂くとして、主な仕様は次のようになっています。
- 3Dアクションゲーム用のジャンプである。
- ボタンを長押しすることでジャンプの高さが変わる。
今回は3Dゲーム用ですが、基本的な部分は2Dも同じなので2Dゲームに適用する場合は適宜関数等を変更してください。
あとは「気持ちのいいジャンプってどんな感じかな」と考えてみた結果、単にボタンを押したらジャンプするだけでなく、ボタンを押し続けたときに高さが変わるようにするとゲームの手触りが良くなるかなと思い、そのような仕様にすることにしました。
ボタン長押しで高さが変わるジャンプのC#スクリプト
ではここからが本題で、仕様のとおり「ボタン長押しで高さが変わるジャンプ」を実現するためのC#スクリプトを掲載します。
using UnityEngine; public class JumpSample : MonoBehaviour { [SerializeField] Rigidbody rigidBody; [SerializeField, Min(0)] float jumpPower = 5f; [SerializeField] AnimationCurve jumpCurve = new(); [SerializeField, Min(0)] float maxJumpTime = 1f; [SerializeField] float groundCheckRadius = 0.4f; [SerializeField] float groundCheckOffsetY = 0.45f; [SerializeField] float groundCheckDistance = 0.2f; [SerializeField] LayerMask groundLayers = 0; [SerializeField] string JumpButtonName = "Jump"; bool isGrounded = false; bool jumping = false; float jumpTime = 0; RaycastHit hit; Transform thisTransform; void Start() { thisTransform = transform; } void Update() { isGrounded = CheckGroundStatus(); // ジャンプの開始判定 if (isGrounded && Input.GetButton(JumpButtonName)) { jumping = true; } // ジャンプ中の処理 if (jumping) { if (Input.GetButtonUp(JumpButtonName) || jumpTime >= maxJumpTime) { jumping = false; jumpTime = 0; } else if(Input.GetButton(JumpButtonName)) { jumpTime += Time.deltaTime; } } } private void FixedUpdate() { Jump(); } void Jump() { if (!jumping) { return; } rigidBody.velocity = new Vector3(rigidBody.velocity.x, 0, rigidBody.velocity.z); // ジャンプの速度をアニメーションカーブから取得 float t = jumpTime / maxJumpTime; float power = jumpPower * jumpCurve.Evaluate(t); if (t >= 1) { jumping = false; jumpTime = 0; } rigidBody.AddForce(power * Vector3.up, ForceMode.Impulse); } // 接地判定 bool CheckGroundStatus() { return Physics.SphereCast(thisTransform.position + groundCheckOffsetY * Vector3.up, groundCheckRadius, Vector3.down, out hit, groundCheckDistance, groundLayers, QueryTriggerInteraction.Ignore); } }
C#スクリプトの解説
Update関数内
まずUpdate関数内では接地判定を行ったり、キー入力を取得してジャンプ判定を行ったりします。
- 接地している状態でジャンプボタンを押すとジャンプを開始します。
- ジャンプ中にジャンプボタンを押し続けるとタイマーが加算されます。
- ボタンを離すとジャンプを終了します。
Jump関数
ジャンプ用の関数では次のような処理を行っています。
- Y方向の速度をいったんリセットする。
- ジャンプ速度の乗数をタイマーの値に基づいてアニメーションカーブから取得し、基本速度に乗算する。
- もしタイマーがジャンプ入力の最大時間に達したらジャンプ終了。
- そうでなければ、AddForce関数(モードはImpluse)でジャンプ力を加算する。
まずジャンプ速度をどう計算しようかなと迷ったのですが、うまい計算式が思いつかなかったので離れ業としてアニメーションカーブを使うことにしました。これを使うと、ある時刻におけるジャンプ力を曲線で表現できるので便利です。使いようによっては風変わりなジャンプの挙動も実現できます。
それからAddForce関数のモードは、普通にForceを使うとジャンプとしては不自然な動きになるのでImpluseモードを使うことにしました。ただそのままだと物凄い勢いでジャンプしてしまうので、FixedUpdate関数が実行されるたびに最初にY方向の速度をリセットしています。
CheckGroundStatus関数
接地判定を行います。3Dゲームの場合はSphereCastという球状のレイキャストを使えるので、通常のレイキャストではなくそちらを採用することにしました。これによって足元の形状を立体的に判定できるので判定精度が上がります。
なお接地判定については下記の記事で詳しく解説しているので、そちらも併せてご覧頂けれと思います。
使い方
最後に上記のスクリプトの使い方を簡単に説明しておきます。任意のゲームオブジェクトに
- Rigidbody
- 3Dのコライダー(基本的にはカプセルコライダー)
- 上記のC#スクリプト(JumpSample)
をアタッチして下記のように設定を行ってください。
なお、このゲームオブジェクトのレイヤーは地面以外に設定してください(例えば、新しく「Player」というレイヤーを作ってそれを設定するなど)。
Rigidbodyの設定
- 補間:ON
- Constraints→「回転を固定」のチェックボックスををすべてON
C#スクリプトの設定
- リジッドボディ:先ほど追加したリジッドボディを登録
- Jump Power:適当に設定
- Jump Curve:
好きなように設定。横軸は0~1の範囲にすること。以下は一例です。
- Max Jump Time:ジャンプ入力を最大で何秒間受け付けるか。
- Ground Check Radius:接地判定の球の半径
- Ground Check Offset Y:
接地判定のオフセット。半径よりも大きな値にすること。ちなみにレイキャストは始点を判定してくれないので、始点をずらすことでこの問題に対処している。 - Ground Check Distance:接地判定の距離
- Ground Layers:地面のレイヤー。
- Jump Button Name:ジャンプボタンの名前。
おわりに
以上、気持ちいいジャンプの挙動の作り方について丁寧にご説明しました。
ジャンプはゲームの世界ではすごく基本的な挙動なのですが、きちんと作り込もうとすると意外と難しくて奥が深いメカニクスだと思います。上記の内容はジャンプのごく浅い部分について説明したに過ぎず、まだまだ改善の余地がありますので、ここから先はぜひご自身で色々試して頂ければと思います。
この記事がUnityでのゲーム開発のお役に立てば幸いです。