Open main menu

Keymapper

Revision as of 13:10, 6 March 2022 by Praetorian (talk | contribs) (syntax fixes and update sample code)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Keymapper considerations

Basics

  • Backends define what input devices are available and with which characteristics (number of buttons, button names, etc.).
  • Engines define what kind of events they expect for input handing (mouse button presses, keyboard shortcuts, custom actions..) through keymaps.
  • The keymapper maps the events produced by the backend to those that are expected by the engine based on default bindings and user preferences.

The keymapper is not limited to mapping keyboard keys, it is meant to remap any kind of event. At the moment, mouse buttons, keyboard keys and joystick/gamepad buttons events are supported.

For engine developers

  • Keymaps are defined by implementing MetaEngine::initKeymaps(). Keymaps can be seen as a contract for what the engine expects in terms of events for input handing. Key combinations that are not defined by keymaps cannot be used on platforms without a mouse and keyboard. For engines that don't have a keymap, the default game keymap defined in metaengine.cpp is used. Events that don't match a keymap are sent to the engine unprocessed, as they are produced by the backend.
  • A keymap is made of actions. Using the remap dialog users can associate these actions to events generated by the backend. When defining actions, engine developers decide what happens when the action is executed. There are two options (hybrids are possible):
    • Generate events that match what the existing event handling the engine expects. That's easy to do. Just declare the keymap and it's done. However there are issues with this approach. A user who changes the default binding for an action does not expect the original binding to still work. Depending on how the engine is structured this also may cause key handling to be defined twice: Once in the keymap, and once in the event loop.
    • Generate custom actions events with meaning private to the engine (EVENT_CUSTOM_ENGINE_ACTION_{START,END}).


Example when reusing the existing input handling:
Common::KeymapArray MyMetaEngine::initKeymaps(const char *target) const {
	Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "my-engine", "My game keymap");

	Action *act;

	act = new Action("TALK", _("Talk"));
	act->setKeyEvent(KEYCODE_t);
	act->addDefaultInputMapping("t");
	act->addDefaultInputMapping("JOY_X");
	engineKeyMap->addAction(act);

	act = new Action("USE", _("Use"));
	act->setKeyEvent(KEYCODE_u);
	act->addDefaultInputMapping("u");
	act->addDefaultInputMapping("JOY_Y");
	engineKeyMap->addAction(act);

	return Keymap::arrayOf(engineKeyMap);
}

void MyEngine::doInput() {
	Common::Event event;
	while (_system->getEventManager()->pollEvent(event)) {
		switch (event.type) {
		case Common::EVENT_KEYDOWN:
			switch (event.kbd.keycode) {
			case Common::KEYCODE_t:
				doTalk();
				break;
			case Common::KEYCODE_u:
				doUse();
				break;
			}
		}
	}
}


Example when using custom engine actions:
enum MyEngineAction {
	kMyActionTalk,
	kMyActionUse
}

Common::KeymapArray MyMetaEngine::initKeymaps(const char *target) const {
	Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "my-engine", "My game keymap");

	Action *act;

	act = new Action("TALK", _("Talk"));
	act->setCustomEngineActionEvent(kMyActionTalk);
	act->addDefaultInputMapping("t");
	act->addDefaultInputMapping("JOY_X");
	engineKeyMap->addAction(act);

	act = new Action("USE", _("Use"));
	act->setCustomEngineActionEvent(kMyActionUse);
	act->addDefaultInputMapping("u");
	act->addDefaultInputMapping("JOY_Y");
	engineKeyMap->addAction(act);

	return Keymap::arrayOf(engineKeyMap);
}

void MyEngine::doInput() {
	Common::Event event;
	while (_system->getEventManager()->pollEvent(event)) {
		switch (event.type) {
		case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
			switch ((MyEngineAction)event.customType) {
			case kMyActionTalk:
				doTalk();
				break;
			case kMyActionUse:
				doUse();
				break;
			}
		}
	}
}
Engines with engine-driven input handing should prefer using custom action events as this approach is more powerful. Engines where input handling is script-driven probably have no choice but to use the first option. It's up to the engine developer to choose which is the best in each specific context.
  • Keymaps can be situational, thus engines may define multiple keymaps. Keymaps can be enabled / disabled at any time to select which actions are relevant for the current situation. For example for a RPG game there may be one keymap for the overworld, one for combat and one for shops.
  • Text input needs special care in conjunction with the keymapper. Given that keys can be remapped, players may want to remap, for example, the arrow keys to WSAD. But then when text input is required, it is no longer possible to type those letters because they map to the arrow keys. The solution to this issue is to disable the keymap that handles key shortcuts while a text input widget is focused.
  • Keep in mind that some ports may not have a keyboard and mouse. The default input mapping should include mapping for game controllers.

For backend developers

  • To define the available input devices, a backend needs to implement OSystem::getHardwareInputSet(). The declared input devices must match the events that are produced by the backend in reaction to user input. It's best not to have any hardcoded button mapping in the backend. For example in the case of a game console with solely a gamepad as input device, when the player presses a button, the backend should send EVENT_JOYBUTTON_{DOWN,UP} events, not EVENT_LBUTTON{DOWN,UP}. The keymapper will do the transformation from joystick events to mouse events if necessary.
  • Backends can define (or replace) default bindings for any keymap. To do so, implement OSystem::getKeymapperDefaultBindings(). This can be used to make room in the default keymaps for backend specific actions, or to provide better defaults for the platform than those defined in the keymap.
  • Backends can define keymaps to allow players to remap the backend specific features. To do so, implement OSystem::getGlobalKeymaps(). When implementing backend specific keymaps, it's best to request EVENT_CUSTOM_BACKEND_ACTION_{START,END} events in response to the user input. During event handling, Event::customType is used to recognize the action that needs to be executed.

TODO

  • Consider adding support for joystick button combos
  • Rework the way keyboard input is mapped to actions. Match only on the keycode and then filter on the modifiers to handle partial matches. Besides keep a list of currently started actions. When receiving a key up event, check if an end event needs to be emitted for one of the started actions ignoring the modifiers (to handle the case where the user stops pressing the modifier keys first). Additionally filter the emitted actions based on the list of currently started actions to prevent an action from being started twice at the same time by two different inputs.