U
14 / 22 · 9 分
FAQ

ジャンプと、それを成立させる接地判定

短い答え

キャラクターをジャンプさせ、空中での再ジャンプを止めるには?

ジャンプ自体は 1 行。難しいのは「足が何かに付いている瞬間」を知ることです。キャラクターの真下にある小さな球を Ground 層に対して検査し、true を返したときだけジャンプを許可します。垂直速度が 0 かで判定するのは絶対にやめてください——頂点でも 0 になります。

キャラクターをジャンプさせるだけなら一行です。ただ「何かの上に立っているときだけジャンプさせる」のが半日かかる作業で、理由は単純に Unity が足元の接地を教えてくれず —— 自分で行って聞いてくるしかないからです。

アニメーション図

足元の下の球が毎フレーム答えを返し、その隣の二つの時間窓こそが、操作を過酷ではなくフェアに感じさせるものです。

ジャンプそのもの

The one line, for each of the two movement systems
// CharacterController: you own gravity, so you own the jump too.
verticalSpeed = Mathf.Sqrt(jumpHeight * -2f * gravity);

// Rigidbody: hand it an impulse and let physics do the arc.
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);

接地チェック

ありがちな間違いは、縦方向の速度がゼロ近いかどうかで接地を判定することです。速度はジャンプの頂点で必ずゼロに近くなるため、まさにその頂点で二回目のジャンプができてしまいます —— 誰も求めていない定番のふわふわ二段ジャンプです。

  1. 1

    Ground レイヤーを作成します:Layers▸Add Layer で layer を追加し、床とすべてのプラットフォームをそこに割り当てます。

  2. 2

    キャラクターの空の子オブジェクトを一つ作り、GroundCheck と名付けて、足元の真下 —— 足にぴったりではなく数センチ下 —— に置きます。

  3. 3

    毎フレーム、その点を中心とした小さな球と Ground レイヤーの当たりを調べます。何かの上に立っているとは、その球が何かと重なっているということです。

Jump.cs — the whole thing
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class Jump : MonoBehaviour
{
    public Transform groundCheck;
    public LayerMask groundLayer;
    public float checkRadius = 0.25f;
    public float jumpHeight = 2f;
    public float gravity = -20f;

    // A jump the player asked for a moment too early still counts.
    public float bufferTime = 0.12f;
    // So does one asked for a moment after walking off an edge.
    public float coyoteTime = 0.12f;

    CharacterController cc;
    float verticalSpeed, lastGrounded, lastPressed;

    void Awake() => cc = GetComponent<CharacterController>();

    void Update()
    {
        bool grounded = Physics.CheckSphere(
            groundCheck.position, checkRadius, groundLayer, QueryTriggerInteraction.Ignore);

        if (grounded) lastGrounded = Time.time;
        if (Input.GetButtonDown("Jump")) lastPressed = Time.time;

        if (grounded && verticalSpeed < 0f) verticalSpeed = -2f;

        if (Time.time - lastPressed < bufferTime && Time.time - lastGrounded < coyoteTime)
        {
            verticalSpeed = Mathf.Sqrt(jumpHeight * -2f * gravity);
            lastPressed = lastGrounded = -99f;   // spend both, so it fires once
        }

        verticalSpeed += gravity * Time.deltaTime;
        cc.Move(Vector3.up * verticalSpeed * Time.deltaTime);
    }

    // Draw the check in the Scene view, so you can see what it is testing.
    void OnDrawGizmosSelected()
    {
        if (groundCheck == null) return;
        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(groundCheck.position, checkRadius);
    }
}

二つのタイマーは省略可能な飾りではない

  • Coyote time —— 段差の端から歩き出した 2 フレーム後に Jump を押したプレイヤーは、まさにジャンプするつもりだった人です。これがなければ、ゲームに無視されている気がしてなりません。

  • Jump buffer —— 着地の直前に Jump を押したなら、着地と同時にジャンプするつもりです。これがなければその一押しは無駄に捨てられ、プレイヤーは操作系のせいにします。

  • どちらも 0.1 秒前後が目安です。意識されることはなくても、この二つが「ピシッと決まる操作」と「壊れた操作」の違いの大半を埋めています。

これを作るレッスン

移動、ジャンプ、ブロックへの着地

この記事はそれだけで完結するレシピです。コースでは同じものを、5 つのタブを貫くひとつのプロジェクトの一部として作ります — キャラクターと戦闘 のレッスン 02。