Few things are more infuriating to gamers than being unable to remap the keys on your keyboard to their preferences. Especially in the competitive gaming scene where muscle memory is key, forcing your own control scheme onto players who have been practicing their own mapping for ages can be the kiss of death for your title. Fortunately, remapping your keyboard controls in ShiVa is not difficult, although ShiVa’s keyboard control handlers do not make it too obvious how to do it efficiently without looping through all possible key constants.
Supporting different layouts
Keyboards can have many different configurations for a number of reasons. You can never rely on keys being in the same place, boards having the same number of keys, or even the same keys across all variations.
Keyboards in different countries have different layouts. The default top row of letters in English-speaking countries is QWERTY, in Germany it is QWERTZ, and in France it is AZERTY, just to name a few. That means, without any adjustments, the popular WASD keys for player movement will not work in many countries.
Additionally, a few completely different layouts compete with QWERTY, like DVORAK, Workman or JCUKEN for instance.
System and software specific keycodes
Not even the keycodes behind every key are standardized between the platforms. On IBM, our familiar QWERTY would translate to keycodes 17-22, while the ASCII codes would be 81, 87, 69, 82, 84 and 89.
Keyboard handlers and keycodes
ShiVa handles input via its two user handlers,
onKeyboardKeyDown ( kKeyCode ) onKeyboardKeyUp ( kKeyCode )
both of which forward their kKeyCode parameter to use in your script, which is a number that represents the value of the key that was just pressed/released. It does not represent the actual keycode reported by the system, since that would be quite useless for a multiplatform engine and system keycodes differ from platform to platform (see above). That’s what makes comparisons like this possible:
if kKeyCode == input.kKeyA then ... end
Key A will always have the same keycode to ShiVa (input.kKeyA = 0), no matter if you are working on AZERTY or QWERTY, even though the keycodes to the operating system will be different. Unknown keys will identify as keycode 255.
Most ShiVa samples were designed to show you a specific feature as fast as possible with as little code as possible. That means that all keyboard input is hardcoded, like so:
function CarGame_Main.onKeyboardKeyDown ( kKeyCode ) if ( this.hCar ( ) ) then if ( kKeyCode == input.kKeyA ) then object.sendEvent ( this.hCar ( ), "CarGame_Car", "onTurnLeft", true ) elseif ( kKeyCode == input.kKeyD ) then object.sendEvent ( this.hCar ( ), "CarGame_Car", "onTurnRight", true ) elseif ( kKeyCode == input.kKeyW ) then object.sendEvent ( this.hCar ( ), "CarGame_Car", "onGoFront", true ) elseif ( kKeyCode == input.kKeyS ) then object.sendEvent ( this.hCar ( ), "CarGame_Car", "onGoBack", true ) end end end
Users who do not have the WASD keys in the common cross configuration at the left side of their boards will have to fight with very awkward movement controls. Clearly, hardcoding keyboard input is not the way to go in a commercial release.
An action-based approach
If you hardcode your keys and bind them to actions, keys are assuming direct control of your character. What you need an additional layer of abstraction. Instead of being controlled by keys, you should think of your character being controlled by actions, like “move forward” or “turn left”, and your keys controlling these actions.
Mapping actions to numbers
We will map a number to every action in the game. Our example has only 4 movement actions, so our numbers will be 1-4 for the directions up/down/left/right plus 0 for inaction. The association of keycode->action will be stored in a table. Since input.kKeyA == 0 and unknown keys identify as keycode 255, you need a table that is as least 256 entries big.
local hT = this._keytable ( ) table.reserve ( hT, 256 ) for i=0, 255 do table.add ( hT, 0 ) end
By default, every 256 possible keys are not associated with any action (0). A common WASD association would look like this:
table.setAt ( hT, input.kKeyW, 1 ) table.setAt ( hT, input.kKeyS, 2 ) table.setAt ( hT, input.kKeyA, 3 ) table.setAt ( hT, input.kKeyD, 4 )
When a keyboard key is pressed, you just check the keycode against the table as an index (no loop required!) to see whether there is an action associated with that keycode.
function kbdRemap.onKeyboardKeyDown ( kKeyCode ) local hT = this._keytable ( ) local res = table.getAt ( hT, kKeyCode ) if res and res > 0 then user.sendEventImmediate ( this.getUser ( ), "kbdRemap", "onMoveCharacter", res ) end end
If the result of the table check is bigger than 0, you can send the result to the character movement function, something like:
function kbdRemap.onMoveCharacter ( nDirection ) if nDirection == 1 then log.message ( "up" ) elseif nDirection == 2 then log.message ( "down" ) elseif nDirection == 3 then log.message ( "left" ) -- ... end end
To remap an action to another key, you will simply have to set the old keycode index to 0 and assign the action to another index.
table.setAt ( hT, input.kKeyA, 0 ) -- formerly 1 table.setAt ( hT, input.kKeyQ, 1 ) -- new "up" action key
Keycode to Name String
So far, we have assigned keycodes to actions without telling the player about the mapping. However players will care little about ShiVa’s keycodes, the will want to know the name of the keys instead. Example: Quake 4 keybinding screen
Mapping keycodes back to actual strings is easy, but requires a bit of busy writing, since you essentially need to assign a string to every 102 (or so) keys individually. If you want to support multiple languages, you would have to do this for every language.
function kbdRemap.setupKeyCodes ( ) local hK = this._keycodes ( ) table.reserve ( hK, 256 ) for i=0, 255 do table.add ( hK, "unknown" ) end -- numbers table.setAt ( hK, input.kKey0, "0" ) table.setAt ( hK, input.kKey1, "1" ) -- ... table.setAt ( hK, input.kKey8, "8" ) table.setAt ( hK, input.kKey9, "9" ) -- letters table.setAt ( hK, input.kKeyA, "A" ) table.setAt ( hK, input.kKeyB, "B" ) table.setAt ( hK, input.kKeyC, "C" ) -- ... table.setAt ( hK, input.kKeyX, "X" ) table.setAt ( hK, input.kKeyY, "Y" ) table.setAt ( hK, input.kKeyZ, "Z" ) -- arrows - need for translation! table.setAt ( hK, input.kKeyUp, "Up" ) table.setAt ( hK, input.kKeyDown, "Down" ) table.setAt ( hK, input.kKeyLeft, "Left" ) table.setAt ( hK, input.kKeyRight, "Right" ) -- functions table.setAt ( hK, input.kKeyF1, "F1" ) -- ... table.setAt ( hK, input.kKeyF12, "F12" ) -- non-alpahnumeric - need for translation! table.setAt ( hK, input.kKeySpace, "Space" ) -- ... end
When this one-time setup is complete, you can query the name of the key just like you query the actions:
function kbdRemap.onKeyboardKeyDown ( kKeyCode ) local name = "unknown" local res = table.getAt ( this._keycodes ( ), kKeyCode ) if res then name = res end log.message ( "CODE " ..kKeyCode .." (" ..name ..")" ) end