探索 visionOS 的游戏输入
Explore game input in visionOS
2024年6月10日
一句话判断
visionOS 游戏输入的最佳策略是优先使用系统手势(间接输入为主),只在必要时才用 ARKit 自定义手势或外设——能让人拿起就玩的手势设计才是 spatial computing 游戏的王道。
这场 Session 讲了什么
这场 Session 全面梳理了 visionOS 上可用的游戏输入方式。visionOS 的输入分为两类:手势(gesture)和物理外设。手势又分为 direct(直接触摸虚拟物体)和 indirect(看一眼 + 手指捏合远距离操作)两种模式。Session 通过 Loona(拼图游戏)、Super Fruit Ninja、What the Golf、Blackbox 等实际游戏案例,展示了不同输入方式的设计选择。
核心观点很明确:系统手势应该是你的默认选择,因为用户已经熟悉、无需额外设备、在所有空间(Shared Space 到 Full Immersion)都能用。如果需要更精细的控制,可以把系统手势组合起来使用。只有当系统手势无法满足需求时,才考虑用 ARKit 的 hand skeleton tracking 自定义手势。物理外设(游戏手柄、键盘鼠标)作为补充选项。
值得深挖的点
Direct vs Indirect 输入的选择逻辑
visionOS 的 infinite canvas 意味着 direct input 需要大范围的手臂运动,长时间玩会很累。Indirect input 则允许用户把手放在身体附近,通过小幅度手指运动控制远处的对象——用 Session 的话说,“small hand movements amplified into big actions”。
Loona 拼图游戏选择了 indirect input:看一眼拼图块,捏合拿起,移动手指来定位,双击自动吸附。这让人可以坐着舒适地玩。Super Fruit Ninja 则选择了 direct input:切水果这种高能量游戏,直接伸手挥砍的体验是间接输入替代不了的。
Session 建议两种都支持,但 indirect input 应该是大多数游戏的默认选择,尤其是长时间游玩的场景。如果你选择 direct input,必须提供充分的反馈(视觉粒子效果 + 音效),因为 visionOS 没有触觉反馈(haptics),玩家需要通过视觉和听觉确认每一次交互。
手势组合与 SpatialEventGesture
系统手势可以两两组合:同时发生(simultaneously)或顺序发生(sequentially)。还可以和键盘修饰键组合。但最有意思的是 SpatialEventGesture——它提供了对手势事件的底层访问,比如 Tap Begins、Moves、Ends。这在双手同时操作不同对象时特别有用,因为你可以追踪每个动作的独立 target。
比如在一个卫星操控场景中,你可以同时用一只手拖拽移动卫星、另一只手旋转和缩放。SpatialEventGesture 能让每个手势独立追踪,即使前一个手势还没结束,新手势也能被识别。这种多任务并行操作是 spatial computing 独有的交互模式。
代码片段
让 RealityView 中的实体响应系统手势
场景:让 3D 对象可以点击交互。
// 实体需要两个组件才能响应手势
let entity = Entity()
// 1. 标记为可交互目标
entity.components.set(InputTargetComponent())
// 2. 添加碰撞组件
entity.components.set(CollisionComponent(shapes: [.generateSphere(radius: 0.1)]))
// 在 RealityView 上挂载手势
RealityView { content in
content.add(entity)
}
.gesture(
SpatialTapGesture()
.targetedToAnyEntity()
.onEnded { value in
// 处理点击
handleTap(on: value.entity)
}
)
坑:忘记添加 InputTargetComponent 或 CollisionComponent 是最常见的遗漏——两个缺一不可。
手势同时识别
场景:拖拽对象的同时允许旋转和缩放。
// Drag 手势 + 同时监听 Magnify 和 Rotate
entity.gesture(
DragGesture()
.simultaneously(with: MagnifyGesture())
.simultaneously(with: RotateGesture())
.onChanged { value in
// 移动、缩放、旋转可以同时进行
// 转换之间是无缝的
}
)
坑:不用 simultaneously(with:) 的话,新手势会等旧手势结束才被识别,多任务操作时会卡顿。
自定义手势(通过 ARKit Hand Tracking)
场景:系统手势无法满足需求时,用 hand skeleton tracking 自定义。
import ARKit
let session = ARKitSession()
let handTracking = HandTrackingProvider()
Task {
try await session.run([handTracking])
for await update in handTracking.anchorUpdates {
guard let hand = update.anchor else { continue }
// 访问每个关节的位置和方向
let thumbTip = hand.handSkeleton?.thumb.tip
let indexTip = hand.handSkeleton?.indexFinger.tip
// 根据关节位置判断自定义手势
}
}
坑:自定义手势需要你自己设计教程让用户学习,确保手势简单直觉,并加入视觉/听觉反馈让用户确认自己做得对。
最佳实践
已有项目:如果你的 visionOS 游戏已经使用系统手势,考虑添加 simultaneously(with:) 来支持多手势并行操作,并确保 indirect input 模式可用。如果游戏偏高能量(如音乐节奏、动作类),添加 direct input 支持。
新项目:从 indirect input 开始设计交互。系统手势 + 组合手势能覆盖绝大多数场景。只在有明确需求时才引入自定义手势或外设支持。无论用什么输入方式,视觉反馈和音效是必须的——visionOS 没有 haptics,这是你告诉用户”你碰到东西了”的唯一途径。
还有什么值得关注
- 所有系统手势在 visionOS 的每个空间(Shared Space 到 Full Immersion)都可用,不受空间类型限制。
- Unity 开发者通过 Unity Input System 接收手势事件,tap 数据以 World Touch Event 形式提供。
- Blackbox 游戏是混合系统手势和自定义手势的优秀案例:它用一个简单的自定义手势(伸开手掌创造泡泡)作为进入/退出关卡的主要交互,其余操作用系统手势。