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

Unity 3D刚体非预期轴向吸附与冲量响应异常问题排查

Unity刚体Velocity异常问题排查与解决

Unity版本:2022.3.11f1,3D URP项目

开发简易玩家控制器时遇到两个刚体Velocity异常问题:

问题一:移动旋转时速度吸附至世界X轴

  • 黄线代表Velocity,红线代表W键输入结合鼠标方向的推力
  • 现象:移动中旋转玩家刚体,速度/方向突然吸附到世界X轴
  • 疑问:为何速度会吸附到世界轴向?如何解决?

问题二:跳跃时出现额外Z轴推力与轨迹偏转

  • 黄线代表Velocity,红线代表W键输入结合鼠标方向的推力
  • 现象:按空格键施加Y轴冲量时,刚体受到世界Z轴方向额外推力;轨迹末端出现类似碰撞隐形墙体的偏转
  • 疑问:为何设置世界Y轴冲量后速度立即向Z轴倾斜?轨迹末端偏转原因是什么?

资源结构

CAMERA

  • 父物体:CameraHolder,挂载MoveCamera脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveCamera : MonoBehaviour
{
    public Transform cameraPosition;

    void Update()
    {
        transform.position = cameraPosition.position;
    }
}
  • 子物体:PlayerCam(主相机),挂载PlayerCam脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerCam : MonoBehaviour
{
    public float sensX;
    public float sensY;
    public Transform orientation;

    float xRotation;
    float yRotation;

    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    void Update()
    {
        float mouseX = Input.GetAxisRaw("Mouse X") * Time.deltaTime * sensX;
        float mouseY = Input.GetAxisRaw("Mouse Y") * Time.deltaTime * sensY;

        yRotation += mouseX;
        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        transform.rotation = Quaternion.Euler(xRotation, yRotation, 0);
        orientation.rotation = Quaternion.Euler(0, yRotation, 0);
    }
}

PLAYER

  • 父物体:Player,挂载Rigidbody和PlayerMovement脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    [Header("Movement")]
    public float moveSpeed;

    float horizontalInput;
    float verticalInput;
    Vector3 moveDirection;
    Rigidbody rb;

    public float groundDrag;
    public float jumpForce;
    public float jumpCooldown;
    public float airMultiplier;
    bool readyToJump = true;

    [Header("Keybinds")]
    public KeyCode jumpKey = KeyCode.Space;

    [Header("Ground Check")]
    public float playerHeight;
    public LayerMask whatIsGround;
    public Transform orientation;
    bool grounded;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.freezeRotation = true;
    }

    void Update()
    {
        Debug.DrawRay(rb.position, rb.velocity, Color.yellow);
        Debug.DrawRay(rb.position, rb.GetAccumulatedForce(), Color.red);

        grounded = Physics.Raycast(transform.position, Vector3.down, playerHeight * 0.5f + 0.2f, whatIsGround);

        MyInput();
        SpeedControl();

        rb.drag = grounded ? groundDrag : 0;
    }

    private void FixedUpdate()
    {
        MovePlayer();
    }

    private void MyInput()
    {
        horizontalInput = Input.GetAxis("Horizontal");
        verticalInput = Input.GetAxis("Vertical");

        if (Input.GetKey(jumpKey) && readyToJump && grounded)
        {
            readyToJump = false;
            Jump();
            Invoke(nameof(ResetJump), jumpCooldown);
        }
    }

    private void MovePlayer()
    {
        moveDirection = orientation.forward * verticalInput + orientation.right * horizontalInput;
        Debug.Log("Orientation --- " + orientation.position);
        Debug.Log("Orientation --- " + orientation.forward);
        Debug.DrawLine(rb.position, moveDirection, Color.green);

        var force = moveDirection.normalized * moveSpeed * 10f;
        if (!grounded) force *= airMultiplier;
        rb.AddForce(force, ForceMode.Force);
    }

    private void SpeedControl()
    {
        Vector3 flatVelocity = new Vector3(rb.velocity.x, 0f, rb.velocity.y);

        if(flatVelocity.magnitude > moveSpeed)
        {
            Vector3 limitedVelocity = flatVelocity.normalized * moveSpeed;
            rb.velocity = new Vector3(limitedVelocity.x, rb.velocity.y, limitedVelocity.z);
        }
    }

    private void Jump()
    {
        rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
        rb.AddForce(transform.up * jumpForce, ForceMode.Impulse);
    }

    private void ResetJump()
    {
        readyToJump = true;
    }
}
  • 子物体:PlayerObj,挂载Capsule Collider
  • 子物体:Orientation,仅用于方向控制的Transform
  • 子物体:CameraPos,仅用于位置控制的Transform

问题排查与解决方案

针对问题一:速度吸附至世界X轴

问题根源在SpeedControl方法中的向量构造错误:

// 错误代码:混淆了速度轴向,将Y分量赋值给Z
Vector3 flatVelocity = new Vector3(rb.velocity.x, 0f, rb.velocity.y);

旋转玩家时,原代码会把Y轴速度(地面移动时Y速度为0)赋值给Z分量,导致平面向量仅剩X轴分量,限制速度后就会吸附到世界X轴。修正为:

// 正确代码:保留X、Z平面分量,Y置0
Vector3 flatVelocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);

针对问题二:跳跃时Z轴额外推力与轨迹偏转

  1. Z轴额外推力原因:
    同样来自SpeedControl的轴向错误,跳跃后Y轴有速度,原代码会把跳跃的Y速度错误赋值给Z分量,导致限制速度时给Z轴增加了额外速度。修正上述SpeedControl的向量构造即可解决。

  2. 轨迹末端偏转原因:
    按以下步骤排查:

    • 检查场景中是否存在隐藏碰撞体:在场景视图勾选Gizmos中的碰撞体显示,确认无意外的地形、测试碰撞盒;
    • 调整刚体碰撞检测模式:将Rigidbody的碰撞检测模式设为Continuous Dynamic,避免高速移动时碰撞检测遗漏;
    • 校验地面检测LayerMask:确保whatIsGround仅包含地面层,避免误判空中状态导致错误施加地面拖拽或移动力;
    • 检查Orientation物体:确认其仅受PlayerCam脚本控制旋转,无额外父物体或脚本影响其姿态。

内容的提问来源于stack exchange,提问作者Logan Alexander Thies

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:25:27