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

Unity 3D开发中看向特定角度时相机控制器异常抖动问题

Unity 相机控制器特定方向旋转抖动问题修复

开发相机控制器时遇到无明确触发规律的异常:当相机看向特定方向时会持续抖动,过程中相机旋转会出现无征兆的快速跳变,无法定位问题根源。
以下是在相机对象上挂载、在Update生命周期中持续调用的拍照模式控制代码:

void PhotoMode()
{
    Vector3 newPos = transform.position;
    // 向前移动
    if(Input.GetKey(InputManager.IM.forward))
    {
        newPos += cam.transform.forward  * moveSpeedPhoto * Time.unscaledDeltaTime;
    } 

    // 向后移动
    if(Input.GetKey(InputManager.IM.backward))
    {
        newPos -= cam.transform.forward * moveSpeedPhoto * Time.unscaledDeltaTime;
    } 

    // 向左移动
    if(Input.GetKey(InputManager.IM.left))
    {
        newPos += -transform.right * moveSpeedPhoto * Time.unscaledDeltaTime;
    }

    // 向右移动
    if(Input.GetKey(InputManager.IM.right))
    {   
        newPos += transform.right * moveSpeedPhoto * Time.unscaledDeltaTime;
    }

    float x = Input.GetAxis("Mouse X");
    float y = Input.GetAxis("Mouse Y");

    x *= photoRotSpeed * Time.unscaledDeltaTime;
    y *= photoRotSpeed * Time.unscaledDeltaTime;

    currentX = Mathf.Lerp(currentX, x, smoothTime * Time.unscaledDeltaTime);
    currentY = Mathf.Lerp(currentY, y, smoothTime * Time.unscaledDeltaTime);

    float rotationX = transform.localEulerAngles.y + currentX * photoRotSpeed;

    rotationY += currentY * photoRotSpeed;
    rotationY = Mathf.Clamp(rotationY, -90+65, 90+65);

    transform.localRotation = Quaternion.Euler(new Vector3(-rotationY, rotationX, 0));

    transform.position = newPos;
}

相机异常抖动效果演示

问题根因

出现特定方向触发抖动的核心原因有三个:

  • 欧拉角读取跳变:计算水平旋转角度时,直接读取transform.localEulerAngles.y作为累计基准。Unity返回的欧拉角y轴取值范围固定为0~360度,当相机旋转跨过0度/360度阈值时,这个值会瞬间从359跳变为0,直接造成数百级别的角度差量,触发相机快速甩动式抖动,这也是只有特定方向才会触发异常的根本原因。
  • 旋转转速重复乘算:鼠标输入的x、y轴数值已经提前乘过一次photoRotSpeed * Time.unscaledDeltaTime,后续计算实际旋转角度时又对平滑后的输入值二次乘算photoRotSpeed,相当于实际旋转速度是参数设定值的平方倍,进一步放大了跳变带来的影响。
  • 俯仰角度范围设置不当:原代码将俯仰角限制在-25度到155度区间,接近万向节锁的临界角度,在该区间内旋转时容易出现旋转轴向偏移,进一步加剧抖动效果。

修复方案

针对上述问题的修复逻辑为:不再依赖Transform组件返回的欧拉角值,自行维护累计旋转角度;去掉重复乘算的转速参数;修正俯仰角限制范围避开万向节锁。修复后代码如下:

// 全局维护累计旋转值,避免依赖Transform欧拉角的跳变值
private float currentRotX; // 水平旋转累计值
private float rotationY; // 俯仰旋转累计值

void PhotoMode()
{
    Vector3 newPos = transform.position;
    // 向前移动
    if(Input.GetKey(InputManager.IM.forward))
    {
        newPos += cam.transform.forward  * moveSpeedPhoto * Time.unscaledDeltaTime;
    } 

    // 向后移动
    if(Input.GetKey(InputManager.IM.backward))
    {
        newPos -= cam.transform.forward * moveSpeedPhoto * Time.unscaledDeltaTime;
    } 

    // 向左移动
    if(Input.GetKey(InputManager.IM.left))
    {
        newPos += -transform.right * moveSpeedPhoto * Time.unscaledDeltaTime;
    }

    // 向右移动
    if(Input.GetKey(InputManager.IM.right))
    {   
        newPos += transform.right * moveSpeedPhoto * Time.unscaledDeltaTime;
    }

    float x = Input.GetAxis("Mouse X");
    float y = Input.GetAxis("Mouse Y");

    // 去掉提前乘的photoRotSpeed,避免重复乘算
    x *= Time.unscaledDeltaTime;
    y *= Time.unscaledDeltaTime;

    currentX = Mathf.Lerp(currentX, x, smoothTime * Time.unscaledDeltaTime);
    currentY = Mathf.Lerp(currentY, y, smoothTime * Time.unscaledDeltaTime);

    // 用自维护的累计值计算旋转角度,不再读取Transform的实时欧拉角
    currentRotX += currentX * photoRotSpeed;
    rotationY += currentY * photoRotSpeed;
    // 修正俯仰角范围,避开万向节锁临界值
    rotationY = Mathf.Clamp(rotationY, -89f, 89f);

    transform.localRotation = Quaternion.Euler(new Vector3(-rotationY, currentRotX, 0));
    transform.position = newPos;
}

可选优化:如果希望前后移动时不随俯仰角度产生上下漂移,可对移动方向做水平投影处理,移除y轴分量后归一化再参与移动计算,避免移动过程中出现高度偏移。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.29 14:54:03