如何使用SDL同时处理多个按键输入?

15
我需要关于使用SDL处理键盘事件的建议。
我有一个第一人称摄像机,可以前进、后退、左右平移,并且可以使用鼠标来观察周围的环境,这非常棒。以下是我的processEvents函数:
void processEvents()
{
    int mid_x = screen_width  >> 1;
int mid_y = screen_height >> 1;
int mpx = event.motion.x;
int mpy = event.motion.y;
float angle_y  = 0.0f;
float angle_z  = 0.0f;

while(SDL_PollEvent(&event))
{
    switch(event.type)
    {
        case SDL_KEYDOWN:
            switch(event.key.keysym.sym)
            {
                case SDLK_ESCAPE:
                    quit = true;
                    break;
                case SDLK_w:
                    objCamera.Move_Camera( CAMERASPEED);
                    break;
                case SDLK_s:
                    objCamera.Move_Camera(-CAMERASPEED);
                    break;
                case SDLK_d:
                    objCamera.Strafe_Camera( CAMERASPEED);
                    break;
                case SDLK_a:
                    objCamera.Strafe_Camera(-CAMERASPEED);
                    break;
                default:
                    break;
            }
            break;
            
        case SDL_MOUSEMOTION:
            if( (mpx == mid_x) && (mpy == mid_y) ) return;

            SDL_WarpMouse(mid_x, mid_y);

            // Get the direction from the mouse cursor, set a resonable maneuvering speed
            angle_y = (float)( (mid_x - mpx) ) / 1000;      
            angle_z = (float)( (mid_y - mpy) ) / 1000;

            // The higher the value is the faster the camera looks around.
            objCamera.mView.y += angle_z * 2;

            // limit the rotation around the x-axis
            if((objCamera.mView.y - objCamera.mPos.y) > 8)  objCamera.mView.y = objCamera.mPos.y + 8;
            if((objCamera.mView.y - objCamera.mPos.y) <-8)  objCamera.mView.y = objCamera.mPos.y - 8;

            objCamera.Rotate_View(-angle_y);
            
            break;
            
        case SDL_QUIT:
            quit = true;
            break;
            
        case SDL_VIDEORESIZE:
            screen = SDL_SetVideoMode( event.resize.w, event.resize.h, screen_bpp, SDL_OPENGL | SDL_HWSURFACE | SDL_RESIZABLE | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE );
            screen_width = event.resize.w;
            screen_height = event.resize.h;
            init_opengl();
            std::cout << "Resized to width: " << event.resize.w << " height: " << event.resize.h << std::endl;
            break;

        default:
            break;
    }
}
}

现在虽然这个功能可以工作,但它有一些限制。最大的限制,也是我提问的目的,是它似乎只能处理最近按下的按键。所以,如果我按住“s”向后走,然后按下“d”向右平移,我最终只会向右平移,而不会向后走。
有人可以指点我如何更好地处理SDL键盘输入,支持同时按下多个按键等吗?
6个回答

21

SDL可以跟踪所有按键的当前状态。您可以通过以下方式访问此状态:

SDL_GetKeyState()

因此,每次迭代都可以根据按键状态更新移动。为了使移动流畅,应根据更新之间经过的时间来更新移动幅度。


15
一个好的方法是编写一个键盘(“输入”)处理程序来处理输入事件,并将事件状态保存在某种结构中(关联数组听起来不错 - key[keyCode])。
每次键盘处理程序接收到“按键按下”事件时,它会将该键设置为启用(true),并且当它获取到按键弹起事件时,它将其设置为禁用(false)。
然后,您可以同时检查多个键,而无需直接拉取事件,并且您将能够在整个帧中重复使用键盘,而无需将其传递到子例程中。
一些快速的伪代码:
class KeyboardHandler {
    handleKeyboardEvent(SDL Event) {
        keyState[event.code] = event.state;
    }

    bool isPressed(keyCode) {
        return (keyState[keyCode] == PRESSED);
    }

    bool isReleased(keyCode) {
        return (keyState[keyCode] == RELEASED);
    }

    keyState[];
}

...

while(SDL Pull events)
{
    switch(event.type) {
        case SDL_KEYDOWN:
        case SDL_KEYUP:
                keyHandler.handleKeyboardEvent(event);
            break;
        case SDL_ANOTHER_EVENT:
                ...
            break;
    }   
}

// When you need to use it:
if(keyHandler.isPressed(SOME_KEY) && keyHandler.isPressed(SOME_OTHER_KEY))
    doStuff(TM);

真是一个很好的解决方案,这可以在你几乎所有的游戏中使用。 - devsaw

5
如果您正在使用SDL2,则请使用SDL_GetKeyboardState
示例:
const Uint8 *keyboard_state_array = SDL_GetKeyboardState(NULL);

SDL_PollEvent(&event);

if(event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
{
    // Move centerpoint of rotation for one of the trees:
    if (keyboard_state_array[SDL_SCANCODE_UP] && !(keyboard_state_array[SDL_SCANCODE_DOWN]))
    {
        --location.y;
    }
    else if (!keyboard_state_array[SDL_SCANCODE_UP] && keyboard_state_array[SDL_SCANCODE_DOWN])
    {
        ++location.y;
    }

    if (keyboard_state_array[SDL_SCANCODE_RIGHT] && !keyboard_state_array[SDL_SCANCODE_LEFT])
    {
        ++location.x;
    }
    else if (!keyboard_state_array[SDL_SCANCODE_RIGHT] && keyboard_state_array[SDL_SCANCODE_LEFT])
    {
        --location.x;
    }
}

1
我们可以使用 只有 ifs,而不需要添加额外的条件,这样就可以避免使用 else ifs 和 &&s。 - Beyondo

3

除了只关注按键事件,任何需要同时关注多个键的解决方案都必须同时关注按下和释放事件,并跟踪相关键的状态。

因此,我们可以使用以下伪代码:

on keydown:
    case left_key:
        object.setMovement(left)
    case forward_key:
        object.setMovement(forward)

相反,你会有类似以下的内容(再次是伪代码):
on keydown:
    case left_key:
        keystates[left] = true
        object.updateMovement(keystates)
    case forward_key:
        keystates[forward] = true
        object.updateMovement(keystates)

on keyup:
    case left_key:
        keystates[left] = false
        object.updateMovement(keystates)
    case forward_key:
        keystates[forward] = false
        object.updateMovement(keystates)

然后,updateMovement例程将查看keystates,并根据所有移动键的状态来确定组合移动。


0

0
我知道之前的回答已经很好地解决了这个问题,但我想分享一下我的方法,可以区分任意按键的KeyUp、KeyDown和KeyHold。我基本上是将所有按键状态存储在两个数组中,一个叫做Previous,一个叫做Current,这样我就可以同时检测到按键按下、释放和持续按下。
#include "smlkr_input_system.h"


SMLKR_InputSystem* InputSys = NULL; /*--- SINGLETON InputSys ---*/


void SMLKR_InputSystem_Init() {
     
    SMLKR_InputSystem* InputSystem = (SMLKR_InputSystem *)malloc(sizeof(SMLKR_InputSystem));
    
    InputSystem->KeyStatesPrevious = (Uint8*)calloc(512,sizeof(Uint8));
    InputSystem->KeyStatesCurrent = (Uint8*)calloc(512,sizeof(Uint8));
    InputSys = InputSystem;
}

void SMLKR_InputSystem_Feed(SDL_Event* e) {
    while (SDL_PollEvent(e)) {
        if (e->type == SDL_QUIT) {
            break;
        }
    }

    memcpy(InputSys->KeyStatesPrevious,InputSys->KeyStatesCurrent,sizeof(Uint8)*512);
    memcpy(InputSys->KeyStatesCurrent,SDL_GetKeyboardState(NULL),sizeof(Uint8)*512);
}

bool SMLKR_InputSystem_GetKeyDown(const SDL_Scancode keycode) {
    return InputSys->KeyStatesPrevious[keycode] == 0 && InputSys->KeyStatesCurrent[keycode] == 1;
}

bool SMLKR_InputSystem_GetKeyUp(const SDL_Scancode keycode) {
    return InputSys->KeyStatesPrevious[keycode] == 1 && InputSys->KeyStatesCurrent[keycode] == 0;
}

bool SMLKR_InputSystem_GetKey(const SDL_Scancode keycode) {
    return InputSys->KeyStatesPrevious[keycode] == 1 && InputSys->KeyStatesCurrent[keycode] == 1;
}

然后我在游戏循环中使用我的feed函数,就像这样:
void SMLKR_Main_GameLoop() {

    SDL_Event e;
    bool quit = false;

    while (!quit) {
        SDL_RenderClear(MainRenderer);
        SMLKR_InputSystem_Feed(&e);

        if(e.type==SDL_QUIT) { quit = true; }

        SMLKR_World_Update(Game->World);
        SDL_RenderPresent(MainRenderer);
    }
}

最后,这是你可以在任何.cpp文件中使用的代码片段,用于控制按键操作。
if(SMLKR_InputSystem_GetKey(SDL_SCANCODE_LEFT) && SMLKR_InputSystem_GetKey(SDL_SCANCODE_UP)) {
        printf("left&up\n");
}

GetKeyUp和GetKeyDown的工作方式也是相同的。希望能对你有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接