0
\$\begingroup\$

I'm not sure if I understand callback functions completely - they are functions that another function calls? This seems like a powerful tool, especially w/r/t handling input. I've written up this quick and dirty example using callbacks(or at least my understanding of them), but I feel as though I am doing too much.

1st, I would gather input from the OS.

2nd, I would pass that input into some sort of Context to handle input.

3rd, I would use the current active Contexts to shoot off these callback functions, based off of the input.

Something like this,

//Gathers keyboard input
struct InputData {

    std::map<SDLKey, bool> held_keys;
    std::map<SDLKey, bool> pressed_keys;
    std::map<SDLKey, bool> released_keys;
};


//Takes a shared_ptr in the constructor so it always knows what the current InputData is

class Context {

public:
    //Constructors/Destructors
    ...

    //Maps a function to a SDLKey
    void set_held_key_to_function(SDLKey key, std::function func)
    {
        held_key_action[key] = func;
    }

    void set_pressed_key_to_function(SDLKey key, std::function func)
    {
        pressed_key_action[key] = func;
    }

    void set_released_key_to_function(SDLKey key, std::function func)
    {
        released_key_action[key] = func;
    }


    //Fires off actions of pressed keys
    void PressKey(SDLKey key)
    {
        if(input_->pressed_keys[key] == true) {
            pressed_key_action[key]();
        }           
    }

    //Fires off actions of held keys
    void HeldKey(SDLKey key)
    {
        if(input_->held_keys[key] == true) {
            held_key_action[key]();
        }           
    }


    //Fires off actions of released keys
    void ReleasedKey(SDLKey key)
    {
        if(input_->released_keys[key] == true) {
            released_key_action[key]();
        }           
    }


private:
    std::shared_ptr<InputData> input_;

    std::map<SDLKey, std::function> held_key_action;
    std::map<SDLKey, std::function> pressed_key_action;
    std::map<SDLKey, std::function> released_key_action;
};

Is this a viable way to handle user input, especially considering that the input in a Pause Menu would be vastly different than the input in the actual Gameplay part? Or am I vastly over-complicating the entire process of handling input? Am I even understanding callbacks correctly?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

In the broadest sense, yes a callback is nothing more than a function call.

But rather than trying to over-engineer an input system by using function pointers, I would consider approaching input from two angles, and allowing both to be available.

Observer

There are times where it makes sense for part of your code to react to state transitions in a generic semi-coupled way. Listeners are a great way to do this with little effort in C/C++.

 class KeyboardListener {
 public:
   void OnKeyEvent(const KeyEvent& evt) {}
 };

Any object that now wants to be informed about keyboard events can simply be derived from this listener contract and your input system simply needs to allow a way to register these dependencies.

 class Input {
 public:
   void AddListener(KeyboardListener* pListener) {
    /* add pointer to a list */
   }
 };

Whenever a keyboard press/release occurs, your input system can then create a KeyEvent, populate it with information and then iterate the listeners and dispatch it.

Polling

There are often other times where it can be just as easy and perhaps cleaner to poll for the current state rather than trying to react to it using a listener.

For example, in a PlayerController, it may make more sense to simply do something like this in it's update method rather than trying to implement it as a listener.

 void PlayerController::Update(float deltaTime) {
   if(Input.IsKeyDown(KeyCode::KC_SPACE) && !isJumping && isGrounded) {
     isJumping = true;
     Jump(deltaTime);
   }
 }

When you combine this together, you can start to create elaborate high-level systems. For example, an action/keybind system may act as an observer to the input system. As input occurs, the action system updates the state of game action commands based on bindings between keys and actions. Any other piece of code can query the current state of a game action command and react accordingly.

void PlayerController::Update(float deltaTime) {
  if(Actions.IsActionToggled(JumpAction::NAME)) {
    mJump->Execute(this);
  }
}

This allows you to separate the actual key combinations from the actions and even allow actions to be triggered based on a series of key presses rather than just logical press/release state. And instead of polling, you could react with listeners.

void PlayerController::OnAction(const Action& action) {
 switch(action.GetType()) {
   case JumpAction::TYPE:
     Jump();
     break;
 };
}

Hope this gives you an idea of how simply this can be handled without having to dive into function pointers :p.

\$\endgroup\$
2
  • \$\begingroup\$ +1 nice. More often than not, I end up with 50% of my input going to either "the camera" (3D) or "the interface" (2D). As such, the two top-level buckets tend towards polling, which then validate and turn the input into specific dispatches, as you've described. I tend to divide them only because the respective buckets have better access to 2D-/3D-specific implementation details. i.e. CheckCollision(Vector2) vs. CheckCollision(Ray) \$\endgroup\$
    – Jon
    Commented Apr 24, 2015 at 9:25
  • \$\begingroup\$ Good read! While it's not exactly what I'm looking for, it's definitely helped. I want to be able to assign different "actions" for different characters/allow the user to set up the controller/keyboard to whatever suits them best, which is why I was initially leaning towards function pointers. However, the Observer pattern mixed with Polling might just be what I should be looking in to. \$\endgroup\$ Commented Apr 24, 2015 at 18:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .