GetRawInputData与GetAsyncKeyState()

时间:2010-02-14 16:44:32

标签: c++ winapi directx directinput

好吧,我正在努力避免使用已弃用的DirectInput。

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

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 消息。这会产生奇怪的“粘滞键”错误。

5 个答案:

答案 0 :(得分:6)

您可以改用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(&current[0]); // normally do error checking

你已经完成了。

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

正如你所说的有延迟,我认为你想要减少延迟,为什么不调用'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 对downvote的评论 - 你100%正确 - 从未意识到我输入的内容是代码进入这个...是的,你已经把手指放在它上面,永远不要在没有用户知道的情况下改变全局/系统范围的设置!我的立场由Blindy的评论纠正。请忽略我的答案,因为它是100%错误

希望这有帮助, 最好的祝福, 汤姆。

答案 3 :(得分:0)

  1. 使用Window消息进行检测 按键,并跟踪数组 按键自己。
  2. 注意 WM_ACTIVATE消息并使用 此时获取GetAsyncKeyboardState “修复”数组以匹配 实际状态。
  3. 这种组合应该保持一致。

答案 4 :(得分:0)

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

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

<块引用>

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

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 消息。它使 奇怪的“粘滞键”错误。

当您 RAWINPUTDEVICE structure 时,通过 register your raw input device 中的 RIDEV_INPUTSINK 标志修复了此问题:即使您的窗口不在前台,您也会收到消息。

您的问题似乎不在您使用的 API 中。您想要一种与系统交互的特定方式:

<块引用>

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

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

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

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

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

这里只是一些:

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

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