Reputation: 43
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
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
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