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

游戏鼠标视角系统:鼠标增量计算异常问题求助

问题描述

我正在为自研游戏开发鼠标视角系统,当前使用的代码如下:

class InputManager 
{

public:

    bool GetKey(KeyCode key, KeyState state) 
    {
        int vkCode = static_cast<int>(key);
        short result = GetAsyncKeyState(vkCode);

        switch (state) 
        {
        case KeyState::PRESSED:
            return (result & 0x8000) != 0;

        case KeyState::RELEASED:
            return (result & 0x0001) != 0;

        }

        return false;
    }

    bool GetMouseButton(MouseCode key, KeyState state) 
    {
        int vkCode = static_cast<int>(key);
        short result = GetAsyncKeyState(vkCode);

        switch (state) 
        {

        case KeyState::PRESSED:
            return (result & 0x8000) != 0;

        case KeyState::RELEASED:
            return (result & 0x0001) != 0;

        }

        return false;
    }

    Vector2i GetMousePosition() 
    {
        POINT point;

        GetCursorPos(&point);

        return { point.x, point.y };
    }

    Vector2i GetMouseDelta()
    {
        static POINT lastMousePosition = { 0, 0 };
        POINT currentMousePosition;
        GetCursorPos(&currentMousePosition);

        Vector2i delta = {};

        delta.x = currentMousePosition.x - lastMousePosition.x;
        delta.y = currentMousePosition.y - lastMousePosition.y;

        lastMousePosition = currentMousePosition;

        return delta;
    }

    void SetMouseMode(const MouseMode& mode)
    {
        ShowCursor((bool)mode);

        mouseMode = mode;
    }

    void Update() 
    {
        if (mouseMode == MouseMode::LOCKED && GetKey(KeyCode::ESCAPE, KeyState::PRESSED))
            SetMouseMode(MouseMode::FREE);

        if (mouseMode == MouseMode::LOCKED)
        {
            RECT rect;

            GetWindowRect(GetDesktopWindow(), &rect);

            int CenterX = (rect.right - rect.left) / 2;
            int CenterY = (rect.bottom - rect.top) / 2;

            SetCursorPos(CenterX, CenterY);

            RECT lockRect = { CenterX, CenterY, CenterX, CenterY };
            ClipCursor(&lockRect);
        }
        else
            ClipCursor(NULL);
    }

    static InputManager& GetInstance() 
    {
        static InputManager instance;

        return instance;
    }

private:

    bool IsCursorInWindow() 
    {
        POINT mousePosition;

        GetCursorPos(&mousePosition);
        ScreenToClient(Window::GetHandle(), &mousePosition);

        RECT clientRect;

        GetClientRect(Window::GetHandle(), &clientRect);

        return PtInRect(&clientRect, mousePosition);
    }

    MouseMode mouseMode = MouseMode::FREE;

    Vector2i mouseDelta = {};
    Vector2i lastPosition = {};

    TimePoint lastUpdate = Clock::now();
    Milliseconds updateInterval{ 16 };

    InputManager() = default;

    InputManager(const InputManager&) = delete;

    void operator=(const InputManager&) = delete;
};

目前遇到的问题是:鼠标未按预期在一帧内处于“自由”状态以正确计算鼠标增量,反而被固定在屏幕中心,导致增量计算结果极小,且鼠标无法向下或向右移动。尝试调整更新频率、更换更新线程等方案均无效,请求技术支持。


解决方案

核心问题分析

你的代码存在两个致命逻辑错误:

  1. LOCKED模式下执行顺序颠倒:先把鼠标拉回屏幕中心,再计算增量,导致当前位置和上次位置几乎无差异,增量自然极小。
  2. 错误的ClipCursor限制:把鼠标锁在单个像素点上,完全阻断了鼠标的物理移动,这是无法上下左右移动的直接原因。

修复步骤

  1. 调整Update方法的执行顺序:先计算真实鼠标增量,再重置鼠标位置到中心。
  2. 移除单点ClipCursor限制:LOCKED模式不需要锁死鼠标,只需要每次更新后重置位置即可实现无限视角转动。
  3. 替换GetMouseDelta的静态变量为成员变量:避免模式切换时的残留值导致增量异常。

修改后的代码

class InputManager 
{

public:

    bool GetKey(KeyCode key, KeyState state) 
    {
        int vkCode = static_cast<int>(key);
        short result = GetAsyncKeyState(vkCode);

        switch (state) 
        {
        case KeyState::PRESSED:
            return (result & 0x8000) != 0;

        case KeyState::RELEASED:
            return (result & 0x0001) != 0;

        }

        return false;
    }

    bool GetMouseButton(MouseCode key, KeyState state) 
    {
        int vkCode = static_cast<int>(key);
        short result = GetAsyncKeyState(vkCode);

        switch (state) 
        {

        case KeyState::PRESSED:
            return (result & 0x8000) != 0;

        case KeyState::RELEASED:
            return (result & 0x0001) != 0;

        }

        return false;
    }

    Vector2i GetMousePosition() 
    {
        POINT point;

        GetCursorPos(&point);

        return { point.x, point.y };
    }

    Vector2i GetMouseDelta()
    {
        POINT currentMousePosition;
        GetCursorPos(&currentMousePosition);

        Vector2i delta = {};

        delta.x = currentMousePosition.x - lastMousePosition.x;
        delta.y = currentMousePosition.y - lastMousePosition.y;

        lastMousePosition = currentMousePosition;

        return delta;
    }

    void SetMouseMode(const MouseMode& mode)
    {
        ShowCursor((bool)mode);
        
        // 切换到锁定模式时,重置鼠标位置和记录
        if (mode == MouseMode::LOCKED)
        {
            RECT rect;
            GetWindowRect(GetDesktopWindow(), &rect);
            int centerX = (rect.right - rect.left) / 2;
            int centerY = (rect.bottom - rect.top) / 2;
            
            lastMousePosition = { centerX, centerY };
            SetCursorPos(centerX, centerY);
        }

        mouseMode = mode;
    }

    void Update() 
    {
        if (mouseMode == MouseMode::LOCKED && GetKey(KeyCode::ESCAPE, KeyState::PRESSED))
            SetMouseMode(MouseMode::FREE);

        if (mouseMode == MouseMode::LOCKED)
        {
            // 1. 先获取用户真实的鼠标移动增量
            Vector2i delta = GetMouseDelta();
            
            // 2. 这里把增量传递给视角控制逻辑(示例)
            // Camera::GetInstance().Rotate(delta.x * 0.1f, delta.y * 0.1f);
            
            // 3. 重置鼠标到屏幕中心,准备下一帧计算
            RECT rect;
            GetWindowRect(GetDesktopWindow(), &rect);
            int centerX = (rect.right - rect.left) / 2;
            int centerY = (rect.bottom - rect.top) / 2;
            
            SetCursorPos(centerX, centerY);
            lastMousePosition = { centerX, centerY };
            
            // 可选:如果需要限制鼠标在游戏窗口内,用窗口客户区替代单点
            // RECT clientRect;
            // GetClientRect(Window::GetHandle(), &clientRect);
            // ClientToScreen(Window::GetHandle(), (POINT*)&clientRect.left);
            // ClientToScreen(Window::GetHandle(), (POINT*)&clientRect.right);
            // ClipCursor(&clientRect);
        }
        else
        {
            ClipCursor(NULL);
        }
    }

    static InputManager& GetInstance() 
    {
        static InputManager instance;

        return instance;
    }

private:

    bool IsCursorInWindow() 
    {
        POINT mousePosition;

        GetCursorPos(&mousePosition);
        ScreenToClient(Window::GetHandle(), &mousePosition);

        RECT clientRect;

        GetClientRect(Window::GetHandle(), &clientRect);

        return PtInRect(&clientRect, mousePosition);
    }

    MouseMode mouseMode = MouseMode::FREE;

    Vector2i mouseDelta = {};
    POINT lastMousePosition = { 0, 0 }; // 成员变量替代静态变量,方便模式切换时重置

    TimePoint lastUpdate = Clock::now();
    Milliseconds updateInterval{ 16 };

    InputManager() = default;

    InputManager(const InputManager&) = delete;

    void operator=(const InputManager&) = delete;
};

关键修改说明

  • 成员变量替代静态变量:lastMousePosition改为成员变量后,切换模式时可以直接重置,避免静态变量残留值导致的异常增量。
  • 调整执行顺序:先计算增量再重置鼠标位置,确保增量是用户真实的移动数据。
  • 移除单点锁定:删除RECT lockRect = { CenterX, CenterY, CenterX, CenterY }; ClipCursor(&lockRect);,这个操作会完全锁死鼠标,是问题的核心诱因。
  • 模式切换重置:切换到LOCKED模式时直接把鼠标定位到中心并更新记录,避免第一帧出现异常增量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 21:14:58