在C/C++中自己编写键盘/输入系统

20

问题:

学习如何创建自己的输入/输出系统需要哪些资源?

我的理解:

我知道这取决于操作系统,因此让我们分别列出 Linux 和 Windows 的资源清单(如果可能的话)。对于 Linux,我猜测需要对 X Window 系统有相当了解。对于 Windows,我猜测需要使用 win32 API。但是,我猜测仅仅了解这些还不够,因为如果可能的话,我宁愿用 C++ 编写输入系统。

提问的原因:

我尝试阅读 OIS 源代码(因为这将用 CC++ 编写),但我不喜欢它的编写方式。因此,我决定学习如何为一个简单的乒乓球游戏(使用 C++ 编写)编写自己的键盘输入/输出系统。

2个回答

48
更新: 这是我写的一个处理键盘输入的库,使用 FreeBSD 许可证。我甚至已经将其标记为 v1.0,因此我认为它已达到“发布质量”。https://github.com/depp/keycode

我最近非常努力地为游戏制作这个东西,并且还没有完成。我将分享我所知道的。

按键码

对于游戏来说,按键码通常是你想要的。

当你在键盘上按下一个键时,操作系统首先将按钮按下转换为按键码。按键码指定了键位在键盘上的物理位置。例如,在美国键盘上,代码4可能对应于标有A的键(即使该键在法国或俄罗斯拥有不同的标签)。每个平台都有一组不同的按键码,或者可能有多个集合。你可能以其他名称来了解它们,例如扫描码虚拟按键码

  • Windows 使用虚拟按键码 (MSDN 文档)。它们在各种硬件和软件配置上都是稳定的。你可以在 <Winuser.h> 头文件中找到定义。在 Windows 上,如果你按下最左边的 home 行键(A 在美国,Q 在法国),你将得到代码65。

  • Mac OS X 有自 80 年代以来就一直稳定的按键码。它们在 <Carbon/Events.h> 中定义。你实际上不需要链接 Carbon 来使用按键码,但你需要这个头文件。在 OS X 上,如果你按下最左边的 home 行键,你将得到代码4。

  • Linux有几组不同的键码。因此,在Linux上,你有几个选择。你可以使用键符(它们有一些缺点,我将在下面解释),你可以假设用户正在使用特定的输入驱动程序(现在Evdev是一个非常好的猜测),或者你可以以某种方式找出机器使用的输入驱动程序。要获取键码,你必须读取键盘定义文件。例如,查看/usr/share/X11/xkb/keycodes/evdev来获取Evdev键码。使用Evdev时,如果按下最左边的Home行键,你会得到代码38。

  • 当然,如果跨平台的键码相同,那就太容易了。你可以使用特定于平台的键码或将其转换为独立于平台的值。我建议使用USB HID代码(pdf)作为独立于平台的代码,因为许多聪明的人已经经过协商并同意称呼每个键。

    我上面发布的库有针对每个平台的表格,例如用于将键码转换为USB HID代码的WIN_NATIVE_TO_HID

    艰难的部分是向用户说明他们应该按哪个按钮,但至少其他国家的人也可以玩你的游戏。

    字符代码

    尽管字符代码对于你和你的观众都居住在美国而言更易于使用,但你不希望使用它们。

    在将按钮按下转换为键码后,操作系统会将键码转换为字符代码。字符代码受当前键盘布局的影响,并且通常还受修改键的影响。

    所以,如果你在键盘上按下 A 键,在不同的平台上会得到 65、4 或 38 的键码。但是,如果 Shift 键按下,则会得到字符代码 'a''A';如果键盘布局设置为法语,则可能会得到 'Q';如果键盘布局设置为俄语,则可能会得到 'Ф'。因此,如果你将 WASD 编码进游戏中并使用字符代码,当其他国家的人玩你的游戏时,输入将完全失效。你必须在法国使用 ZQSD,在俄罗斯使用 ЦФЫВ,很快你就会头疼。
    我自己使用非 QWERTY 布局(Dvorak),大多数游戏都无法正常运行。在 W 键的位置是 ,,如果你按下 Shift 键,则变成 <,有些游戏不能将其识别为相同的键。例如,我按下 , 前进,但如果我在按下 Shift 键时释放该按钮,则游戏会认为我已经释放了 < 并认为 , 仍然被按下,所以我会一直向前移动。即使我切换到美国键盘布局(我认为这是 SDL 中的一个故障),大多数使用 SDL 的游戏在 Mac 上也无法正常运行。
    LibSDL
    SDL 2.0 提供了平台无关的键码,称为扫描码。使用 "SDL_scancode.h" 头文件。SDL 开发人员得出了相同的结论,即扫描码应该被转换回 USB HID 码,因此 SDL 扫描码与我上面发布的库完全兼容(参见 keycode.hSDL_scancode.h,数字值相同)。

    出于这个原因和其他原因,如果您正在使用SDL 1.2,我强烈建议升级到2.0版。


    这是我在提问中收到的最好的答案之一。你应该得到更多的赞和认可。不幸的是,这就是我能给的全部了。 - zeboidlund
    3
    实际上,这个答案对于Windows是错误的。VK_代码是独立于硬件的,但它们并不独立于布局。尝试将布局切换为法语,并查看哪个键用VK_Q响应。在Windows中,您需要使用扫描码。 - user519179

    1
    以下内容仅与在Windows上处理输入相关。
    正如capr在Dietrich Epp的答案评论中提到的,VK_代码会根据您使用的键盘布局语言而改变。因此,如果您的键盘布局为法语,并在QWERTY键盘上按下“Q”,则会产生“A”的虚拟键。如果您的布局是en-US等,则会产生“Q”的虚拟键,如预期所示。
    由于实际扫描码是硬件相关的(因此不可用),我通过将生成的扫描码转换为对应于en-US键盘布局的虚拟键来解决了此问题。然后可以按照Dietrich Epp所描述的方式将其转换为USB HID代码。无论布局语言如何,这都会产生相同的预期USB HID代码。
    将扫描码转换为en-US VK的方法,
    // Get and store the name of the currently used locale
    wchar_t defaultLayoutName[KL_NAMELENGTH];
    GetKeyboardLayoutName(defaultLayoutName)
    
    // Load and store a handle to the locale for en-US ("00000409")
    HKL defaultEnUSInputLocale = LoadKeyboardLayout(TEXT("00000409"), KLF_NOTELLSHELL);
    
    // Load the locale that was in use before so as not to change the keyboard layout of the user
    LoadKeyboardLayout(defaultLayoutName, KLF_ACTIVATE);
    
    --- (at windowProc) ---
    case WM_INPUT:
        // Use the handle to the en-US locale to translate the scan codes to VK_.
        virtualKey = MapVirtualKeyExW(scanCode, MAPVK_VSC_TO_VK_EX, defaultEnUSInputLocale);
    
        // translate virtualKey to USB HID by using e.g. the codes supplied by Dietrich Epp
        int8_t usbHIDkey = VK_TO_HID[virtualKey];
    

    请注意,在Windows上,与虚拟按键相关的一些额外障碍。我发现这个网站对此问题非常有帮助:https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接