I used this trick for PyWeek, so I figured I’d share it.
I’m a dvorak user myself, and the fact that many game developers assume that people use QWERTY can often be a cause of frustration. People simply map their keys to WASD without regard for different keyboard layouts.
Fortunately, many professional games seem to handle it correctly, with even the help messages automatically displaying the right text (saying ‘press E to strafe right’ instead of 'press D to strafe right), which is what prompted me to look into how they do this.
Apparently, the Windows API at least provides a way for developers to map keyboard scan codes to virtual key codes, providing us with a way to know which virtual key is mapped to which physical keyboard key. This allows us to get the correct events to listen to in order for people not to have to reach all the way over their keyboards for basic walk controls. (for dvorak, it’s “,aoe”, for colemak, it’s “wars”, etc.)
Keep in mind that this is rough, mostly untested code.
if sys.platform == 'win32':
try:
import ctypes
except:
ctypes = None
print("ctypes import failed")
def map_vsc(vsc, default):
""" Map scan code to an event to listen to. The default argument is what is
returned if we don't have a way to map the scan code to an event name. """
if sys.platform == 'win32' and ctypes is not None:
vk = ctypes.windll.user32.MapVirtualKeyA(vsc, 1) # MAPVK_VSC_TO_VK
if vk > 0:
# OK, we've got the vk, now map it to a character.
char = ctypes.windll.user32.MapVirtualKeyA(vk, 2) # MAPVK_VK_TO_CHAR
if char > 0:
return chr(char).lower()
return default
Now, here’s an example that shows how to use it. The scan codes for WASD are 0x11, 0x1E, 0x1F and 0x20, respectively.
keybinds = {
'forward': map_vsc(0x11, default='w'),
'back': map_vsc(0x1F, default='s'),
'lstrafe': map_vsc(0x1E, default='a'),
'rstrafe': map_vsc(0x20, default='d'),
'jump': 'space',
}
self.accept(keybinds['forward'], self.on_forward)
... etc ...
The values for the scan keys are consecutive based on the physical location of the key on the keyboard; ‘Q’ is 16 (0x10), ‘W’ is 17 (0x11), ‘E’ is 18 (0x12) etc.
If I find time to do the equivalent for X11, I’ll post an updated snippet. The current Carbon-based renderer of Panda for OS X actually assumes QWERTY for the mapping, so it’s not an issue there.
In the long run, I’ll look into implementing a way for Panda3D to handle this kind of thing natively and I’ll probably make a blog post about it.