Skip to content

Instantly share code, notes, and snippets.

What would you like to do?

Every emulator should have most of these input-related features but I haven't found anything with more than a small fraction:

Default and custom input profiles. Custom profiles can have game-specific input bindings. Bindings in custom profiles set to the 'default' value defer to the binding in the default profile; it's important that custom profiles aren't just initialized as a copy of the then-current default profile as this makes it impossible to later change some non-overridden binding in the default profile and have it automatically propagate to existing custom profiles.

The emulator should remember which custom profile was last used for which game, based on the ROM hash or filename. (The emulator should also automatically save any relevant per-game emulator settings, but I think you want that separate from input profiles. As an example of what not to do, if I set the CPU overclocking for Metal Slug in MAME to 200% to remove the annoying but hardware-accurate slowdown, it won't remember the setting for the next time I launch the game.)

Your emulator should support turbo/autofire for all bindable buttons. The autofire should be phase-locked to the emulated hardware's vsync for a number of reasons: Using the host computer's clock mishandles slowdown frames (whether hardware-accurate slowdown or due to the host computer's inability to keep up with emulated hardware) or emulated frame skips, and if you're not phase locked you can get inconsistent results due to phase sliding if the game samples input at inconsistent times within frames. (As a fun fact, external autofire boxes are a common feature for shooting games in Japanese arcades, and they phase lock by tapping the video sync signal in the JAMMA pinout.)

The autofire rate should be settable to even fractions of the refresh rate. For a 60 Hz refresh rate, this is 30 Hz, 20 Hz, 15 Hz, etc. You can display these to the user as the divisor: 1, 2, 3, etc, explaining that lower is faster.

Autofire needs an onset time, with a well-tuned default and an override for advanced users. The onset time is necessary so that when the human user intends to do a single tap, it doesn't register as multiple taps due to autofire; it's easy for a single tap of a keyboard key to take more than 3x16 ms = 48 ms due to a combination of human imprecision, mechanical travel and bounce time, debouncing filters, and various sampling intervals, which would register as 2 taps or more if you had a full-rate autofire with no onset time. The onset time only delays the autofire activation, not the initial key-down event. All the emulator autofire implementations I've tested had this issue and the only solution was to lower the autofire rate from 30 Hz, which wouldn't be necessary if you do onset filtering. MAME's default autofire rate is only 10 Hz and I suspect this is the main reason why.

A more advanced feature that's related to this last onset issue is to support quicktap keys. A quicktap key when pressed will automatically release on the subsequent frame even if the host key is still held down (presumably due to the aforementioned sources of release lag). This is useful for high-precision movement such as tap dodging in shooting games. You can implement this as a special case of autofire with a max rate, a burst limit of 1 and an onset time of 0.

In addition to the usual hold-to-activate for autofire, you should support tap-to-toggle. In lots of action games you end up wanting to fire literally all the time, which makes hold-to-activate needlessly taxing on your hands. Tap-to-toggle solves that issue while giving you enough flexibility to stop firing if and when you need to do so. Hold-to-activate is also an issue for limited key rollover, so tap-to-toggle addresses that too.

You should be able to bind autofire and non-autofire versions of the same underlying key. In some shooting games, holding the fire button has a distinct function from mashing the button (e.g. charge shot vs rapid shot).

More generally, you should be able to bind the same emulated key multiple times. For example, I might want to bind LB and RB to the same emulated key so that when I'm holding LT or RT, I can use the opposite side's bumper button for better ergonomics.

For multiply-bound keys, you want the usual semantics: the button state is the logical OR of the bound key states. However, this may not give what you want for buttons with both autofire and non-autofire bindings. If A and Autofire A are held, what should happen? Extending the logical OR logic to this case would dictate that the "hold A" interpretation takes precedence, which at least seems like a good enough default. This happens to give the same behavior as the A and C button in Cave shooting games: If you hold A (laser) while holding C (rapid shot, essentially the same as A autofire), you get laser. But you could argue in some cases that the autofire should always take precedence, or that the most recently pressed key's function should take precedence.

You should be able to bind combination keys. Just as you can add autofire or quicktap keys to a profile, you should be able to add combination keys such as A+B or A+B+C. This should not be supported via binding A, B and C separately to the same aliased host key. Combination keys solve several problems: They work around issues with limited keyboard rollover, and they also let you easily do frame-perfect multi-button inputs for older games designed for two-button or three-button controllers that probably would have put those functions on separate buttons if they had them available. And it's nice as a convenience even for newer games; modern fighting games with multi-button inputs usually let you bind combination keys within the game itself. The combination keys should support not just combinations of "face buttons" like A, B, C, but also digital/analog directional inputs.

Conversely, you should be able to bind combinations of host keys to a single function. This is most useful for modifier keys. For example, I want my left/right/up/down arrow keys to bind to the emulated dpad buttons but I want shift + left/right/up/down to bind to quicktap versions of those emulated buttons, so I can do precision movement by holding shift. But this feature should not disallow you from binding host modifier keys on their own, e.g. a useful key cluster on a qwerty keyboard is shift/z/x/c since shift does not have any key rollover conflicts. To support binding these, when prompting for bindings I recommend the following logic:

  1. Wait for first key press event.
  2. Record which keys are pressed.
  3. Wait until all pressed keys are released.
  4. Bind to the combination of all keys that were pressed.

This is basically what MAME does except MAME replaces step 3 with a fixed multi-second timer; this makes the process feel too laggy for my taste when you want to quickly rebind all your controls.

As for the UI, the default process of binding keys should prompt you to press the buttons for each binding in turn, including any custom buttons added to the game's profile for autofire, quicktap and combinations. You can support binding keys individually, but it must support a way to quickly bind everything at once. If your emulator frontend otherwise uses native UI with dropdown menus and so on, strongly consider adding a way to rebind all keys that solely relies on an overlay that works in full screen mode and doesn't require a mouse or keyboard. A nice addition is to have an easy way to bind keys for a given external controller. For example, if I plug in a new external controller, maybe I can press and hold the start and select/back buttons for a few seconds and it will bring up an overlay that lets me bind buttons for that controller; when using this controller-specific binding mode, it shouldn't consider input events from other sources for the button binding prompts, as other players may be pressing buttons at the same time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.