You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity开发求助:玩家贴墙时跳跃过高的Bug修复方案

玩家贴墙时跳跃过高的Bug修复方案

我正在开发一款类《我的世界》《雷神之锤》的游戏,目前遇到跳跃机制问题:当玩家贴墙时会出现跳跃过高的异常情况。我使用Rigidbody处理物理系统,通过修改velocity实现玩家移动,玩家Collider上的Physic Material未设置摩擦和弹跳属性。以下是我的代码,寻求该Bug的修复方案或替代解决思路:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum PlayerMovementState {
    Sneak,
    Walk,
    Run
}

public class PlayerControls : MonoBehaviour
{
    Rigidbody rb;

    Vector3 velocity, desiredVelocity;
    PlayerMovementState moveState;

    float groundDistance;
    [SerializeField]
    bool forwardAir, backAir, rightAir, leftAir;
    [SerializeField]
    LayerMask groundLayer;
    [SerializeField]
    bool onGround;

    bool desiredJump;
    float jumpHeight = 1.0f;

    private void Awake() {
        rb = GetComponent<Rigidbody>();
        moveState = PlayerMovementState.Walk;
        groundDistance = GetComponentInChildren<Collider>().bounds.extents.y;
    }

    private void Update() {
        Vector2 playerInputs = Vector2.ClampMagnitude(
            new Vector2(
                Input.GetAxis("Horizontal"),
                Input.GetAxis("Vertical")
            ), 1.0f
        );

        if (Input.GetKey(KeyCode.LeftShift)) moveState = PlayerMovementState.Sneak; 
        else if (Input.GetKey(KeyCode.LeftControl)) moveState = PlayerMovementState.Run;
        else moveState = PlayerMovementState.Walk;

        float speed = moveState == PlayerMovementState.Run ? 10f : (
            moveState == PlayerMovementState.Sneak ? 2f : 5f
        );

        RaycastGround();
        onGround = !forwardAir && !backAir && !rightAir && !leftAir;

        if (Input.GetButtonDown("Jump")) desiredJump = true;

        if (moveState == PlayerMovementState.Sneak)
        {
            if (forwardAir && playerInputs.y > 0) playerInputs.y = 0f;
            if (backAir && playerInputs.y < 0) playerInputs.y = 0f;
            if (rightAir && playerInputs.x > 0) playerInputs.x = 0f;
            if (leftAir && playerInputs.x < 0) playerInputs.x = 0f;
        }

        desiredVelocity = 
            (transform.forward * playerInputs.y + transform.right * playerInputs.x) * speed;
    }

    private void FixedUpdate() {
        velocity = rb.velocity;

        float acceleration = 10;
        velocity.x = Mathf.MoveTowards(velocity.x, desiredVelocity.x, acceleration);
        velocity.z = Mathf.MoveTowards(velocity.z, desiredVelocity.z, acceleration);

        if (desiredJump && onGround)
        {
            desiredJump = false;
            float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);

            velocity.y += jumpSpeed;
        }

        rb.velocity = velocity;

        desiredJump = false;
    }

    void RaycastGround()
    {
        forwardAir = !(Physics.Raycast(
            transform.position + Vector3.forward * 0.1f,
            -Vector3.up,
            groundDistance + 0.1f,
            groundLayer
        ));

        backAir = !(Physics.Raycast(
            transform.position - Vector3.forward * 0.1f,
            -Vector3.up,
            groundDistance + 0.1f,
            groundLayer
        ));

        rightAir = !(Physics.Raycast(
            transform.position + Vector3.right * 0.1f,
            -Vector3.up,
            groundDistance + 0.1f,
            groundLayer
        ));

        leftAir = !(Physics.Raycast(
            transform.position - Vector3.right * 0.1f,
            -Vector3.up,
            groundDistance + 0.1f,
            groundLayer
        ));
    } 
}

问题原因分析

  1. 地面检测逻辑缺陷:当前onGround要求四个方向的射线全部触地才判定为在地面,既不符合实际需求(玩家只要脚部接触地面就应允许跳跃),还可能在贴墙时出现误判;更关键的是,贴墙时的碰撞可能导致Rigidbody的y轴残留微小向上速度。
  2. 跳跃速度叠加错误:代码中使用velocity.y += jumpSpeed而非直接赋值,若此时y轴已有向上速度,叠加后总跳跃速度会远高于预期,导致跳得过高。

修复方案

1. 替换可靠的地面检测

将原有的四条射线检测改为Physics.CheckSphere,检测玩家脚下的球形区域是否接触地面层,大幅提升检测准确性:

void CheckGround()
{
    // 检测玩家脚下略低于碰撞体底部的球形区域
    Vector3 checkPos = transform.position - Vector3.up * (groundDistance - 0.05f);
    float checkRadius = GetComponentInChildren<Collider>().bounds.extents.x * 0.8f;
    onGround = Physics.CheckSphere(checkPos, checkRadius, groundLayer);
}

然后在Update中替换原有的RaycastGround()调用为CheckGround(),同时删除forwardAir、backAir等冗余变量及相关逻辑。

2. 修改跳跃速度赋值方式

将跳跃时的速度叠加改为直接赋值,避免残留速度影响:

if (desiredJump && onGround)
{
    desiredJump = false;
    float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);
    // 直接设置y轴速度为跳跃速度,而非叠加
    velocity.y = jumpSpeed;
}

3. 优化跳跃输入逻辑

移除FixedUpdate末尾的desiredJump = false,避免刚触发的跳跃输入被直接清空:

private void FixedUpdate() {
    velocity = rb.velocity;

    float acceleration = 10;
    velocity.x = Mathf.MoveTowards(velocity.x, desiredVelocity.x, acceleration);
    velocity.z = Mathf.MoveTowards(velocity.z, desiredVelocity.z, acceleration);

    if (desiredJump && onGround)
    {
        desiredJump = false;
        float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);
        velocity.y = jumpSpeed;
    }

    rb.velocity = velocity;

    // 移除这行代码,保留未处理的跳跃输入
    // desiredJump = false;
}

替代思路(可选)

如果仍想保留射线检测,可改为只要有一条射线触地就判定为在地面,修改onGround判断逻辑:

onGround = !forwardAir || !backAir || !rightAir || !leftAir;

但这种方式仍不如球形检测可靠,建议优先使用前者覆盖玩家脚部的整个接触区域,减少误判。

内容的提问来源于stack exchange,提问作者mikeriin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 00:24:31