Keyboard remapping – ShiVa Engine

Keyboard remapping

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.

International layouts

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.

Competing layouts

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.

Hardcoding keys

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 )

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 )

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 )

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" )
	-- ...

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" )
	-- 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" )
	-- ...

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
    log.message ( "CODE " ..kKeyCode .." (" ..")" )

Need more answers?

  • slackBanner