WWDC Quick Look 💓 By SwiftGGTeam
Explore game input in visionOS

Explore game input in visionOS

观看原视频

Highlight

visionOS 游戏输入有三个层级:系统手势(默认选择)、组合手势(更精细控制)、自定义手势(ARKit hand tracking,仅 Full Space 可用),物理外设作为补充。


核心内容

做 visionOS 游戏时,输入方式的选择直接决定玩家能不能”拿起来就玩”。Apple Vision Pro 上,人们用眼睛和手来操作一切,但”手”的用法有两种:direct(伸手直接触碰虚拟物体)和 indirect(看一眼目标,手指捏合远距离操作)。Direct 适合高能量游戏,但长时间举手会很累;indirect 允许手放在身侧,小动作放大为大操作,更适合长时间游玩。Session 里 Loona 拼图游戏就是 indirect 的典型——看着拼图块捏住、拖动、双指轻点归位,站着坐着都能玩。Super Fruit Ninja 则走 direct 路线,直接挥砍飞来的水果,配合闪电拖尾和爆汁音效弥补没有 haptics 的缺憾。

Apple 的建议很直接:系统手势应该是默认选择。玩家已经会用系统手势操作整个平台,不需要你教;不需要额外设备,开箱即用;从 Shared Space 到 Full Space 全部支持。如果系统手势不够用,可以用 simultaneously(with:) 把多个手势组合起来。只有当系统手势和组合手势都无法满足需求时,才考虑用 ARKit 的 hand skeleton tracking 做自定义手势——但自定义手势只能在 Full Space 里使用,Shared Space 下 ARKit 不可用。物理外设(Xbox、PlayStation 手柄、蓝牙键盘/触控板)适合需要同时两个以上输入、或从其他平台移植的游戏,GameController 框架在 iOS、iPadOS、macOS、visionOS 上完全一致。

详细内容

系统手势:最简单的起点

visionOS 提供的系统手势包括:单次 tap、双次 tap、pinch and hold、pinch and drag,以及双手缩放和旋转。要让 RealityView 里的实体响应 tap,需要两步:给实体添加 InputTargetComponentCollisionComponent,然后给 RealityView 挂上 tap 手势。 (05:16

struct ContentView: View {
  var body: some View {
    RealityView { content in
      // For entity targeting to work, entities must have an InputTargetComponent
      // and a CollisionComponent!
    }
    .gesture(TapGesture().targetedToAnyEntity().onEnded { value in
      print("Tapped entity \(value.entity)!")
    })
  }
}

关键点:

  • InputTargetComponent 让实体成为可交互目标
  • CollisionComponent 为实体提供碰撞检测范围
  • targetedToAnyEntity() 表示 RealityView 中任何实体都可触发
  • onEnded 闭包在 tap 结束时执行,value.entity 拿到被点击的实体

组合手势:拖动 + 缩放 + 旋转同时进行

simultaneously(with:) 可以让多个手势同时生效。Session 演示了一个卫星操作场景:拖动移动、捏合缩放、手腕旋转同时进行,系统在每个手势发生时即时识别。 (07:08

var manipulationGesture: some Gesture<AffineTransform3D> {
  DragGesture()
    .simultaneously(with: MagnifyGesture())
    .simultaneously(with: RotateGesture3D())
    .map { gesture in
      let (translation, scale, rotation) = gesture.components()

      return AffineTransform3D(
        scale: scale,
        rotation: rotation,
        translation: translation
      )
    }
}

关键点:

  • simultaneously(with:) 让多个手势并行检测,前一个手势未结束时也能识别新手势
  • gesture.components() 解构出平移、缩放、旋转三个分量
  • AffineTransform3D 把三个变换合成为一个统一的 3D 仿射变换
  • SpatialEventGesture 提供更低层的访问,可追踪 Tap Begins/Moves/Ends,适合双手同时操作两个不同目标

自定义手势:ARKit hand skeleton tracking

自定义手势需要 Full Space。Shared Space 下 ARKit 不可用,这是硬限制。Blackbox 游戏是混合使用系统手势和自定义手势的范例:自定义手势用来创建新气泡(进出关卡),系统手势用来点击气泡。Session 演示了一个”双手指尖画圈”的自定义手势。 (09:33

let leftHandIndexFingerTip = leftHandAnchor.skeleton.joint(named: .handIndexFingerTip)
// ...
let leftHandIndexFingerTipWorldPosition = matrix_multiply(leftHandAnchor.originFromAnchorTransform,
                leftHandIndexFingerTip.anchorFromJointTransform).columns.3.xyz
// ...
let isCircleShapeGesture = indexFingersDistance < 0.04 && thumbsDistance < 0.04
if isCircleShapeGesture {
  // respond to gesture
}

关键点:

  • skeleton.joint(named:) 获取指定关节的锚点
  • originFromAnchorTransformanchorFromJointTransform 相乘,得到关节在世界坐标系中的位置
  • .columns.3.xyz 从 4x4 矩阵提取平移分量(世界坐标 xyz)
  • 双手食指尖距离和拇指尖距离均小于 4 厘米时,识别为 circle 手势
  • 自定义手势必须有视觉和音效反馈,让玩家知道自己在正确执行

游戏手柄:GameController 框架

visionOS 支持与 iOS/macOS 相同的游戏手柄,包括 Xbox Series X 和 PlayStation DualSense。检测手柄连接用 GCControllerDidConnect 通知。 (14:00

NotificationCenter.default.addObserver(
    forName: NSNotification.Name.GCControllerDidConnect,
    object: nil, queue: nil) { note in
      guard let _controller = note.object as? GCController else { return }
      _controller.physicalInputProfile[GCInputButtonA]?.valueChangedHandler = {
        //...
      }
    }

关键点:

  • GCControllerDidConnect 通知在游戏启动时也会触发已连接设备,不会漏掉
  • physicalInputProfile[GCInputButtonA] 通过键路径访问具体按钮
  • 可以设置 valueChangedHandler 回调,也可以主动 poll 按钮状态
  • 还需要监听 GCControllerDidDisconnect,手柄断开时回退到手势输入

在 visionOS 2 中,还需要给 RealityView 添加 .handlesGameControllerEvents(matching:) 修饰符,表示这个视图接收手柄输入。 (14:24

struct ContentView: View {
  var body: some View {
    RealityView { content in
      // Tag your RealityView to respond to controller input events.
    }
    .handlesGameControllerEvents(matching .gamepad)
  }
}

关键点:

  • .handlesGameControllerEvents(matching:) 让 RealityView 接收指定类型的控制器事件
  • .gamepad 匹配标准手柄输入,还有 .extendedGamepad 等选项
  • 这是 visionOS 2 新增的必要步骤,不加这个修饰符手柄输入不会路由到你的视图

输入方式与空间类型的对应关系

输入方式Shared SpaceFull Space
系统手势支持支持
游戏手柄/键盘/触控板支持支持
自定义手势(ARKit)不可用支持

无障碍

无论使用哪种输入方式,都要为不同能力的玩家提供替代方案。Synth Riders 提供了单手模式选项。RealityKit 可使用原生无障碍框架,Unity 可用 Apple Accessibility 插件。

核心启发

  • 做什么:为新项目优先实现 indirect input + 系统手势。为什么值得做:indirect input 让手放在身侧就能操作,长时间游玩不累;系统手势零学习成本,玩家拿起就用。怎么开始:给实体加 InputTargetComponentCollisionComponent,挂 TapGesture().targetedToAnyEntity(),确保 Shared Space 和 Full Space 都能运行。

  • 做什么:用 simultaneously(with:) 组合 Drag + Magnify + Rotate 手势。为什么值得做:3D 空间中的物体操作天然需要同时移动、缩放、旋转,组合手势让操作无缝衔接。怎么开始:按 Session 的 manipulationGesture 模式,用 .map 把三个分量合成为 AffineTransform3D

  • 做什么:为手柄输入做自动检测和切换,而不是让玩家去设置里翻找。为什么值得做:Wylde Flowers 的做法——手柄一连接就自动切换输入,这是玩家期望的体验。怎么开始:监听 GCControllerDidConnect / GCControllerDidDisconnect,连接时切换到手柄,断开时回退手势,RealityView 加 .handlesGameControllerEvents(matching .gamepad)

  • 做什么:为所有输入方式添加视觉反馈和音效。为什么值得做:visionPro 没有 haptics,视觉和音效是告诉玩家”你碰到东西了”的唯一途径。怎么开始:手势触发时播放音效(AVFoundation)+ 实体上播放视觉动画,手柄输入用 CoreHaptics 触发震动。

关联 Session

评论

GitHub Issues · utterances