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

Unity球形重力系统玩家旋转与移动逻辑异常求解

行星重力机制下玩家旋转移动逻辑异常修复

问题背景

开发基于行星重力物理机制的游戏时,玩家旋转功能实现阻塞,核心问题表现:

  • 按下左方向键时,预期玩家左转同时向对应方向移动,实际左转后玩家朝向自身左侧偏移移动,不符合预期移动方向
  • 若直接使用本地输入方向计算位移,会出现坐标偏移、脱离行星表面的问题

原始实现代码(Unity C#):

private void FixedUpdate()
{
    ProcessInput();

    Vector3 gravityUp = (transform.position - gravityTarget.position).normalized;
    Vector3 bodyUp = transform.up;
    
    rb.AddForce(gravityUp * gravity);

    Quaternion targetRotation = Quaternion.FromToRotation(bodyUp , gravityUp) * transform.rotation;
    
    transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation , 50 * Time.fixedDeltaTime);
    transform.rotation =targetRotation;


    Quaternion desRot = Quaternion.LookRotation(moveDir);
    desRot = Quaternion.RotateTowards(transform.rotation, desRot , 360 * Time.deltaTime);


    transform.rotation = desRot;

}

private void Update()
{
    rb.MovePosition(rb.position + transform.TransformDirection(moveDir) * 15f * Time.deltaTime);
}

private void ProcessInput()
{
    moveDir = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
}

问题根因

  1. 旋转逻辑存在冲突:FixedUpdate中先计算了对齐行星重力法向的旋转,紧接着直接把旋转赋值为targetRotation覆盖了Slerp插值结果,后续又直接用无上下方向约束的LookRotation(moveDir)生成目标旋转,两次旋转赋值互相覆盖,导致玩家朝向和重力对齐逻辑完全失效
  2. 移动方向转换逻辑错误:moveDir是基于输入的本地平面向量,直接用TransformDirection转换时没有对齐当前行星表面的切线坐标系,左转后轴向映射错乱
  3. 物理更新生命周期混乱:移动逻辑放在Update中执行,和FixedUpdate的物理、旋转更新不同步,容易出现位置偏移、穿模、脱离表面的问题
  4. 重力方向计算错误:代码中gravityUp是从行星中心指向玩家的向量,施加重力时没有取反,相当于给玩家施加了背离行星的推力,进一步加剧脱面问题

修复方案

核心调整点

  • 移除重复、互相覆盖的旋转赋值逻辑,将重力对齐旋转和玩家输入转向合并计算,保证玩家始终保持脚部朝向行星重心
  • 基于当前重力法向构建行星表面的局部坐标系,将输入方向映射到行星切线平面,从根源避免移动方向带垂直分量导致脱离表面
  • 所有和刚体相关的移动、旋转逻辑全部移到FixedUpdate中执行,保证物理更新帧同步
  • 修正重力方向,关闭刚体默认重力和自动旋转,避免引擎默认物理和自定义行星重力冲突

修复后完整代码

// 可在Inspector面板配置参数
public float gravityStrength = 9.8f;
public float moveSpeed = 15f;
public float rotateSpeed = 360f;
public float gravityAlignSpeed = 50f;

public Transform gravityTarget;
private Rigidbody rb;
private Vector3 moveDir;

private void Awake()
{
    rb = GetComponent<Rigidbody>();
    // 关闭刚体默认旋转、默认重力,所有物理效果手动计算
    rb.constraints = RigidbodyConstraints.FreezeRotation;
    rb.useGravity = false;
}

private void FixedUpdate()
{
    ProcessInput();
    // 1. 计算重力法向(行星中心指向玩家的单位向量)
    Vector3 gravityUp = (transform.position - gravityTarget.position).normalized;
    
    // 2. 施加指向行星的重力
    rb.AddForce(-gravityUp * gravityStrength, ForceMode.Acceleration);

    // 3. 插值对齐行星表面法向
    Quaternion gravityAlignRot = Quaternion.FromToRotation(transform.up, gravityUp) * transform.rotation;
    rb.MoveRotation(Quaternion.Slerp(rb.rotation, gravityAlignRot, gravityAlignSpeed * Time.fixedDeltaTime));

    // 4. 有输入时处理转向和移动
    if (moveDir.sqrMagnitude > 0.01f)
    {
        // 将玩家本地前向、右向投影到行星切线平面,构建表面局部坐标系
        Vector3 surfaceForward = Vector3.ProjectOnPlane(transform.forward, gravityUp).normalized;
        Vector3 surfaceRight = Vector3.ProjectOnPlane(transform.right, gravityUp).normalized;
        // 将输入方向映射到行星切线平面,得到世界空间下的实际移动方向
        Vector3 worldMoveDir = (surfaceRight * moveDir.x + surfaceForward * moveDir.z).normalized;
        
        // 生成对齐重力方向的转向目标,保证转向时不会歪倒
        Quaternion turnTargetRot = Quaternion.LookRotation(worldMoveDir, gravityUp);
        rb.MoveRotation(Quaternion.RotateTowards(rb.rotation, turnTargetRot, rotateSpeed * Time.fixedDeltaTime));
        
        // 基于映射后的世界方向移动
        rb.MovePosition(rb.position + worldMoveDir * moveSpeed * Time.fixedDeltaTime);
    }
}

private void ProcessInput()
{
    moveDir = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:12:31