WWDC Quick Look 💓 By SwiftGGTeam
Tap into virtual and physical game controllers

Tap into virtual and physical game controllers

观看原视频

Highlight

iOS 15 推出虚拟屏幕控制器 GCVirtualController,将触摸输入转换为游戏控制器输入,与现有 GCController 代码兼容;同时支持 PlayStation DualSense 控制器的自适应扳机效果。

核心内容

移动游戏控制的痛点

从主机平台移植到 iOS/iPadOS 的游戏通常设计为使用两个模拟摇杆、方向键和数字按钮。开发者反馈说,为这些游戏构建一致、手感好、外观优美的触摸输入很困难。触摸输入处理代码与键盘、鼠标和游戏控制器输入代码路径不同,增加了维护成本。(06:13

虚拟控制器的解决方案

Apple 推出了新的虚拟屏幕控制器,专门解决这些问题。这些屏幕控件:

  • 外观精美:针对不同手型抓握位置精心调校,响应迅速
  • 代码统一:虚拟控制器在代码中表现为 GCController 对象,与物理控制器使用相同的输入处理逻辑
  • 可定制:支持自定义符号、颜色和样式,同时保持 iOS/iPadOS 设计语言
  • 自适应布局:根据配置自动调整布局

虚拟控制器的核心原则是左右区域独立配置,布局由配置决定。每侧可以包含 0-4 个按钮,以及一个摇杆、方向键或触摸板。(07:22

Game Controller 框架基础

Game Controller 框架的目标是让开发者轻松支持 Apple 平台上各种高效、低延迟的输入方式。通过通用 API 抽象输入硬件,开发者只需编写一次输入代码,无需担心输入如何重新映射或不同控制器之间的差异。(00:40

在 iOS、iPadOS 和 tvOS 上,玩家可以创建系统级和每应用的游戏控制器输入重新映射,让游戏更可定制、更易访问。(01:04

详细内容

控制器连接和断开处理

处理控制器连接和断开的基本模式:(01:27

import GameController

// 监听控制器连接通知
NotificationCenter.default.addObserver(
    self,
    selector: #selector(controllerDidConnect),
    name: .GCControllerDidConnect,
    object: nil
)

// 监听控制器断开通知
NotificationCenter.default.addObserver(
    self,
    selector: #selector(controllerDidDisconnect),
    name: .GCControllerDidDisconnect,
    object: nil
)

@objc func controllerDidConnect(_ notification: Notification) {
    let controller = notification.object as? GCController
    // 设置输入处理或轮询控制器状态
}

@objc func controllerDidDisconnect(_ notification: Notification) {
    let controller = notification.object as? GCController
    // 清理控制器相关资源
}

关键点:

  • 控制器连接或断开时,系统创建或移除 GCController 对象
  • 发送 GCControllerDidConnectGCControllerDidDisconnect 通知
  • 相同模式适用于 GCKeyboardGCMouse 对象
  • 可以选择设置值变化处理器或轮询输入状态

创建虚拟控制器

创建虚拟控制器的步骤:(08:42

import GameController

// 创建配置对象
let configuration = GCVirtualControllerConfiguration()

// 设置左侧元素
configuration.leftThumbstick = true
configuration.leftButtons = [GCInputButtonOptions.a]

// 设置右侧元素
configuration.rightThumbstick = true
configuration.rightButtons = [
    GCInputButtonOptions.a,
    GCInputButtonOptions.b
]

// 创建虚拟控制器
let virtualController = GCVirtualController(configuration: configuration)

// 连接控制器,触发 GCControllerDidConnect 通知
virtualController.connect()

关键点:

  • GCVirtualControllerConfiguration 定义虚拟控制器的布局
  • 每侧可以配置摇杆、方向键或触摸板(三选一)
  • 每侧可以配置 0-4 个按钮
  • 调用 connect() 后,虚拟控制器表现为标准的 GCController 对象
  • 配置在创建时固定,可以显示和隐藏元素,但不能添加或删除

自定义按钮外观

自定义虚拟控制器的按钮外观:(09:17

// 获取按钮配置
if let buttonA = virtualController.controller?.extendedGamepad?.buttonA {
    // 创建自定义形状(如旋转攻击图标)
    let attackPath = UIBezierPath()
    attackPath.move(to: CGPoint(x: 0.5, y: 0))
    attackPath.addLine(to: CGPoint(x: 1, y: 1))
    attackPath.addLine(to: CGPoint(x: 0, y: 1))
    attackPath.close()

    buttonA.path = attackPath
}

if let buttonB = virtualController.controller?.extendedGamepad?.buttonB {
    // 创建跳跃图标
    let jumpPath = UIBezierPath()
    jumpPath.move(to: CGPoint(x: 0.2, y: 0.8))
    jumpPath.addLine(to: CGPoint(x: 0.5, y: 0.2))
    jumpPath.addLine(to: CGPoint(x: 0.8, y: 0.8))
    jumpPath.close()

    buttonB.path = jumpPath
}

// 隐藏按钮
if let buttonX = virtualController.controller?.extendedGamepad?.buttonX {
    buttonA.isHidden = true
}

关键点:

  • 通过 path 属性设置按钮的自定义形状
  • 使用 UIBezierPath 创建矢量图形
  • isHidden 属性可以显示或隐藏按钮
  • 自定义形状应该简洁清晰,适合快速识别

DualSense 自适应扳机

PlayStation DualSense 控制器的自适应扳机技术可以根据玩家行为动态应用不同的阻力,增强沉浸感。可以模拟拉动弓弦、弹弓在张力下的感觉等。(10:45

支持自适应扳机的步骤:(12:06

import GameController

// 获取当前控制器
guard let controller = GCController.current else { return }

// 验证是否为 DualSense 控制器
guard let dualSense = controller.physicalInputProfile as? GCDualSenseGamepad else {
    return
}

// 获取右自适应扳机
let rightTrigger = dualSense.rightAdaptiveTrigger

// 玩家瞄准时应用阻力
func updateSlingshotResistance(isAiming: Bool, pullAmount: Float) {
    if isAiming {
        if pullAmount < 1.0 {
            // 未完全拉回,应用反馈效果
            // FeedbackEffect 提供恒定阻力
            let feedbackEffect = GCAdaptiveTriggerFeedbackEffect(
                strength: pullAmount
            )
            rightTrigger.applyEffect(feedbackEffect)
        } else {
            // 完全拉回,应用振动效果
            // VibrationEffect 让扳机振动,模拟保持张力
            let vibrationEffect = GCAdaptiveTriggerVibrationEffect(
                strength: pullAmount,
                frequency: 0.5
            )
            rightTrigger.applyEffect(vibrationEffect)
        }
    } else {
        // 不瞄准时关闭效果
        rightTrigger.applyEffect(nil)
    }
}

// 玩家发射弹弓后关闭效果
func fireSlingshot() {
    rightTrigger.applyEffect(nil)
}

关键点:

  • 检查 physicalInputProfile 是否为 GCDualSenseGamepad 类型
  • rightAdaptiveTriggerleftAdaptiveTrigger 访问左右自适应扳机
  • GCAdaptiveTriggerFeedbackEffect 提供恒定阻力
  • GCAdaptiveTriggerVibrationEffect 让扳机振动
  • 使用完记得调用 applyEffect(nil) 关闭效果

App Store 标记和自定义

在 Xcode 中添加 Game Controllers capability 来标记应用支持游戏控制器:(03:10

  1. 打开项目设置
  2. 选择 Signing & Capabilities
  3. 添加 Game Controllers capability

这会在应用的 Info.plist 中添加游戏控制器能力,App Store 会为应用添加”控制器支持”徽章。应用也会被包含在游戏控制器偏好设置的每应用自定义部分。(03:22

UI 中的正确字形

在 UI 中显示正确的按钮字形很重要。不同控制器的按钮布局不同:

  • MFi 或 Xbox 控制器:显示 Y 按钮
  • PlayStation 控制器:显示三角形按钮

即使玩家在系统重新映射 UI 中重新映射了按钮,也应显示正确的字形。(03:36

// 获取与动作关联的按钮
guard let buttonA = controller.extendedGamepad?.buttonA else { return }

// 获取按钮的 SF Symbols 名称
let symbolName = buttonA.sfSymbolsName

// 创建系统图像
if let symbolName = symbolName, let image = UIImage(systemName: symbolName) {
    // 在 UI 中显示图像
    buttonImageView.image = image
}

关键点:

  • sfSymbolsName 属性返回按钮的系统符号名称
  • 使用 UIImage(systemName:) 创建图像
  • 这样会自动反映系统重新映射
  • 另一种方法是显示位置关系(如”上方按钮”)而非具体按钮名称

Share 按钮和屏幕捕获

支持 Share 按钮的控制器可以让玩家捕获屏幕和录制视频:(03:46

  • 双击 Share 按钮:捕获屏幕截图到相册
  • 长按 Share 按钮:开始和停止 ReplayKit 录制

iOS 15 新增了 15 秒高光片段功能。玩家可以开启自动后台缓冲,随时长按保存最后 15 秒的游戏画面。(13:31

可以在游戏控制器偏好面板中切换全局或每应用设置。如果想在游戏中的重要时刻触发自己的高光片段,有专门的 API 可用。

核心启发

  • 做什么:为移植游戏创建统一的控制方案

    • 为什么值得做:虚拟控制器让触摸输入表现为 GCController,与物理控制器使用相同的代码路径,减少维护成本
    • 怎么开始:创建 GCVirtualControllerConfiguration,设置左右两侧的摇杆和按钮,调用 connect() 连接控制器
  • 做什么:实现 DualSense 自适应扳机的沉浸感

    • 为什么值得做:自适应扳机可以根据游戏状态动态调整阻力,增强武器拉弓、弹弓拉拽等动作的真实感
    • 怎么开始:检查 physicalInputProfile 是否为 GCDualSenseGamepad,获取自适应扳机,应用 GCAdaptiveTriggerFeedbackEffectGCAdaptiveTriggerVibrationEffect
  • 做什么:支持控制器输入重新映射

    • 为什么值得做:玩家可以在系统设置中重新映射按钮,提升游戏的可访问性和可定制性
    • 怎么开始:在 Xcode 中添加 Game Controllers capability,使用 sfSymbolsName 属性显示正确的按钮字形
  • 做什么:集成 Share 按钮的屏幕捕获功能

    • 为什么值得做:玩家可以轻松捕获游戏精彩瞬间,分享给朋友,提升游戏传播度
    • 怎么开始:支持 ReplayKit API,在游戏重要时刻触发 15 秒高光片段录制
  • 做什么:为触摸设备设计可定制的控制布局

    • 为什么值得做:不同游戏需要不同的控制方案,虚拟控制器的灵活配置可以满足各种需求
    • 怎么开始:根据游戏类型选择合适的摇杆、方向键和按钮组合,自定义按钮形状提升可识别性

关联 Session

评论

GitHub Issues · utterances