Mac OS X 游戏如何接收低级键盘输入事件?

Posted

技术标签:

【中文标题】Mac OS X 游戏如何接收低级键盘输入事件?【英文标题】:How can Mac OS X games receive low-level keyboard input events? 【发布时间】:2011-11-13 00:22:06 【问题描述】:

游戏需要对键盘输入进行低级访问。在 Windows 上,有 DirectInput。但是 Mac OS X 游戏开发者使用什么技术呢?

显然,有足够多的 Mac 游戏可以恰到好处地输入键盘,而没有常见解决方案的缺点:

解决方案 #1:Use keyUp / keyDown events

-(void)keyUp:(NSEvent*)event;
-(void)keyDown:(NSEvent*)event;

不可接受的缺点: keyDown 事件会根据“按键重复率”和“按键重复延迟”的系统偏好设置重复。这会导致一个初始 keyDown 事件,然后在它开始以系统首选项设置定义的速率重复之前暂停。此方案不能用于连续的关键事件。

我想知道是否可以禁用按键重复行为?我想您可以读取第一个 keyDown 键,然后在您的类中记住“带有 keyCode x 的键已关闭”状态,直到收到该键的 keyUp 事件,从而绕过重复延迟和重复率问题。

解决方案 #2:Use Quarts Event Taps

见Quartz Event Services Reference。它似乎足够低级。

不可接受的缺点: 需要在通用访问 - 键盘下的系统首选项中启用辅助设备。虽然这可能默认开启,但不能保证它可能会在某些系统上关闭。

我还读到 Quartz 事件点击需要应用程序以 root 身份运行,但没有找到对此的确认。

解决方案 #3:Carbon Events / IOKit HID

Carbon Event Manager Reference 被标记为旧版,不应用于新开发。

不可接受的缺点:没有人知道未来的 Mac OS 版本会继续支持 Carbon 事件多长时间。

除了 Carbon 是一个遗留框架之外,这似乎仍然是最好的解决方案。但是使用 Carbon Events 还有其他缺点吗?

问题:

Mac OS X 游戏开发人员使用哪种技术来接收低级键盘输入事件?如果他们使用上述技术之一,他们如何解决我提到的缺点?

更新:

我最终转而使用常规的 NSEvent 消息并将它们包装在 neat API for polling the keyboard states 中。

【问题讨论】:

为什么 keyDown: 的重复行为不可接受?在获得 keyUp 之前,您不能忽略其他事件吗? 我真的不知道最好的解决方案是什么,但关于解决方案 #1 的缺点 - NSEventisARepeat 方法来确定事件是否是自动键重复的结果.您可以简单地忽略这些并假设该键被连续按下,直到您收到相应的keyUp: 事件。 我将此作为注释添加到解决方案 #1。我希望的是一个游戏开发者的框架,它只允许你调用像 isKeyDown:(UInt16)virtualKeyCode 这样的方法。 关于#2,在文档中找到了这个:新事件点击的位置。传递 Event Tap Locations 中列出的常量之一。只有以 root 用户身份运行的进程才能在 HID 事件进入窗口服务器的位置找到事件点击;对于其他用户,此函数返回 NULL。 developer.apple.com/library/mac/documentation/Carbon/Reference/… 关于#2,是的,我确认某些事件类型需要root权限。例如,key_down 和 key_up。我猜原因是他们不想让程序监控用户的按键输入。 【参考方案1】:

看看 ControllerMate 和 manymouse。

http://icculus.org/manymouse/

http://www.orderedbytes.com/controllermate/

【讨论】:

【参考方案2】:

我在#3 上运气不错,但如果你想支持键盘以外的任何东西,它需要做很多繁重的工作。

不过,在我们深入研究之前,请快速指出一点,Carbon 和 IOKit HID 是两个独立的东西。碳可能会在某个时候消失。但 IOKit HID 将继续存在,并在 10.5 中进行了一次不错的改造。

有关这些东西如何组合在一起的完整示例,请查看https://github.com/OpenEmu/OpenEmu/blob/master/OpenEmu/OEHIDManager.m。这只是其中的一小部分,因为其中还有其他文件。

你想要做的文档可以在http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html找到

同样,这不会很快消失,并且与 Carbon 和 Carbon Events 完全分开。

【讨论】:

【参考方案3】:

polkit(Obj-C 工具包)中也有一些设备类,包括...

HIDController 使用 USB HID 设备 AppleRemote 使用 Apple Remote MidiController 使用 Midi 设备 OSCController 通过 UDP 使用 OSC 兼容设备 SerialPort 使用任何串口设备 SC2004LCDModule 使用来自 Siliconcraft.net 的 SC 2004

http://code.google.com/p/polkit/

【讨论】:

【参考方案4】:

我找到了using Quartz Event Taps 的一个很好的例子。

但问题是:

    对于某些事件类型,用户需要以 root 权限运行应用程序。例如拦截 keydown 和 keyup 事件需要 root。

    拦截是系统范围的,这意味着当程序运行时,它将捕获所有关键事件,甚至那些发送到其他应用程序的关键事件。实施需要非常小心,以免破坏其他应用程序。

【讨论】:

【参考方案5】:

我参加这个聚会迟到了,但这是我使用 Swift 编写的 OSX 游戏的解决方案。这很简单,而且看起来效果很好。

首先你可以把这段代码放到接收键盘事件的控制器中:

var keysDown = Set<UInt16>()

override func keyDown(e: NSEvent) 
    keysDown.insert(e.keyCode)


override func keyUp(e: NSEvent) 
    keysDown.remove(e.keyCode)

然后,系统的其他部分可以确定特定键是否已关闭:

if (keysDown.contains(49)) 
    // space bar is down

或者循环遍历当前按下的所有键

for char in keysDown 

    switch char 

    case 49:
        // space
        player.jump()
    case 126:
        // up
        player.moveForward()
    case 124:
        // right
        player.turnRight()

        //   ... etc ...

    default:
        // during development I have this line so I can easily see which keys have which codes
        print(char)
    

注意 keysDown 是一个 Swift Set,所以你不必担心重复或排序。密钥要么在集合中,要么不在集合中。

我不太确定键码的标准化程度。但是您可以提供一个键盘配置页面,用户可以在其中为每个操作键入键,然后保存发生的任何键码。

【讨论】:

这样其实挺优雅的,但是不收集修饰键。 @2075 不,它没有。它不适用于不同类型的键盘和布局。对于国际比赛来说,这不是一个好的解决方案。 @Andreas 如果你能想出更好的解决方案,请分享:)【参考方案6】:

好的,所以我真的迟到了,但我认为我的观点直截了当,类似于上述霍华德先生的解决方案。请记住,此输入用于控制 SpriteKit 游戏中的相机。这样您就可以获得平稳的连续运动和精确的停止。

let moveRight: SKAction = SKAction.repeatForever(SKAction.moveBy(x: -5000, y: 0, duration: 1.5))
...

override func keyDown(with event: NSEvent) 
    camera!.constraints = [] // remove constraints usually added by a cameraComponent.

    let ekc = event.keyCode
    if ekc == 123  camera!.run(moveLeft, withKey: "moveLeft") 
    if ekc == 124  camera!.run(moveRight, withKey: "moveRight") 
    if ekc == 126  camera!.run(moveUp, withKey: "moveUp") 
    if ekc == 125  camera!.run(moveDown, withKey: "moveDown") 

    print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")



override func keyUp(with event: NSEvent) 
    let ekc = event.keyCode
    if ekc == 123  camera!.removeAction(forKey: "moveLeft") 
    if ekc == 124  camera!.removeAction(forKey: "moveRight") 
    if ekc == 126  camera!.removeAction(forKey: "moveUp") 
    if ekc == 125  camera!.removeAction(forKey: "moveDown") 

【讨论】:

以上是关于Mac OS X 游戏如何接收低级键盘输入事件?的主要内容,如果未能解决你的问题,请参考以下文章

覆盖应用程序的低级键盘挂钩问题

如何在 Mac OS X 中重新映射“上下文菜单”键?

在 Mac OS X 中模拟按键事件

OS X (macOS) 和低级 C(Objective-C 替代方案)中的 Applescript

对应Linux / dev / input的Mac低级键盘设备?

mac os开机密码忘了怎么改