GetRawInputData 与 GetAsyncKeyState()

发布于 2024-08-21 00:46:44 字数 2225 浏览 10 评论 0原文

好吧,我试图避免使用已弃用的 DirectInput。

但我需要在游戏的每个“帧”或“迭代”抓取所有关键状态,以便我可以采取相应的行动。例如,如果玩家按下 VK_RIGHT 键,那么他将在该帧上向右移动一点点。

WM_INPUT 消息的问题在于,由于游戏循环的编写方式,它们每帧可能出现不可预测的次数:

    MSG message ;
    while( 1 )
    {
        if( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ) )
        {
            if( message.message == WM_QUIT )
            {
                break ;  // bail when WM_QUIT
            }
            
            TranslateMessage( &message ) ;
            DispatchMessage( &message ) ;
        }
        else
        {
            // No messages, so run the game.
            Update() ;
            Draw() ;
        }
    }

因此,如果多个 WM_INPUT 消息堆叠在那里,那么它们都会被处理在 Update()/Draw() 之前。

我通过使用 BOOL 数组来记住哪些键被按下来解决了这个问题:

    bool array_of_keys_that_are_down[ 256 ] ;

    case WM_INPUT :
        if( its keyboard input )
        {
            array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
        }

这工作正常,因为 Update() 函数会检查

    void Update()
    {
        if( array_of_keys_that_are_down[ VK_RIGHT ] )
        {
            // Move the player right a bit
        }
    }

,但现在的问题是 WM_INPUT 消息不会经常生成 足够了。第一次按下 VK_RIGHT 和随后的 VK_RIGHT 消息之间有大约 1 秒的延迟,即使玩家一直将手指放在上面。它不像 DirectInput,您可以 keyboard->GetDeviceState( 256, (void*)array_of_keys_that_are_down ); (通过一次调用抓取每帧的所有按键状态)

所以我迷路了。除了对我需要监视的每个键调用 GetAsyncKeystate() 函数之外,如果您无法可靠地抓取每个帧的所有键状态,我认为没有办法避免使用 DirectInput。

在我看来,DirectInput 是这个问题的非常好的解决方案,但如果它被弃用,那么确实必须有某种方法可以仅使用 Win32 api 方便地完成此操作。

目前,array_of_keys_that_are_down 每帧都会重置为全 FALSE。

    memset( array_of_keys_that_are_down, 0, sizeof( array_of_keys_that_are_down ) ) ;

*编辑

我一直在解决这个问题,一个解决方案是只重置一个关键状态,一旦它被发布,

    case WM_INPUT :
        if( its keyboard input )
        {
            if( its a down press )
                array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
            else
                array_of_keys_that_are_down[ VK_CODE ] = FALSE ;
        }

我不喜欢这个解决方案,因为它看起来脆弱 >。如果用户在按下某个键时离开应用程序,那么该键将被“卡住”,直到他切换回去并再次按下同一键,因为我们永远不会收到上击 WM_INPUT 消息。它会产生奇怪的“粘滞键”错误。

Well, I'm trying to avoid using the deprecated DirectInput.

But I need, at each "frame" or "iteration" of the game to snatch ALL KEY STATES so that I can act accordingly. For example, if the player is down on the VK_RIGHT key then he will move just a smidgen right on that frame.

The problem with WM_INPUT messages is they can appear an unpredictable number of times per frame, because of the way the game loop is written:

    MSG message ;
    while( 1 )
    {
        if( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ) )
        {
            if( message.message == WM_QUIT )
            {
                break ;  // bail when WM_QUIT
            }
            
            TranslateMessage( &message ) ;
            DispatchMessage( &message ) ;
        }
        else
        {
            // No messages, so run the game.
            Update() ;
            Draw() ;
        }
    }

So if more than one WM_INPUT message is stacked there then they will all get processed before Update()/Draw().

I resolved this issue by using an array of BOOL to remember what keys were down:


    bool array_of_keys_that_are_down[ 256 ] ;

    case WM_INPUT :
        if( its keyboard input )
        {
            array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
        }

That works fine because the Update() function checks


    void Update()
    {
        if( array_of_keys_that_are_down[ VK_RIGHT ] )
        {
            // Move the player right a bit
        }
    }

BUT the problem is now that WM_INPUT messages don't get generated often enough. There's a delay of about 1 second between the first press of VK_RIGHT and subsequent VK_RIGHT messages, even if the player had his finger down on it the whole time. Its not like DirectInput where you can keyboard->GetDeviceState( 256, (void*)array_of_keys_that_are_down ); (snatch out all key states each frame with a single call)

So I'm lost. Other than resorting to GetAsyncKeystate() function calls for each key I need to monitor, I see no way to avoid using DirectInput if you can't snatch out all key states each frame reliably.

It seems to me that DirectInput was a very good solution to this problem, but if it was deprecated, then there really must be some way to do this conveniently using Win32 api only.

Currently array_of_keys_that_are_down gets reset back to all FALSE's every frame.

    memset( array_of_keys_that_are_down, 0, sizeof( array_of_keys_that_are_down ) ) ;

*EDIT

I've been working on this problem and one solution is to only reset a key state, once its been released

    case WM_INPUT :
        if( its keyboard input )
        {
            if( its a down press )
                array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
            else
                array_of_keys_that_are_down[ VK_CODE ] = FALSE ;
        }

I don't like this solution though because it seems flimsy. If the user switches away from the application while down on a key, then that key will be "stuck" until he switches back and presses that same key again because we'll never get the upstroke WM_INPUT message. It makes for weird "sticky key" bugs.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

七度光 2024-08-28 00:46:44

您可以使用 GetKeyboardState 相反。你通常想要的是两个数组;一帧存储前一帧的输入状态,一帧存储当前帧的输入状态。这使得诸如区分被保持和被触发之类的事情成为可能。

// note, cannot use bool because of specialization
std::vector<unsigned char> previous(256);
std::vector<unsigned char> current(256);

// in update_keys or similar:
current.swap(previous); // constant time, yay
GetKeyboardState(¤t[0]); // normally do error checking

你就完成了。

You can use GetKeyboardState instead. What you generally want is two arrays; one stores the previous frames' input state, and one stores the current. This allows things like differentiating between being held and being triggered.

// note, cannot use bool because of specialization
std::vector<unsigned char> previous(256);
std::vector<unsigned char> current(256);

// in update_keys or similar:
current.swap(previous); // constant time, yay
GetKeyboardState(¤t[0]); // normally do error checking

And you're done.

叹倦 2024-08-28 00:46:44

所提出的解决方案是正确的方法——忽略自动重复,只记录向下/向上状态。

要处理任务切换问题,请查看 WM_ACTIVATE 消息——它可以让人们检测到窗口何时失去焦点。当相关窗口发生这种情况时,假设所有键都被释放。 (这类似于使用非独占协作级别时对 DirectInput 所做的事情。)

The presented solution is the right way to do it -- ignore autorepeat, and just record down/up states.

To handle the task switching problem, look into the WM_ACTIVATE message -- it lets one detect when a window loses focus. When this happens to the relevant window, assume all keys become released. (This is similar to what one would have to do with DirectInput when using the nonexclusive cooperative level.)

美煞众生 2024-08-28 00:46:44

解决问题的最佳(就结果的一致性和效率而言)解决方案是使用原始输入。基于事件,快速、高效,不会丢失输入。

您可能会错过 GetAsyncKeyState 调用的输入。例如,假设在以 60 Hz 运行的游戏迭代开始时,您调用 GetAsyncKeyState 并且未按下按键。到目前为止,一切都很好。然后 5 毫秒后,您按下一个键,比如说 VK_TAB,并按住它 5 毫秒。然后在下一个游戏迭代开始时(大约 6.67 毫秒后),您再次调用 GetAsyncKeyState。但到那时该键就不再被按下了。从游戏的角度来看,它从来没有受到过压力!它可能看起来太过遥远,但事实并非如此。我玩过使用该系统的游戏,但在 60 FPS 时错过了输入。令人沮丧且不必要。

我一直在解决这个问题,一个解决方案是仅重置
关键状态,一旦释放

案例 WM_INPUT :
    if( 其键盘输入 )
    {
        if(按下按钮)
            array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
        别的
            array_of_keys_that_are_down[ VK_CODE ] = FALSE ;
    }

我不喜欢这个解决方案,因为它看起来很脆弱。如果用户
按下某个键时退出应用程序,然后按下该键
将被“卡住”,直到他切换回并再次按下同一键
因为我们永远不会收到上行的 WM_INPUT 消息。它使得
奇怪的“粘滞键”错误。

此问题已通过 RAWPUTDEVICE 中的 RIDEV_INPUTSINK 标志修复结构当您注册您的原始数据时输入设备:即使您的窗口不在前台,您也能收到消息。

看来您的问题不在于您正在使用的 API 中。您需要一种与系统交互的特定方式:

但我需要在游戏的每个“帧”或“迭代”中抢夺所有内容
关键状态,以便我可以采取相应行动。例如,如果玩家按下 VK_RIGHT 键,那么他将在该帧上向右移动一点点。

WM_INPUT 消息的问题是它们可能会出现不可预测的情况
每帧的次数,因为游戏循环的方式
写的。

我通过使用 BOOL 数组来记住哪些键解决了这个问题
下降了。

您想在检查时了解关键状态,因此请编写自己的输入处理层。您想要基于轮询的系统,因此创建 KeyboardStateHandler 类,使其响应所有按键按下和按键释放原始输入事件,然后在游戏循环中,调用 KeyboardStateHandler.GetKeys() 并获取所有按键的状态。

我认为您真正想做的是创建一个适当的健壮输入处理层,这将是您上面提出的所有问题的解决方案。

以下只是一些:

https://bell0bytes.eu/inputsystem/ 网络存档链接

https://www.gamedev.net/blog/355/entry- 2250186-designing-a-robust-input-handling-system-for-games/ 网络存档链接

https://blog.gemserk.com/2012/08/23/decoupling -game-logic-from-input-handling-logic/ Web 存档链接

无论您使用什么来实际获取输入:GetKeyAsyncState、DirectInput、Raw Input或其他方法,您希望将输入处理与游戏逻辑分开。

The best(in terms of consistency of results and efficiency) solution to your problem is to use raw input. Event-based, fast, efficient, no chances of missing input.

You can miss inputs with GetAsyncKeyState calls. For example, let's say at the start of a game iteration working at 60 Hz you do a call to GetAsyncKeyState and key is not pressed. So far so good. Then 5 ms later you press a key, let's say VK_TAB, and hold it for 5ms. Then at the start of the next game iteration(about ~6.67ms later) you call GetAsyncKeyState again. But by that time the key is not pressed again. And from the game's perspective it was never pressed! It might look like too far reaching, but it's not. I played games which use this system and miss inputs at 60 FPS. Frustrating and unnecessary.

I've been working on this problem and one solution is to only reset a
key state, once its been released

case WM_INPUT :
    if( its keyboard input )
    {
        if( its a down press )
            array_of_keys_that_are_down[ VK_CODE ] = TRUE ;
        else
            array_of_keys_that_are_down[ VK_CODE ] = FALSE ;
    }

I don't like this solution though because it seems flimsy. If the user
switches away from the application while down on a key, then that key
will be "stuck" until he switches back and presses that same key again
because we'll never get the upstroke WM_INPUT message. It makes for
weird "sticky key" bugs.

This is fixed with a RIDEV_INPUTSINK flag in the RAWINPUTDEVICE structure when you register your raw input device: you get your messages even when your window is not in foreground.

It looks like your problem isn't in the APIs you're using. You want a specific way of interacting with the system:

But I need, at each "frame" or "iteration" of the game to snatch ALL
KEY STATES so that I can act accordingly. For example, if the player is down on the VK_RIGHT key then he will move just a smidgen right on that frame.

The problem with WM_INPUT messages is they can appear an unpredictable
number of times per frame, because of the way the game loop is
written.

I resolved this issue by using an array of BOOL to remember what keys
were down.

You want to know key states when you check for them, so write your own input handling layer. You want polling-based system, so make KeyboardStateHandler class, make it to answer to all the key press and key release raw input events, and then in your game loop, you call keyboardStateHandler.GetKeys() and get states of all keys.

I think what you really want to do is to create an appropriate robust input handling layer, that would be the solution to all of your problems you raised above.

Here are just a few:

https://bell0bytes.eu/inputsystem/ Web archive link

https://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input-handling-system-for-games/ Web archive link

https://blog.gemserk.com/2012/08/23/decoupling-game-logic-from-input-handling-logic/ Web archive link

Doesn't matter what you use to actually get the input: GetKeyAsyncState, DirectInput, Raw Input or other methods, you want to separate input handling from game logic.

巡山小妖精 2024-08-28 00:46:44

正如您所说有延迟,我的印象是您想减少延迟,为什么不调用 'SystemParametersInfo' 要设置打字延迟和速率,您需要查看键盘延迟..'SPI_GETKEYBOARDDELAY'和键盘速度'SPI_GETKEYBOARDSPEED'。该函数如下所示:

int SetKeyboardSpeed(int nDelay){
   /* fastest nDelay = 31, slowest nDelay = 0 */
   return (SystemParametersInfo(SPI_SETKEYBOARDSPEED, nDelay, NULL, SPIF_SENDCHANGE) > 0);
}
int SetKeyboardDelay(int nDelay){
   /* 0 = shortest (approx 250ms) to 3 longest (approx 1sec) */
   return (SystemParametersInfo(SPI_SETKEYBOARDDELAY, nDelay, NULL, SPIIF_SENDCHANGE) > 0);
}

编辑:响应Blindy的反对票评论 - 你是100%正确的 - 从未意识到当我在其中输入代码时...是的,您已经注意到了,永远不要在用户不知情的情况下更改全局/系统范围的设置!我的观点是 Blindy 的纠正评论。请忽略我的回答,因为它是 100% 错误

希望这有帮助,
此致,
汤姆.

As you said there's a delay, I am under the impression that you want to decrease the delay, why not call the 'SystemParametersInfo' to set the typematic delay and speed rate, you would need to look at the keyboard delay..'SPI_GETKEYBOARDDELAY' and keyboard speed 'SPI_GETKEYBOARDSPEED'. The function would look like this:

int SetKeyboardSpeed(int nDelay){
   /* fastest nDelay = 31, slowest nDelay = 0 */
   return (SystemParametersInfo(SPI_SETKEYBOARDSPEED, nDelay, NULL, SPIF_SENDCHANGE) > 0);
}
int SetKeyboardDelay(int nDelay){
   /* 0 = shortest (approx 250ms) to 3 longest (approx 1sec) */
   return (SystemParametersInfo(SPI_SETKEYBOARDDELAY, nDelay, NULL, SPIIF_SENDCHANGE) > 0);
}

Edit: In response to Blindy's comment for the downvote - You are 100% correct - Never realized that as I typed the code into this...and yes, you've put a finger on it, Never ever change global/system-wide settings without the user ever knowing about! I stand corrected by Blindy's comment. Please disregard my answer as it is 100% wrong!

Hope this helps,
Best regards,
Tom.

酒几许 2024-08-28 00:46:44
  1. 使用Window消息来检测
    按键,并跟踪数组
    自己按下按键。
  2. 留意
    WM_ACTIVATE消息并使用
    此时 GetAsyncKeyboardState
    “修复”数组以匹配
    实际状态。

这种组合应该保持一致。

  1. use Window messages to detect
    keypresses, and track the array of
    pressed keys yourself.
  2. watch for
    the WM_ACTIVATE message and use
    GetAsyncKeyboardState at that point
    to 'fix' the array to match the
    actual state.

That combination should keep things consistent.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文