Input - dprandle/nsengine GitHub Wiki
The engine input is handled by a combination of raw input events, the input system, and a resource called an input map. The purpose of doing this is to abstract specific keys away from the input response code so that a certain function can easily be re-mapped to a different key.
Imagine that we were building a game where space bar was jump, and “e” was the action key to interact. Everywhere in the input response code there would be “if (key_pressed == key_e)” or “if (key_pressed == key_space)” types of things. Then what if we decide e and space are bad key assignments - that it would be better if they were switched?
In the engine, to map specific actions to keys or mouse buttons, a resource called an “nsinput_map” is used. Input maps can be created, deleted, saved, and loaded the same way any other resource in the engine can. To use an input map, just assign it to the input system. The input system will then use the input map to translate raw input events in to action events. The code below shows how to create a new input map and assign it to the input system. This code assumes the engine has been started and a plugin has been setup as the active plugin.
#include <nsinput_system.h>
#include <nsinput_map.h>
#include <nsengine.h>
#include <nsglfw.h>
#include <nsplugin.h>
#include <nsinput_map_manager.h>
#include <nsopengl_driver.h>
void setup_basic_plugin();
void setup_input_map(nsinput_map * imap);
int main()
{
glfw_setup(ivec2(800,600), false, "Sample Prog");
nse.create_context();
nse.create_video_driver<nsopengl_driver>();
nse.start();
nsplugin * plg = setup_basic_plugin();
// Create a new input map in the plugin
nsinput_map * imap = plg->create<nsinput_map>("my_input_map");
setup_input_map(imap);
while (glfw_window_open())
{
nse.update();
glfw_update();
}
nse.shutdown();
glfw_shutdown();
}
void setup_input_map(nsinput_map * imap)
{
// more setup later
// now assign the input map to the input system
nse.system<nsinput_system>()->set_input_map(imap->full_id());
}
Next we will see how to add some triggers to the input map and respond to those triggers to do something useful.
Let’s say we want to have an action - “jump” - this action can be assigned to a key through the above map. To do this, create a trigger and then assign any key or mouse buttons and modifiers, then add the trigger to the map. But before we do anything, we need to add a context to the input map. You can think of contexts as stacked key mappings - they can be pushed and popped from the input system. The key mapping on the top of the stack has highest precedence, and the key mapping at the bottom has the lowest.
Why bother with this whole context thing? Well - think about pressing the mouse button. It will likely do vastly different things depending on what part of the game you are playing. Having contexts allows the game to have customizable key mappings for different parts of the game. To enable the key mappings for some part, just push the context on to the input system - and then pop it when done.
The normal procedure would be to create some kind of “global” context. Here you would define a key mapping for the most common actions. Then, create another context for, say, while in the pause menu.
Back to the jump scenerio - let’s say during most of our game, space bar is the jump key. But during pause, space menu unpauses the game. To accomplish this, we could first create two contexts, and add jump to the global one and add unpause to the pause screen one.
For example:
void setup_input_map(nsinput_map * imap)
{
// Create the two contexts
imap->create_context("global");
imap->create_context("pause_screen");
// create the trigger - will occur on key press (as opposed to release, or both)
// Make it so any mouse button can be held and will still trigger
nsinput_map::trigger jump_triggger("Jump", nsinput_map::pressed);
jump_trigger.add_mouse_mod(nsinput_map::any_button);
// create the trigger for unpause
nsinput_map::trigger unpause_triggger("Unpause", nsinput_map::pressed);
unpause_trigger.add_mouse_mod(nsinput_map::any_button);
// Add the jump trigger to the global context - associate it with the space bar
imap->add_key_trigger("global", nsinput_map::key_space, jump_trigger);
// Add the unpause trigger to the pause screen context, also make it space bar
imap->add_key_trigger("pause_screen", nsinput_map::key_space, unpause_trigger);
// now assign the input map to the input system
nse.system<nsinput_system>()->set_input_map(imap->full_id());
// push the global context
nse.system<nsinput_system>()->push_context("global");
}
Since the global context is pushed, when space bar is pressed the “Jump” action event will be created. If we push the pause_menu context, since the jump_trigger and the unpause trigger are both assigned to space bar and have the same key/mouse modifiers, the “Unpause” action event will be generated instead of the “Jump” event.
As mentioned in the Events section, all systems are event handlers. This means you can register a system to receive action events, which are generated by the input system using an input map. Using the above Jump example, we can build a very simple system to respond to Jump and Unapause action events. First, we create functions with an action_event * as the only parameter. Then we register these functions as the function to be called when “Unpause” and “Jump” action events are sent.
The following basic system should do what we want.
class our_system : public nssystem
{
public:
our_system(): nssystem() {}
~our_system() {}
virtual void init()
{
register_action_handler(our_system::_jump, "Jump);
register_action_handler(our_system::_unpause_game, "Unpause");
}
virtual void release()
{
unregister_action_handler(our_system::_jump, "Jump");
unregister_action_handler(out_system::_unpause_game, "Unpause");
}
virtual void update()
{
// do stuff
}
int32 update_priority()
{
return 2000;
}
private:
bool _unpause_game(nsaction_event * evnt)
{
// execute code to unpause game or set a bool saying we should unpause and do it in update
}
bool _jump(nsaction_event * evnt)
{
// execute code to jump or set a bool saying we should jump and do it in update
}
};
See the input example to see a complete example, and play around with the code!