The Quest for Custom Keymapping and International Keyboard Support
Normally, in regaining momentum for a project, I’ll pick it back up and work on small-ticket items just to get into the groove again, then work my way into the larger items. Though this time I have done the opposite, I finally have a handle on one of the larger features for version 0.4: custom keymapping.
One of the recurring pieces of feedback for Dance of Death was the inability to descend stairs when using non-English keyboards (Danish, Finnish, etc.). A quick glance over my code revealed no obvious mistakes: I was checking for character codes, as I should have been. This meant that, as long as Flash reported the correct character codes, the fact that a key was placed in another spot would be a non-issue.
Of course, Flash did not report the correct character codes.
Somehow, when using a Danish keyboard, Flash thought it was perfectly acceptable to report a [<] character code in a KeyboardEvent, when a TextField placed upon the stage displayed a [;], the correct character according to the Danish keyboard map.
Some further research revealed an ominous string in the Flash AS3 Language Reference for the KeyboardEvent Class:
Because mappings between keys and specific characters vary by device and operating system, use the TextEvent event type for processing character input.
WTF, Adobe? Not one of your brightest ideas.
After much trial-and-error, the solution emerged, and it looks something like this:
- Create a stage-wide TextField, add it to the stage, and give it focus.
- In addition to the already-in-place KeyboardEvents, add a listener to the TextField for TextEvents. This event’s text property contains a string of the last character entered into the TextField.
- Rewrite the key handler to decouple key presses from in-game actions.
- Store game actions and give them a default mapping that is either a string (for all keypresses that produce a string), or a keycode (for keypresses without character codes, those which do not produce strings). It is important that an action never match both the text and the keycode, or it may be called twice.
- In the KeyboardEvent handler, search through the actions that have keycodes set, find a matching one, and invoke a generalized input handling function with that action (in my case, I defined an Enum-style set of uint consts). Similarly, in the TextEvent handler, search through the actions that have text set, find the matching one, and invoke said generalized function with that action.
Sounds somewhat convoluted, but it feels like a reasonably elegant solution, given Flash’s inane keyboard input handling.
The remaining steps in wrapping up this three-part feature are: providing an interface to allow the user to customize keymapping, and saving/loading mappings across game sessions.
I like your project!
This issue gave me a huge headache when I first encountered it. Interesting workaround! Another relatively simple solution, I’ve discovered, is to not use the keyCode property of a KeyboardEvent, but rather do something like the following:
var sKey:String = String.fromCharCode(keX.charCode)
where keX is the KeyboardEvent that has just been dispatched. As far as I know, this will look directly at the input of the key just hit rather than at which key has just been hit.
Unfortunately, the event.charCode value from a KeyboardEvent is always the same, regardless of input language.
This is not a problem for [a]-[z] keys, but symbol keys are often radically different in keyboards with languages other than EN-US. Some of these symbols that are integral to roguelikes, like [<] and [>], are sometimes placed in entirely new keys that produce a charCode of 0 (take a look at the Danish keyboard map at the beginning of the article, the [<] [>] key is next to the [z]). The only way to accurately catch these is with a TextEvent!
I had a very complex plan to solve this problem for our latest project.
When I read that you just put a giant full screen text field over the whole stage I honestly started laughing out loud (almost maniacally!)
Brilliant solution, thanks!