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; }
问题根因
- 旋转逻辑存在冲突:
FixedUpdate中先计算了对齐行星重力法向的旋转,紧接着直接把旋转赋值为targetRotation覆盖了Slerp插值结果,后续又直接用无上下方向约束的LookRotation(moveDir)生成目标旋转,两次旋转赋值互相覆盖,导致玩家朝向和重力对齐逻辑完全失效 - 移动方向转换逻辑错误:
moveDir是基于输入的本地平面向量,直接用TransformDirection转换时没有对齐当前行星表面的切线坐标系,左转后轴向映射错乱 - 物理更新生命周期混乱:移动逻辑放在
Update中执行,和FixedUpdate的物理、旋转更新不同步,容易出现位置偏移、穿模、脱离表面的问题 - 重力方向计算错误:代码中
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
相关产品推荐
相关产品推荐

