Krzysztof Kwiecień
Krzysztof Kwiecień

Reputation: 43

Handling multiple keys input at once with GLFW

I am trying to create basic camera movement with glfwSetKeyCallback function. The problem is it doesn't handle inputting multiple keys at once, like W and A should move it diagonally upper-left direction. Instead it acts like knowing only about last inputted key. So let's say I press A to move left, it goes left, then I press W to go diagonally upper-left, instead it 'forgets' about A being pressed and goes upward.

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;

The one solution that got into my mind was like create my own boolean tab of keyboard inputs and use GLFW function only to set them, like:

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

And then do what I want with it in totally separate function/class/whatever. It doesn't appear clean to me though. What is a good solution to the problem?

Upvotes: 4

Views: 16623

Answers (2)

brfh
brfh

Reputation: 344

I implement my version of keyboard handle class :

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;
};

to use it. init :

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

and in main loop :

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

You can replace std::function<void(int, float)> f, EVENT e> (function<> is sutusi me in my project) to std::any and pass any type of function, or try to use std::invoke and template<typename Args&...>.

Upvotes: 1

Felix
Felix

Reputation: 2678

I came across the same problem and decided to use callbacks to set an arrays worth of key states. GLFW_REPEAT is only sent for the last key pressed. My callbacks directly delegate to an Input class, but that's not relevant: they pass all the parameters along.

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);
}

The key press array defined inside Input:

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

And the actual handling method, in which I also handle special keys that need the window pointer:

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);
    }
}

Then I handle every key inside the main loop after every render operation with Input::handle(void), used like 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
        }
    }
}

I plan to include a time constant to deal with the issue of varying frame rates, but this is the gist of it.

Result: Camera moves smoothly in every direction even when pressing multiple keys.

Upvotes: 8

Related Questions