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

Unity新输入系统下实现类谷歌地图鼠标定点平滑缩放的问题求助

解决Unity新输入系统下正交相机围绕鼠标平滑缩放的问题

嘿,我完全懂你现在的困扰——正交相机围绕鼠标点缩放看起来是个标准需求,但新输入系统的事件驱动模式加上坐标转换的细节,确实容易踩坑。咱们先拆解一下你之前尝试里的问题,再给出一个能完美实现平滑缩放的方案。

问题根源分析

  1. 第一种尝试的抖动:你提到的抖动主要来自两个点:一是直接使用输入事件触发的即时更新(没有和Unity的帧更新周期同步),二是计算相机位移时没有考虑缩放前后的视口比例变化——直接用deltaPosition会导致每帧的位移计算和缩放不同步,产生跳动。
  2. 第二种尝试的偏移:你的offset计算逻辑没抓住核心——缩放时需要保证鼠标所在的世界点在缩放前后始终处于屏幕的同一位置,而不是基于当前相机位置做简单偏移。

正确的实现思路

核心逻辑非常清晰:缩放前记录鼠标对应的世界坐标,缩放后计算相机需要移动多少距离,才能让这个世界坐标仍然落在鼠标的屏幕位置上。同时结合LateUpdate保证相机更新时机正确,用平滑插值解决抖动问题。

完整代码实现

下面是适配新输入系统的完整代码,包含平滑缩放、围绕鼠标点定位、边界限制的功能:

using UnityEngine;
using UnityEngine.InputSystem;

public class CameraZoomController : MonoBehaviour
{
    [Header("相机设置")]
    public Camera mainCam;
    [Range(1f, 20f)] public float zoomSpeed = 5f;
    [Range(0.1f, 0.5f)] public float smoothFactor = 0.2f;

    [Header("缩放范围")]
    public float minOrthographicSize = 2f;
    public float maxOrthographicSize = 15f;

    [Header("移动边界")]
    public Vector2 minPanPosition = new Vector2(-20f, -20f);
    public Vector2 maxPanPosition = new Vector2(20f, 20f);

    // 新输入系统的动作引用
    private PlayerInput playerInput;
    private InputAction zoomAction;
    private InputAction mousePositionAction;

    // 缓存变量
    private float targetOrthographicSize;
    private Vector3 targetPosition;

    private void Awake()
    {
        // 初始化新输入系统动作
        playerInput = GetComponent<PlayerInput>();
        zoomAction = playerInput.actions["Zoom"];
        mousePositionAction = playerInput.actions["MousePosition"];

        // 初始化目标值为当前相机状态
        targetOrthographicSize = mainCam.orthographicSize;
        targetPosition = transform.position;
    }

    private void OnEnable()
    {
        // 订阅输入事件(这里只是记录输入,实际更新在LateUpdate)
        zoomAction.performed += OnZoomPerformed;
    }

    private void OnDisable()
    {
        zoomAction.performed -= OnZoomPerformed;
    }

    private void OnZoomPerformed(InputAction.CallbackContext context)
    {
        // 获取滚轮的方向值(新输入系统中滚轮返回的是Y轴的增量,通常是±1左右)
        float scrollDelta = context.ReadValue<float>();
        
        // 计算目标缩放值
        targetOrthographicSize -= scrollDelta * zoomSpeed * Time.unscaledDeltaTime;
        targetOrthographicSize = Mathf.Clamp(targetOrthographicSize, minOrthographicSize, maxOrthographicSize);

        // 计算围绕鼠标点的目标位置
        UpdateTargetPosition();
    }

    private void UpdateTargetPosition()
    {
        // 1. 获取当前鼠标的屏幕坐标,并转换为世界坐标(缩放前的坐标)
        Vector2 mouseScreenPos = mousePositionAction.ReadValue<Vector2>();
        Vector3 mouseWorldPos = mainCam.ScreenToWorldPoint(new Vector3(mouseScreenPos.x, mouseScreenPos.y, mainCam.nearClipPlane));

        // 2. 计算缩放前后的视口比例变化
        float scaleRatio = targetOrthographicSize / mainCam.orthographicSize;

        // 3. 计算相机需要移动的距离:让鼠标所在的世界点在缩放后仍然处于鼠标屏幕位置
        Vector3 newCameraPosition = mouseWorldPos - (mouseWorldPos - transform.position) * scaleRatio;

        // 4. 应用边界限制
        newCameraPosition.x = Mathf.Clamp(newCameraPosition.x, minPanPosition.x, maxPanPosition.x);
        newCameraPosition.y = Mathf.Clamp(newCameraPosition.y, minPanPosition.y, maxPanPosition.y);

        // 5. 更新目标位置
        targetPosition = newCameraPosition;
    }

    private void LateUpdate()
    {
        // 平滑更新相机的位置和缩放,解决抖动问题
        mainCam.orthographicSize = Mathf.Lerp(mainCam.orthographicSize, targetOrthographicSize, smoothFactor / Time.deltaTime);
        transform.position = Vector3.Lerp(transform.position, targetPosition, smoothFactor / Time.deltaTime);
    }

    // 如果你需要直接设置缩放等级,可以保留这个方法
    public void SetZoomLevel(float level)
    {
        targetOrthographicSize = Mathf.Clamp(level, minOrthographicSize, maxOrthographicSize);
        UpdateTargetPosition();
    }
}

关键细节解释

  1. 新输入系统的处理:我们只在输入事件中记录目标缩放值和计算目标位置,实际的相机更新放在LateUpdate中,保证和Unity的帧渲染周期同步,避免抖动。
  2. 围绕鼠标点的核心计算:
    • 先拿到缩放前鼠标对应的世界坐标mouseWorldPos
    • 计算缩放比例scaleRatio(目标视口大小/当前视口大小)
    • 相机的新位置 = 鼠标世界点 - (鼠标世界点 - 当前相机位置) * 缩放比例 —— 这个公式的本质是让鼠标点在缩放前后的视口相对位置保持不变
  3. 平滑处理:用Mathf.Lerp和Vector3.Lerp做插值,结合smoothFactor和Time.deltaTime,保证缩放和移动的平滑性,不会有跳动。
  4. 边界限制:在计算目标位置时直接做clamp,避免相机移出指定区域。

新输入系统设置提示

记得在你的Input Action Map中创建两个动作:

  • Zoom:绑定到Mouse -> Scroll Wheel,类型设为Value(Axis)
  • MousePosition:绑定到Mouse -> Position,类型设为Value(Vector2)

然后把这个脚本挂在相机的父物体(或者直接挂在相机上,如果不需要父物体管理的话),并赋值好mainCam等参数。

这样实现出来的效果就是:滚动鼠标滚轮时,相机平滑地围绕鼠标所在的世界点放大/缩小,完全不会有抖动或者偏移问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 19:42:34