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

使用四元数解决FPS游戏重力切换时的相机旋转万向节锁问题

解决Unity多重力方向下FPS相机旋转的万向节锁问题

核心问题在于你用全局欧拉角累积旋转,当重力方向切换到非Y轴时,欧拉角的轴定义和局部坐标系脱节,导致万向节锁和旋转失效。正确的做法是基于当前玩家的局部坐标系(或当前重力对应的地面坐标系)来应用鼠标旋转,用四元数叠加旋转而非直接修改欧拉角。

重构后的完整代码

替换你的PlayerCamMove类,这个版本支持所有六个重力方向,且彻底解决万向节锁问题:

using UnityEngine;

public class PlayerCamMove : MonoBehaviour
{
    public float sensX = 200f;
    public float sensY = 200f;
    public GameObject camera;

    // 限制上下视角的最大角度
    private float verticalClamp = 90f;
    // 相机相对于玩家的上下旋转量
    private float cameraVerticalRotation = 0f;

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

    void Update()
    {
        // 获取鼠标输入
        float mouseX = Input.GetAxis("Mouse X") * sensX * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * sensY * Time.deltaTime;

        // 1. 获取当前重力对应的地面向上轴(重力方向的反方向)
        Vector3 groundUp = GetCurrentGroundUp();

        // 2. 处理玩家左右旋转:绕地面向上轴旋转
        Quaternion playerYawRotation = Quaternion.AngleAxis(mouseX, groundUp);
        transform.rotation = playerYawRotation * transform.rotation;

        // 3. 处理相机上下旋转:绕玩家的局部右轴旋转
        Vector3 playerRight = transform.right;
        Quaternion cameraPitchRotation = Quaternion.AngleAxis(-mouseY, playerRight);
        
        // 计算并限制相机的上下旋转角度
        cameraVerticalRotation -= mouseY;
        cameraVerticalRotation = Mathf.Clamp(cameraVerticalRotation, -verticalClamp, verticalClamp);

        // 4. 应用相机最终旋转:玩家旋转 + 相机自身上下旋转
        // 先让相机继承玩家的旋转,再叠加上下旋转
        Quaternion baseRotation = transform.rotation;
        camera.transform.rotation = baseRotation * Quaternion.AngleAxis(cameraVerticalRotation, Vector3.right);
    }

    // 根据当前重力状态返回地面向上轴(重力的反方向)
    private Vector3 GetCurrentGroundUp()
    {
        switch (GameManager.Instance.gravityDirection)
        {
            case GameManager.GravityState.Down:
                return Vector3.up; // 重力(0,-1,0),地面向上是(0,1,0)
            case GameManager.GravityState.Up:
                return Vector3.down; // 重力(0,1,0),地面向上是(0,-1,0)
            case GameManager.GravityState.Left:
                return Vector3.right; // 重力(-1,0,0),地面向上是(1,0,0)
            case GameManager.GravityState.Right:
                return Vector3.left; // 重力(1,0,0),地面向上是(-1,0,0)
            case GameManager.GravityState.Forward:
                return Vector3.back; // 重力(0,0,1),地面向上是(0,0,-1)
            case GameManager.GravityState.Back:
                return Vector3.forward; // 重力(0,0,-1),地面向上是(0,0,1)
            default:
                return Vector3.up;
        }
    }
}

关键逻辑说明

  1. 基于局部坐标系旋转:

    • 左右旋转(Yaw)始终绕当前地面的向上轴(重力反方向),保证玩家左右转时始终贴合当前“地面”。
    • 上下旋转(Pitch)始终绕玩家的局部右轴,不管玩家怎么旋转,上下看的逻辑都是基于玩家自身的右侧方向,符合FPS直觉。
  2. 四元数叠加:
    用Quaternion.AngleAxis()生成旋转四元数,再通过乘法叠加到当前旋转上,完全避免欧拉角的万向节锁问题,旋转计算更稳定。

  3. 统一重力方向处理:
    通过GetCurrentGroundUp()方法统一返回当前重力对应的地面向上轴,不用为每个重力方向单独写旋转逻辑,扩展性更强。

  4. 视角限制:
    单独维护cameraVerticalRotation变量来限制上下视角,避免旋转叠加导致的角度溢出问题。

额外注意事项

  • 确保玩家的移动逻辑也基于当前地面坐标系(比如移动方向是玩家的局部前/右,而不是世界坐标系),这样切换重力后WASD移动也会贴合新地面。
  • 如果切换重力时需要玩家瞬间旋转到直立状态,可以在重力切换时调用类似transform.rotation = Quaternion.FromToRotation(transform.up, groundUp) * transform.rotation;的代码,让玩家快速对齐新地面。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:35:41