U
14 / 22 · 9 分钟
常见问题

跳跃,以及让它正确的落地检测

简短回答

怎么让角色跳跃,并阻止它在半空中再次起跳?

跳跃本身只有一行代码;难的是判断脚什么时候踩到了东西。用角色正下方的一个小球去和 Ground 层做检测,只有检测返回 true 时才允许起跳。绝不要去判断竖直速度是否为零——在抛物线最高点它同样是零。

让角色跳起来只有一行代码。让它只在踩着东西的时候才跳,才是吃掉你一整个下午的部分 —— 原因在于 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,然后把地板和每一个平台都放到该图层上。

  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 —— 玩家在走出平台边缘两帧后才按 Jump,说明他就是想跳。没有它,游戏就像在故意无视你。

  • Jump buffer —— 玩家在落地前一刻按 Jump,是想一落地就跳。没有它,这次按键被白白丢掉,玩家就会骂手感。

  • 两者都在 0.1 秒左右。谁都感觉不到它们,但操作是紧实还是稀烂,差别一大半就在这两者身上。

做出这个的那一课

移动、跳跃并落到方块上

这篇指南单独成篇,是一份可以直接照着做的配方。在课程里,同样的东西会作为贯穿全部五个章的那个项目的一部分来搭建 —— 角色与战斗,第 02 课。