4
\$\begingroup\$

Currently I'm busy working on a game engine. I have my own window class with GLFW functionalities. Currently, I have a very primitive way to handle input in my class:

It is not fast, and not "extendable" enough. I'm essentially using something along the lines of

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_E && action == GLFW_PRESS)
        //do something
}

I would like to call something like the following:

// Create a window object
Window window("title",800,600);
//the parameters specify the key pressed and what the window has to do when the event occurs.
window.getKeyPressed(KEY,ACTION);

How would I go about implementing a more flexible input handling system?

\$\endgroup\$
  • \$\begingroup\$ It's a little unclear what you want here? A specific function to be called when the E-key is pressed instead of a generic one? \$\endgroup\$ – Tyyppi_77 Oct 31 '17 at 7:17
  • \$\begingroup\$ Yes, not the E key in particular, a general key. I also wanted to know what is the "best way" possible to handle input using Glfw in an ipothetical Window class \$\endgroup\$ – user100681 Oct 31 '17 at 8:00
  • 1
    \$\begingroup\$ Best way is subjective. \$\endgroup\$ – Tyyppi_77 Oct 31 '17 at 8:09
  • \$\begingroup\$ I understand, but i actually wanted to have a cleverer method to handle input since mine is not good. That's all \$\endgroup\$ – user100681 Oct 31 '17 at 8:24
4
\$\begingroup\$

If if-statements won't do it for you, I'd suggest a map-based approach, where your Window (or a separate InputManager) class stores a map of function callbacks, that get invoked when a key is pressed.

class Window
{
public:
    using Callback = std::function<void()>;

    void addBinding(int key, const Callback& callback)  
    {
        m_Callbacks[key].push_back(callback);
    }

    void onKeyPress(int key)
    {
        for (Callback& callback : m_Callbacks[key])
        {
            callback();
        }
    }
private:
    std::map<int, std::vector<Callback>> m_Callbacks;
};

This way you can create std::functions and pass those to the class, and then once you get a keypress event from GLFW, all you have to do is call onKeyPress with the correct key, and the callbacks will get invoked.

Just be careful with object lifetime if you end up creating std::functions for class methods. Adding a removeBinding method to the Window class might not be a bad idea.

Also, supporting other events (like key releases for an example) isn't tricky at all with this solution: either add a specific method (like addReleaseCallback) or introduce another layer to the map, so that the first layer fetches the correct bindings for an event type, and the second layer is the key lookup.

\$\endgroup\$
  • 2
    \$\begingroup\$ Thank you for helping, i will definitely try to implement this into my engine! \$\endgroup\$ – user100681 Oct 31 '17 at 13:02
2
\$\begingroup\$

Similar to Tyyppi_77's answer, I also normally opt for a map-based approach. However, I prefer to use a map similar to the one shown below:

struct KeyPress {
    bool current, previous;
}

std::map<int, KeyPress> mKeyPresses;

This way, you can track whether the key has just been pressed, released, or is being held down, based on combinations of bools. You can also easily specify which keys are used, without having to track all of the keys or rely on complicated callbacks. It also, in my opinion, makes conditional events easier to control over callbacks, because you can use if/switch statements to handle whether a key is in a certain state or not.

\$\endgroup\$
1
\$\begingroup\$

There is another option, which is more object orientated.

Generate an engine input event for each GLFW input and send it to a central event queue. Then you can have a bunch of behaviours elsewhere in your engine that will handle respond to these events via hard coded behaviour, or scripts:

class KeyEvent : public GameEvent {
    public:
        int key;
        int keystate;  
};


handleInput(GLFWwindow* window, int scancode, int keystate, Engine& engine) {
    auto event = std::make_shared<KeyEvent>();
    event->key = scancode;
    event->keystate = keystate;
    engine.enqueueEvent(event);
}

Your handler would look something like:

class Controlhandler {
    //game state stuff here
    public:
        void handleEvent(std::shared_ptr<GameEvent> event) {
            auto e = std::dynamic_ptr_cast<KeyEvent>(event);
            if(e){
            // check event, update state, and respond.
            }
        }
};

In this way, you can write control handlers which do a bunch of different stuff with the same events.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy