I would opt to tackle input using a hybrid approach.
- Cache the current input state in the input system, allowing it to be queried.
- Raise appropriate delta input events as necessary.
Lets take an example, pressing both left and mouse buttons imply move forward.
If this use case was designed specifically using only (2), you not only would have to write the handler to detect the mouse button press and release, but maintain state management to track both buttons since its possible one is pressed or released in different frames from the other.
If we used a query approach, the system no longer needs to be bothered with maintaining state for these buttons as the input system does this automatically and exposes a way that a given system could simply poll
if ( input.IsMouseButtonsPressed( Mouse::LEFT || Mouse::RIGHT ) ) {
// do logic
}
This becomes even more powerful when you have more than one occurrance of the same state check, likely in different systems, allowing it to be centralized and for that state to be managed by what I feel is the most appropriate owner, the input system.
In systems design, I like to take this one step farther and layer another abstraction here on top of what I have described the input system doing. This system is called an action or intent system that generally uses both methods to maintain an additional state map meant to abstract an action with a particular keybinding, allowing those bindings to be customized.
Now rather than your movement system looking for key-press W
or up-arrow key for moving forward, it might ask the action system if an action is toggled:
if ( actionSystem.IsActionActive( Action::MOVE_FORWARD ) ) {
// move forward
}
There will often be a mix of both of these in certain systems as sometimes certain bindings are not mutable and therefore those will be used directly, e.g. the left/right mouse button example above.