使用GLFW一次处理多个键输入

时间:2017-10-08 13:50:46

标签: c++ input keyboard glfw

我正在尝试使用glfwSetKeyCallback函数创建基本的相机移动。问题是它不能同时处理多个键的输入,例如W和A应该沿对角线左上方向移动。相反,它的行为就像只知道最后输入的密钥。所以,假设我按A向左移动,向左移动,然后按W向对角线左上方移动,而不是'忘记'关于A按下并向上移动。

float cameraSpeed = 0.02f;
    if (key == GLFW_KEY_ESCAPE && (action == GLFW_PRESS || action == GLFW_REPEAT))
        glfwSetWindowShouldClose(window, GLFW_TRUE);
    if (key == GLFW_KEY_W && (action == GLFW_PRESS || action == GLFW_REPEAT))
        Game::GetInstance()->cameraY += cameraSpeed;
    if (key == GLFW_KEY_A && (action == GLFW_PRESS || action == GLFW_REPEAT))
        Game::GetInstance()->cameraX -= cameraSpeed;
    if (key == GLFW_KEY_S && (action == GLFW_PRESS || action == GLFW_REPEAT))
        Game::GetInstance()->cameraY -= cameraSpeed;
    if (key == GLFW_KEY_D && (action == GLFW_PRESS || action == GLFW_REPEAT))
        Game::GetInstance()->cameraX += cameraSpeed;

我想到的一个解决方案就是创建我自己的键盘输入布尔选项卡,并使用GLFW函数来设置它们,如:

if (key == GLFW_KEY_A && action == GLFW_PRESS)
        // set 'A' to true;
if (key == GLFW_KEY_A && action == GLFW_RELEASE)
        // set 'A' to false;

然后在完全独立的函数/类/中执行我想要的操作。虽然它看起来并不干净。什么是解决问题的好方法?

2 个答案:

答案 0 :(得分:4)

我遇到了同样的问题,并决定使用回调来设置一个值为关键状态的数组。 GLFW_REPEAT仅发送给最后一个按下的键。我的回调直接委托给Input类,但这不相关:它们传递所有参数。

void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods){
    Input *input = (Input*)glfwGetWindowUserPointer(win);
    input->keys(win, key, scancode, action, mods);
}

在输入中定义的按键阵列:

bool pressed[KEYS]; // KEYS = 349 (last GLFW macro, GLFW_KEY_MENU = 348)

实际的处理方法,我也处理需要窗口指针的特殊键:

Input::keys(GLFWwindow *win, int key, int scancode, int action, int mods){
    if(key == GLFW_UNKNOWN) return; // Don't accept unknown keys
    if(action == GLFW_PRESS)
        pressed[key] = true;
    else if(action == GLFW_RELEASE)
        pressed[key] = false;

    switch(key){
    case GLFW_KEY_EXCAPE:
        if(action == GLFW_PRESS)
            glfwSetWindowShouldClose(win, true);
    }
}

然后我使用Input::handle(void)进行每次渲染操作后处理主循环内的每个键,像Input in(); in.handle()一样使用:

void Input::handle(void){        // Things to do every frame
    for(int i = 0; i < KEYS; i++){
        if(!pressed[i]) continue;  // Skip if not pressed
        switch(i){
        case GLFW_KEY_SPACE:
            cam->ascend(1.0);    // Ascend with camera
        }
    }
}

我计划包含一个时间常数来处理不同帧速率的问题,但这是它的要点。

结果:即使按下多个键,相机也会在各个方向上平稳移动。

答案 1 :(得分:0)

我实现了我的键盘句柄类版本:

enum EVENT{
    PRESS = 1,
    RELEASE = 2,
    REPEAT = 4,
    NONE = 8
};

static std::map<int, EVENT> event_map = {{GLFW_PRESS, PRESS}, {GLFW_RELEASE, RELEASE}};

class Input{
public:
    Input() {}
    Input(GLFWwindow *w) : window(w) {}
    void set_window(GLFWwindow *w) {window = w;};

void add_key_input(int key, std::function<void(int, float)> f, int events){
    // if (key == -1) {std::cout << "undefinet key : " << key << '\n'; return;}
    keys[key] = KEY(f, event_map[events]); 
}   

void process(float delta){
    for (auto& [key, info] : keys)  {
        int e = glfwGetKey(window, key);
        std::cout << key << " " << e << " " << info.action << " " << info.event << " ";

        if      (e == GLFW_RELEASE &&  info.action == NONE)                             info.action = NONE;
        else if (e == GLFW_RELEASE &&  (info.action == PRESS || info.action == REPEAT)) info.action = RELEASE;
        else if (e == GLFW_PRESS   &&  (info.action == PRESS || info.action == REPEAT)) info.action = REPEAT;
        else if (e == GLFW_PRESS   && (info.action == NONE || info.action == RELEASE))  info.action = PRESS;

        std::cout << info.action << "\n";     
        if (info.event & info.action) {
            info.callback(key, delta);
        }
    }
}

private:
    struct KEY{
        KEY():action(RELEASE) {}
        KEY(std::function<void(int, float)> f, EVENT e): callback(f), action(RELEASE) ,event(e) {}


    std::function<void(int, float)> callback;
    EVENT action;
    EVENT event;
};

GLFWwindow *window;
std::map<int, KEY> keys;
};

使用它。初始化:

key_input.set_window(window);
key_input.add_key_input(GLFW_KEY_ESCAPE, [this](int key, float delta){glfwSetWindowShouldClose(this->window, true);}, PRESS);

在主循环中:

glfwPollEvents();
key_input.process(delta_time); // in my case delta time is time in secs since last frame

您可以将 std::function f, EVENT e>(function<> 是我项目中的 sutusi me)替换为 std::any 并传递任何类型的函数,或尝试使用std::invoke 和模板.