SDL2 运行得太快了——为什么当我只按一个按钮时它会读取多个输入?

SDL2 going too fast -- why does it read multiple inputs when I press just one button?

我已经开始编写 SDL2 程序了。我希望整数 count 在用户按下右箭头键时上升一,在用户按下左箭头键时下降一。

#include <iostream>
#include <SDL2/SDL.h>

int main(){
    SDL_Window *window= SDL_CreateWindow("test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
    int count=      0;
    bool isRunning= true;
    SDL_Event ev;
    while(isRunning){
        if(SDL_PollEvent(&ev)){
            if(ev.type == SDL_QUIT  ||  ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE)
                return 0;
        }

        const Uint8 *keystate=    SDL_GetKeyboardState(NULL);

        if(keystate[SDL_SCANCODE_LEFT])
            --count;
        else if(keystate[SDL_SCANCODE_RIGHT])
            ++count;

        std::cout << count << std::endl;
    }

    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

这是我启动程序并短暂地向右轻击时打印的内容的示例——所有这些都在一两秒内完成:

0
0
0
0
0
0
0
0
0
0
0
1
2
3
4
4
4
4
4

当我快速点击右箭头键时,我希望 count 只增加一个,但它却从 0 变成了 4

为什么?

我该如何解决这个问题?

类似于(它实际上询问鼠标按钮,但它与键盘相同):

不要使用 keyboardState,而是使用 SDL 事件。 SDL 将在每次按下按钮时触发一个事件,而快速 while 循环可以在一次按键期间触发多次。

尽管这个例子远非一个最小的、完整的例子而且它没有编译,我猜问题可能是由于缺少对 SDL_PumpEvents.[=20= 的调用造成的]

documentation 开始:

Note: Use SDL_PumpEvents() to update the state array.

否则,状态数组将不会更新为您遇到的结果。

注意

也就是说,尝试依赖事件而不是用于表示键盘的内部状态数组。

编辑

问题更新后更新。

您应该将 SDL_PollEvent 上的 if 替换为 while,如下所示:

while (SDL_PollEvent(&event)) {
    // here you have an event, you can use it
}

否则,即使没有事件,它也会跳过 if 并执行其他语句。
这意味着如果没有事件,第一次按键后键盘的状态将不会更新,但您仍然会对其进行迭代。

有关 SDL_PollEvent 工作原理的更多详细信息,请参阅 here

我将 while 循环更改为:

while(isRunning){
    while(SDL_PollEvent(&ev)){
        if(ev.type == SDL_QUIT  ||  ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE)
            return 0;
        else if(ev.type == SDL_KEYDOWN){
            if(ev.key.keysym.scancode == SDL_SCANCODE_LEFT){
                --count;
                std::cout << count << std::endl;
            }
            else if(ev.key.keysym.scancode == SDL_SCANCODE_RIGHT){
                ++count;
                std::cout << count << std::endl;
            }
        }
    }
}

如果我理解正确,SDL_PollEvent 仅在对象 ev 的地址为真时触发。如果用户按下一个键,它会继续。如果键是左箭头,count 会向下移动一个。如果键是右箭头,count 增加一个。

现在 cout 按我希望的方式打印。

右击几次后的输出:

0
1
2
3
4

然后离开:

4
3
2
1
0

您的问题是您要求 SDL 提供键状态数组,在您的情况下,这不是最佳方法。那么,SDL 在这种情况下做了什么?它只是为您提供一个包含信息的数组,无论当前密钥是否被持有。您尝试尽可能短地按下按钮,但您的循环真的很快。因此,要解决此问题,您可以使用事件系统的 keydown 功能,当您按下一个按钮时,它会为您提供 true(它的对是 SDL_KEYUP 表示按键释放事件)。

主要区别在于问题:是按住键,还是我只是按下并改变了它的状态?

因此,这是一个示例(在 SDL_PumpEvent 或 SDL_PollEvent 中使用):

//...
if (event.type == SDL_KEYDOWN)
{
    if (event.key.keysym.sym == SDLK_LEFT)
    //do left key things...;
    else if (event.key.keysym.sym == SDLK_RIGHT)
    //do right key stuff...;
}
//...

请注意,此方法不使用扫描码,而是使用键码。 (由于键盘类型不同,它们可能会产生与扫描码不同的结果)。 SDLK_ 常量都是键码。此外(如果你考虑游戏),扫描码有利于玩家移动,按键事件有利于 GUI 元素

更多信息参考:https://www.libsdl.org/release/SDL-1.2.15/docs/html/guideinputkeyboard.html

希望您理解! 拉索洛兹