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

Unity 3D高速移动时射线检测未及时触发致X速度丢失求助

问题描述

我开发了一款简单的平台跳跃游戏,用Raycast射线检测判断玩家是否触地。为了让玩家在碰撞地面前就能跳跃,避免地面摩擦丢失X速度,我放弃了碰撞器改用射线检测。但实际运行时,玩家下落速度过快的话,X速度会直接归零,没法实现提前跳跃并保留X速度的效果。我怀疑射线检测没及时触发,试了以下方法都没用:

  • 将玩家Rigidbody的碰撞检测模式改为连续检测(Continous)而非离散检测(Discrete)
  • 将射线检测代码移至FixedUpdate(效果更差)
  • 在项目脚本执行顺序中把移动脚本放在默认时间之后

相关代码如下:

Jump()方法

public void Jump()
{
    LayerMask ground = LayerMask.GetMask("Ground");

    Vector2 bottomCenter = new Vector2(transform.position.x, transform.position.y - transform.GetComponent<Renderer>().bounds.size.y / 2);
    Vector2 bottomLeftCornerPos = new Vector2(transform.position.x - transform.GetComponent<Renderer>().bounds.size.x / 2, transform.position.y - transform.GetComponent<Renderer>().bounds.size.y / 2);
    Vector2 bottomRightCornerPos = new Vector2(transform.position.x + transform.GetComponent<Renderer>().bounds.size.x / 2, transform.position.y - transform.GetComponent<Renderer>().bounds.size.y / 2);

    // Cast a ray straight down.
    RaycastHit2D down = Physics2D.Raycast(bottomCenter, Vector2.down, 1f, ground);
    RaycastHit2D downleft = Physics2D.Raycast(bottomLeftCornerPos, Vector2.down, 1f, ground);
    RaycastHit2D downright = Physics2D.Raycast(bottomRightCornerPos, Vector2.down, 1f, ground);

    // Determine the closest hit point
    RaycastHit2D hit = down.collider != null ? down : (downleft.collider != null ? downleft : downright);
    if (down.collider != null && (hit.collider == null || down.distance < hit.distance)) hit = down;
    if (downleft.collider != null && (hit.collider == null || downleft.distance < hit.distance)) hit = downleft;
    if (downright.collider != null && (hit.collider == null || downright.distance < hit.distance)) hit = downright;

    // If it hits something...
    if (hit.collider != null)
    {

        if (hit.distance >= 0.01 && hit.distance <= 0.1 && rb2d.velocityY <= 0)
        {

            rb2d.velocityY = jumpPower;
            comboExperiration = ComboExpireTime;
            comboCounter++;


            //Since we are not colliding with floor, send a message to Player Stats that we jumped on a floor
            if (hit.collider.gameObject.CompareTag("Floor"))
            {
                GetComponent<PlayerStats>().CollisionByJump(hit.collider.gameObject);
            }

        }
    }
}

InputManager调用代码

void Update()
{
    if (playerInput.MovementMap.Jump.inProgress)
    {
        player.GetComponent<PlayerMovement>().Jump();
    }
}

问题分析与解决

核心问题

  1. 射线检测时机与范围不足:仅在跳跃输入触发时才检测地面,若玩家下落速度过快,一帧内移动距离超过射线固定长度1f,会直接错过检测范围,导致无法触发跳跃,后续若碰撞到地面(哪怕你禁用了碰撞器,可能仍有残留逻辑)就会丢失X速度。
  2. 跳跃输入逻辑不合理:持续检测jump.inProgress会反复调用Jump(),可能导致多次触发或状态冲突。
  3. 触地状态未持续更新:没有提前缓存接近地面的状态,完全依赖输入帧的瞬时检测,容错率极低。

修复方案

1. 持续更新接近地面的状态

在FixedUpdate中持续检测并缓存isNearGround状态,避免输入帧错过检测时机。

2. 动态调整射线长度

结合玩家当前下落速度计算射线长度,确保覆盖一帧内的移动距离,防止漏检。

3. 优化跳跃输入触发

改为检测跳跃输入的按下瞬间,避免持续调用跳跃逻辑。

4. 确保X速度保留

跳跃时直接复用当前X速度,不单独设置Y速度,防止意外覆盖。

修改后的代码示例

PlayerMovement脚本调整

private bool isNearGround;
private LayerMask groundLayer;
private Renderer playerRenderer;
private Rigidbody2D rb2d;

void Awake()
{
    rb2d = GetComponent<Rigidbody2D>();
    playerRenderer = GetComponent<Renderer>();
    groundLayer = LayerMask.GetMask("Ground");
}

void FixedUpdate()
{
    UpdateNearGroundStatus();
}

// 持续更新接近地面的状态
private void UpdateNearGroundStatus()
{
    Bounds bounds = playerRenderer.bounds;
    Vector2 bottomCenter = new Vector2(bounds.center.x, bounds.min.y);
    Vector2 bottomLeft = new Vector2(bounds.min.x, bounds.min.y);
    Vector2 bottomRight = new Vector2(bounds.max.x, bounds.min.y);

    // 动态计算射线长度:基础检测距离 + 当前帧下落距离
    float rayLength = 0.1f + Mathf.Abs(rb2d.velocity.y) * Time.fixedDeltaTime;

    bool hitCenter = Physics2D.Raycast(bottomCenter, Vector2.down, rayLength, groundLayer);
    bool hitLeft = Physics2D.Raycast(bottomLeft, Vector2.down, rayLength, groundLayer);
    bool hitRight = Physics2D.Raycast(bottomRight, Vector2.down, rayLength, groundLayer);

    isNearGround = hitCenter || hitLeft || hitRight;
}

public void Jump()
{
    if (isNearGround && rb2d.velocity.y <= 0)
    {
        // 保留X速度,设置跳跃Y速度
        rb2d.velocity = new Vector2(rb2d.velocity.x, jumpPower);
        comboExperiration = ComboExpireTime;
        comboCounter++;

        RaycastHit2D closestHit = GetClosestGroundHit();
        if (closestHit.collider?.CompareTag("Floor") == true)
        {
            GetComponent<PlayerStats>().CollisionByJump(closestHit.collider.gameObject);
        }
    }
}

// 获取最近的地面碰撞,用于触发Floor逻辑
private RaycastHit2D GetClosestGroundHit()
{
    Bounds bounds = playerRenderer.bounds;
    Vector2 bottomCenter = new Vector2(bounds.center.x, bounds.min.y);
    Vector2 bottomLeft = new Vector2(bounds.min.x, bounds.min.y);
    Vector2 bottomRight = new Vector2(bounds.max.x, bounds.min.y);
    float rayLength = 0.1f + Mathf.Abs(rb2d.velocity.y) * Time.fixedDeltaTime;

    RaycastHit2D hitCenter = Physics2D.Raycast(bottomCenter, Vector2.down, rayLength, groundLayer);
    RaycastHit2D hitLeft = Physics2D.Raycast(bottomLeft, Vector2.down, rayLength, groundLayer);
    RaycastHit2D hitRight = Physics2D.Raycast(bottomRight, Vector2.down, rayLength, groundLayer);

    RaycastHit2D closestHit = new RaycastHit2D();
    float minDistance = float.MaxValue;

    if (hitCenter && hitCenter.distance < minDistance)
    {
        closestHit = hitCenter;
        minDistance = hitCenter.distance;
    }
    if (hitLeft && hitLeft.distance < minDistance)
    {
        closestHit = hitLeft;
        minDistance = hitLeft.distance;
    }
    if (hitRight && hitRight.distance < minDistance)
    {
        closestHit = hitRight;
    }

    return closestHit;
}

// 可选:绘制射线Gizmos,方便调试
void OnDrawGizmos()
{
    if (playerRenderer == null) return;
    Bounds bounds = playerRenderer.bounds;
    Vector2 bottomCenter = new Vector2(bounds.center.x, bounds.min.y);
    Vector2 bottomLeft = new Vector2(bounds.min.x, bounds.min.y);
    Vector2 bottomRight = new Vector2(bounds.max.x, bounds.min.y);
    float rayLength = 0.1f + (rb2d ? Mathf.Abs(rb2d.velocity.y) * Time.fixedDeltaTime : 0);

    Gizmos.color = Color.red;
    Gizmos.DrawLine(bottomCenter, bottomCenter + Vector2.down * rayLength);
    Gizmos.DrawLine(bottomLeft, bottomLeft + Vector2.down * rayLength);
    Gizmos.DrawLine(bottomRight, bottomRight + Vector2.down * rayLength);
}

InputManager脚本调整

void Update()
{
    // 检测跳跃输入的按下瞬间,避免持续调用Jump()
    if (playerInput.MovementMap.Jump.WasPressedThisFrame())
    {
        player.GetComponent<PlayerMovement>().Jump();
    }
}

额外注意事项

  • 彻底禁用玩家的Collider组件,或设置为Trigger,同时确保地面摩擦参数为0,避免碰撞逻辑干扰X速度。
  • 调试时开启Gizmos,直观查看射线检测范围是否覆盖玩家下落路径。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 10:45:07