3

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

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

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

    味精消息;
    而(一)
    {
        if( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ) )
        {
            如果(message.message == WM_QUIT)
            {
                休息 ; // 当 WM_QUIT 时保释
            }
            
            TranslateMessage( &message ) ;
            DispatchMessage( &message ) ;
        }
        别的
        {
            // 没有消息,所以运行游戏。
            更新() ;
            画() ;
        }
    }

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

我通过使用 BOOL 数组来记住哪些键已关闭,从而解决了这个问题:

    bool array_of_keys_that_are_down[256];

    案例 WM_INPUT :
        if(它的键盘输入)
        {
            array_of_keys_that_are_down[VK_CODE] = TRUE;
        }

这很好用,因为 Update() 函数检查

    无效更新()
    {
        如果(array_of_keys_that_are_down[VK_RIGHT])
        {
            // 将玩家向右移动一点
        }
    }

但现在的问题是 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));

*编辑

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

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

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

4

5 回答 5

7

你可以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

你完成了。

于 2010-02-14T21:02:02.017 回答
1

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

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

于 2010-02-14T20:36:52.077 回答
0

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

int SetKeyboardSpeed(int nDelay){
   /* 最快的 nDelay = 31,最慢的 nDelay = 0 */
   返回(系统参数信息(SPI_SETKEYBOARDSPEED,nDelay,NULL,SPIF_SENDCHANGE)> 0);
}
int SetKeyboardDelay(int nDelay){
   /* 0 = 最短(大约 250 毫秒)到 3 最长(大约 1 秒)*/
   返回(系统参数信息(SPI_SETKEYBOARDDELAY,nDelay,NULL,SPIIF_SENDCHANGE)> 0);
}

编辑:作为对Blindy对downvote 的评论的回应——你是 100% 正确的——从来没有意识到当我在其中输入代码时...... - 用户不知道的范围内的设置!我接受 Blindy 的评论的纠正。请忽略我的回答,因为它是 100%错误的!

希望这会有所帮助,最好的问候,汤姆。

于 2010-02-14T17:32:41.977 回答
0
  1. 使用窗口消息来检测按键,并自己跟踪按键数组。
  2. 注意 WM_ACTIVATE 消息并在该点使用 GetAsyncKeyboardState 来“修复”数组以匹配实际状态。

这种组合应该使事情保持一致。

于 2010-02-15T12:14:00.593 回答
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 结构中的 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/ 网页存档链接

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

于 2021-01-29T16:01:00.247 回答