Bring keyboard and mouse gaming to iPad
Graphics & Games 进阶 25m

将键鼠游戏体验带到 iPad

Bring keyboard and mouse gaming to iPad

2020年6月24日

在 Apple 官方观看视频

一句话判断

iPad 上的键鼠操作体验和传统 PC 不同——如果你在移植 PC 游戏到 iPad,这场 Session 会告诉你哪些坑必须避开。

这场 Session 讲了什么

这场 Session 详细讲解了如何在 iPadOS 上为游戏适配键盘和鼠标输入。iPadOS 13.4 引入了鼠标触控板支持,iPadOS 14 进一步增强了键鼠输入能力。对于游戏开发者来说,这意味着 iPad 可以成为一台便携的游戏设备,前提是你正确适配了输入方式。

演讲者首先介绍了键盘输入的处理。UIKey 和 UIKeyCommand 是处理键盘事件的标准 API,但游戏场景需要更低延迟的输入处理。Session 讲解了如何使用 UIEvent 的 keyboard 事件来捕获按键的按下和释放状态,这对于需要同时检测多个按键的游戏(如 WASD 移动)至关重要。

鼠标和触控板部分是 Session 的重点。iPad 的鼠标/触控板有”指针”概念——一个跟随手指移动的光标。但对于 FPS 或 RTS 游戏来说,你需要的是”直接模式”——鼠标移动直接映射到视角旋转,光标不显示。Session 讲解了如何使用 UIGestureRecognizer 的子类或直接处理 UIEvent 来实现这种直接映射。

最后讨论了如何同时支持触摸和键鼠两套输入方案,以及如何在 UI 中让用户选择偏好的输入方式。

值得深挖的点

  • Pointer Lock 模式的实现:FPS 游戏需要锁定鼠标指针,让鼠标移动量直接映射到视角旋转。在 iPadOS 上,这需要通过 UIPointerLockState 或自定义的手势识别器来实现。关键是处理好指针隐藏和恢复的时机。
  • 触摸与键鼠的并行设计:同一个游戏功能(如”移动”)在触摸上是虚拟摇杆,在键鼠上是 WASD。你需要维护两套并行的输入映射,并在用户切换输入方式时无缝过渡。这不仅是技术问题,更是 UX 设计问题。

代码片段

// 处理键盘按键的按下和释放
class KeyboardInputHandler {
    private var pressedKeys = Set<UIKeyboardHIDUsage>()

    func handleKeyEvent(_ event: UIEvent) {
        guard let key = event.allTouches?.first else { return }

        switch event.type {
        case .keyDown:
            pressedKeys.insert(key.key.keyCode)
            handleMovement()
        case .keyUp:
            pressedKeys.remove(key.key.keyCode)
        default:
            break
        }
    }

    // WASD 移动检测
    func handleMovement() {
        var direction = SIMD2<Float>(0, 0)

        if pressedKeys.contains(.keyboardW) { direction.y += 1 }
        if pressedKeys.contains(.keyboardS) { direction.y -= 1 }
        if pressedKeys.contains(.keyboardA) { direction.x -= 1 }
        if pressedKeys.contains(.keyboardD) { direction.x += 1 }

        if length(direction) > 0 {
            player.move(in: normalize(direction))
        }
    }
}
// 实现鼠标直接映射(Pointer Lock 模式)
class DirectMouseHandler: UIGestureRecognizer {
    private var lastPosition: CGPoint?

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)

        guard let touch = touches.first else { return }
        let currentPos = touch.location(in: view)

        if let last = lastPosition {
            let dx = Float(currentPos.x - last.x)
            let dy = Float(currentPos.y - last.y)

            // 直接映射到视角旋转
            player.rotateCamera(yaw: dx * sensitivity,
                                pitch: dy * sensitivity)
        }
        lastPosition = currentPos
    }
}
// 检测当前输入方式并切换 UI
func updateInputMode() {
    let hasHardwareKeyboard =
        GCKeyboard.coordinator?.keyboard != nil

    let hasMouse =
        GCMouse.coordinator?.mouse != nil

    if hasHardwareKeyboard && hasMouse {
        showKeyboardMouseUI()
        hideVirtualJoystick()
    } else {
        showTouchUI()
        showVirtualJoystick()
    }
}

// 监听输入设备变化
NotificationCenter.default.addObserver(
    forName: .GCKeyboardDidConnect, object: nil, queue: .main
) { _ in self.updateInputMode() }
NotificationCenter.default.addObserver(
    forName: .GCMouseDidConnect, object: nil, queue: .main
) { _ in self.updateInputMode() }

最佳实践

  • 键鼠和触摸两套输入方案都要完整实现,不要只做其中一套
  • FPS 类游戏的鼠标灵敏度提供可调节选项,不同用户偏好差异很大
  • 键鼠操作时隐藏虚拟摇杆和触摸按钮,释放屏幕空间
  • 支持自定义键位映射,PC 游戏玩家的键位偏好各不相同
  • 使用 UIKeyCommand 为菜单操作注册快捷键

还有什么值得关注

  • iPadOS 的键鼠支持后来在 iPadOS 16 中进一步增强
  • 搭配 “Advancements in Game Controllers” 一起看,可以覆盖 iPad 上所有的输入方案
  • 如果你在做跨平台游戏移植,键鼠适配只是第一步——还需要考虑性能优化和触屏降级
WWDC 2020