目录
- 1 问题
- 2 分析
- 3 物理布局
- 3.1 键盘上究竟有多少键
- 3.2 Windows 新三键
- 3.3 其它造型的 ANSI / ISO 键盘
- 3.4 韩国键盘
- 3.5 日本键盘
- 3.6 巴西键盘
- 3.7 不同键数键盘的兼容性问题
- 4 游戏 UI 设计
- 4.1 目标
- 4.2 实现
- 5 总结
3.7 不同键数键盘的兼容性问题
先介绍一个总的原则:在 Windows 中,系统通常不会去特意区分不同的物理键盘布局。
这是因为,键盘不过是个非常简单的硬件设备罢了。它仅有的能力,就是将用户按下和抬起按键的操作,转换为编码发送给操作系统*。例如,101/104-键键盘和 102/105-键键盘仅有的区别,是前者可以发送按下和抬起 0x31 一个键的信号,后者可以发送按下和抬起 0x32、0x64 两个键的信号。
*其实还有从操作系统接收命令,控制 Caps Lock、Scroll Lock 和 Num Lock 灯亮/灭的能力。这种能力相当于可以向外泄露信息,因此也被用于旁路攻击。
上表列出了各种物理键盘布局的「键数」和自己的「特色键」(均为 Windows 下)。由于系统不会去特意区分物理键盘布局,所以如果我们插入了一种键盘,却设置使用了一种不是为该键盘的物理布局设计的键盘/输入法的话,最多只是键盘上缺的那个键,我们无法录入而已。
不过,有两个键是特殊的,在考虑缺键的时候不用考虑,那就是 0x31 和 0x32 两个键。这两个键不会出现在同一个键盘上,而且它们的位置(在非规范的布局中又往往)互相可替换,因此,实践中,一般系统实现时都会将 0x31 和 0x32 键视作同一个按键,映射到软件键盘布局上。
也就是说,如果一个键盘布局原本要求你按 0x31 键,但是你的键盘只有 0x32,没问题,按它,系统会当作 0x31 键。反之,如果一个键盘布局原本要求你按 0x32 键,但是你的键盘只有 0x31,也没问题,按它,系统会当作 0x32 键。
举个例子。比如,硬件是 ISO 105 键盘,却设置使用了为 101/104-键键盘设计的键盘/输入法,例如美国键盘,理论上不会有任何问题。
尽管为 101/104-键键盘设计的键盘/输入法期望的是回车键上方的 0x31 键,而你的键盘上提供的实际上是回车键左边的 0x32 键,但 ISO 布局的 0x32 键会自动接替 ANSI 布局的 0x31 键,起到完全一样的作用。
ISO 布局中左 Shift 键右边的 0x64 键因为在 ANSI 键盘中不存在,所以按下这个键的时候,其实际作用可能和 0x31、0x32 键一样,也可能不一样或者完全没有反应。这取决于软件键盘布局如何设计。
总的来说,如果是在 ISO 105 键盘上使用为 ANSI 104 键盘设计的布局,不会有任何问题。相反,如果硬件是 ANSI 101/104-键键盘,却设置使用了为 ISO 102/105-键键盘设计的键盘/输入法,例如英国键盘,就会有问题:
由于「反斜杠、竖线」键不在了,我们无法输入命令行操作中两个至关重要的符号,使用场景会十分受限。这种现象,在老牌欧洲国家很常见。当地 ISO 键盘占绝对主流,这些键盘布局当初为 ISO 键盘设计,也没有理由做后续的更改,因此就一直流传下来。
相对而言,一些小国家,或者国际影响力没有那么大的国家,在设计键盘布局标准时,通常就会考虑基于 ANSI 键盘设计以减少麻烦。例如,中国直接采用 ANSI 键盘与美国键盘布局,韩国仅在 ANSI 键盘和美国键盘布局的基础上添加了「汉字转换」和「韩英切换」两个功能键。
或者,也可能采用同时兼顾 ANSI 键盘和 ISO 键盘的设计,即 ISO 优先,同时用 Alt Gr 照顾 ANSI 键盘的情况。例如前文提到过的土耳其语 F-键盘:
尽管土耳其语 F-键盘和法国键盘一样将大于号、小于号放在了左 Shift 键右边的 0x64 键上,它也在旁边提供了替代方案:Alt Gr + Shift + [J] 可以输入小于号,Alt Gr + Shift + [Ö] 可以输入大于号。0x64 键上的另一个符号「竖线」也可以通过 Alt Gr + [-] 来输入。
0x64 键上最后一个符号「断开的竖线」用 ANSI 键盘无法输入,但这个符号平常基本上用不到,而且也不会在编程语言中出现,所以没有就没有吧。总而言之,大多数情况下,缺键不会造成完全无法使用,但可能会让使用场景受限。例如缺少大于号、小于号使得命令行操作难以进行。
巴西键盘虽然不是 ISO 键盘,但它是基于 ISO 键盘设计,并且增加了自己独占的按键。可能是考虑到自身的国际影响力有限,巴西键盘在设计时也贯彻了「基于 ISO 键盘,兼顾 ISO 键盘」的理念。
对于没有右 Shift 键左边的那个 0x87 键的 ISO 键盘,巴西键盘允许用户用 Alt Gr + [Q]、Alt Gr + [W] 和 Alt Gr + [E] 分别输入斜杠、问号和度数符号。但是,如果你是用 ANSI 键盘的,那就没办法了,巴西键盘并不提供输入反斜杠和竖线的替代方案。
相对来说,微软韩语输入法和微软日语输入法对键盘的兼容性就非常好,二者都只需要 ANSI 键盘即可正常使用。不过,别忘了在输入法的设置里正确选择 101/102 键盘哦。设置之后,韩国键盘和日本键盘上的特殊键会全部或部分被 ANSI 键盘上就有的键取代,以实现正常操作。
了解了各种各样的键盘之后,咱们来看看,怎么才能在设计游戏 UI 的时候,让 UI 更友好,尽可能照顾到更多的玩家呢?
4 游戏 UI 设计
4.1 目标
首先,笔者想说的第一个设计原则是,如果一个游戏不允许玩家自定义键位,就不要使用 ANSI 键盘以外的键。同时,最好避免使用 0x31(ANSI 键盘的「反斜杠、竖线」)或 0x32 (ISO 键盘回车左下方的键)这种位置不定的键。看一个反面教材:
某日本游戏使用了大部分键盘不存在的「ろ」键和位置并不一定在那里的 0x32 键。用上「ろ」键的那一组操作(ツイスト回転),在大部分键盘上就无法正常进行。用上 0x32 键的那一组操作(パース変更),在 ANSI 键盘上操作起来就会有点别扭,因为「反斜杠、竖线」键离其它键很远。
如果这六个键能向左平移一格,兼容性就会好很多,而且不会成为反面教材。
第二个设计原则是,如果允许玩家自定义键位,请务必尽可能照顾更多的键盘布局的实际情况。例如同样的释放终极技能按键,《守望先锋》在美国键盘下会显示 Q,法国键盘下会显示 A,土耳其 F-键盘下会显示 F,照顾到了大多数常用的非 QWERTY 拉丁字母键盘。
《守望先锋》不仅能够在游戏中的界面根据当前键盘布局自适应显示正确的字母,也可以在键位设置界面根据当前键盘布局自适应显示正确的字母。例如,我们将键盘布局设置为土耳其 F-键盘,然后进入设置界面的话:
可以看到《守望先锋》自动以土耳其 F-键盘来显示所有按键。不过,这里有个小错误:「向后」和装填弹药实际上分别映射到 İ 键和 I 键(相当于 ANSI 的 S 键和 R 键),但是在这里显示的都是同样的 I。可能是字体的问题吧?或者是什么奇怪的 bug。
那么问题来了,我们的游戏在运行时怎么才能知道 Windows 系统当前设置的键盘/输入法,以及这个键盘/输入法的实际布局呢?
4.2 实现
笔者首先想到的是 C# 的 CultureInfo 类,但是失败了。
在笔者实测中,无论笔者怎么切换输入法,CultureInfo 类的 CurrentCulture 总是返回“中文(中国)”。猜测:它返回的是区域与语言选项里的格式设置。未经证实,请勿轻信,总之不能用。
CultureInfo 的另一个 property,CurrentUICulture,无论笔者怎么切换输入法,总是返回“英语(美国)”。猜测:它返回的是语言选项里的 Windows 显示语言。同样未经证实,请勿轻信,总之也不能用。
用 Google 搜索之后,发现 https://yal.cc/csharp-get-current-keyboard-layout/ 提到一种用 Windows API 获取当前键盘布局的方法。经过实测,这种方法也对,也不对。
对的地方是 Windows 的 GetKeyboardLayout 函数确实可以获取键盘布局,但是原作者在后面 AND 了个 0xFFFF 是不对的。
GetKeyboardLayout 函数的返回值是 32 位整数,低 16 位表示的是当前语言,也就是“英语(美国)”、“中文(中国)”这样的。例如当前键盘/输入法为土耳其语 Q-键盘和土耳其语 F-键盘时,GetKeyboardLayout 函数的返回值分别为 0x041f041f 和 0xf014041f,低 16 位是相同的。
因此,如果用 AND 0xFFFF 取低 16 位的话,只能精确到当前语言,无法获得到具体的键盘布局。对于键盘布局单一的语言,可能问题不大。但对于有多种键盘布局的语言(如土耳其语有 Q-键盘和 F-键盘),只看低 16 位就不靠谱了。正确的代码应该是用如下代码取高 16 位即可:
int keyboardLayout = (GetKeyboardLayout(foregroundProcess).ToInt32() >> 16) & 0xFFFF;
实测,试了一些不同的键盘布局,高 16 位都不一样。如果不放心,可以用整个 32 位整数做判断,肯定不会错,而且还能减少位移运算和按位与运算。
需要指出的是,这个函数只能获取键盘布局,不能获取输入法。也就是说,如果你把当前语言切换到中日韩等使用输入法而非键盘布局的语言,这个函数总会返回与输入法无关的结果。例如,当前输入法是搜狗拼音 / 微软拼音,函数的返回值都是 0x08040804。但这已经足够满足我们的需求。
还有一个可以改进的地方是,原作者的代码是获取用户正在操作的窗口(foreground window)而不是获取自己的窗口。如果原作者的目的是实现一个屏显虚拟键盘,那么获取用户正在操作的窗口的键盘布局是没错的。
但是,我们的目标如果只是在游戏中正常显示按键的话,可以只获取当前窗口的键盘布局。方法是,把 GetForegroundWindow 函数调用去掉,直接写:
uint foregroundProcess = GetWindowThreadProcessId(IntPtr.Zero, IntPtr.Zero);
即可获得自己的窗口的键盘布局。
获取到键盘布局之后,笔者遇到了一个新问题:没有找到从 Windows 系统直接获取键盘布局上每一个键是什么内容的函数。这样的函数应该是存在的,因为 Microsoft Keyboard Layout Creator 就有这个从系统中读取现有的键盘布局并加以编辑的功能。
不过,貌似暴雪也没有从系统获取键盘布局的内容。暴雪似乎是在游戏中内置了若干常用键盘布局备用。因为,当我们设置一些非常罕见的键盘(比如阿塞拜疆键盘)时,《守望先锋》就无法辨识它,并且依然按照 QWERTY 来显示各个键位的字符。
事实上,有的时候直接从 Windows 加载键盘布局也不一定是特别好的方法。比如印度键盘,直接买来可能是这个样子的:
对于这种键盘,如果直接像 MSKLC 那样从系统读取键盘布局的话,读出来的就不是拉丁字母,而是天城文了。游戏要面对正确显示天城文的问题,这可不是一件简单的事情,吃力不讨好。不如,直接按照 ANSI 键盘显示英文字母和符号,只要玩家低头看键盘能找到那个字母,就万事 OK。
可能这也是暴雪要自己在游戏里维护一份键盘布局来显示的原因吧。所以,就这样吧。这个探索就到此为止了。
简单来说,关于这个问题,笔者的建议是,找一些常用的键盘布局,加到游戏里,支持正常显示它们就可以了。
另一方面,即使真的能找到 Windows 里加载键盘布局内容的函数,也因为要显示多种文字存在技术问题,笔者并不推荐直接将布局内容在游戏中显示出来。函数获取的内容最好是作为开发者维护游戏中的键盘布局的一个辅助工具,例如读出阿塞拜疆键盘并加到游戏里,供后续正常显示即可。
5 总结
1、不同的物理键盘布局有很多种。常见的有 ANSI(101/104-键)、ISO(102/105-键)、韩国(103/106-键)、日本(106/109-键)、巴西(104/107-键)。均为 Windows 键盘的布局。
常见的键盘物理布局。0x31 个 0x32 被系统视作同一按键
2、设计交互时,应避免使用 ANSI 键盘上没有的按键。
3、由于每个按键的实际作用是硬件和软件共同决定的,因此,如果允许用户自定义键位,游戏应当获取系统当前的键盘布局,并根据键盘布局显示按键上正确的字符。
好有意思的文章
我是雷锋,第一篇传送:https://indienova.com/indie-game-development/keyboard-design-and-ui-1/
非常感谢作者的整理分享,本人从事嵌入式键盘开发,文中提到的MSKLC在其他地方很少被提及,意外的给我很大的帮助,文章由浅入深通俗易懂,值得点赞