WWDC Quick Look 💓 By SwiftGGTeam
Explore spatial accessory input on visionOS

Explore spatial accessory input on visionOS

观看原视频

Highlight

Amanda Han 用一个数字雕刻应用演示了空间配件的完整开发流程。


核心内容

visionOS 一直坚持眼睛加双手的输入范式。但雕刻、CAD、绘画、隔空击球这类场景,靠捏合手势始终差一点:缺少按钮,缺少压感,缺少触觉反馈,更缺少笔尖那种确定的物理边界。开发者想做精细创作工具,只能眼看着手势 API 在毫米级精度上掉链子。

WWDC25 给出了答案:visionOS 26 正式支持两款空间配件——PlayStation VR2 Sense 控制器和 Logitech Muse。前者带按钮、摇杆、扳机,适合游戏(Resolution Games 的 Pickle Pro 已经能在空间里打匹克球);后者笔尖和侧键带压感传感器,外加触觉反馈,专为生产力和创意场景设计(01:22)。两类配件都能在 shared space 与 full space 中使用,由 Apple Vision Pro 摄像头加配件自身的惯性传感器联合追踪。

Amanda Han 在 session 里用一个数字雕刻 app 把整条链路串起来:用 GameController 框架建立连接,用 RealityKit AnchorEntity 把虚拟刀头锚到笔尖,用 SpatialTrackingSession 拿到 transform 来切割虚拟黏土,用 haptics engine 播放每一刀的触感反馈,最后从 ARKit AccessoryAnchor 拿到「左手还是右手握」这种附加信息,让工具栏自动避让握持手。


详细内容

工程配置

第一步在 Xcode capabilities editor 里勾选 Spatial Gamepad,并在 plist 的 Accessory Tracking Usage 字段填写用途说明(例如「Tracking accessory movements to carve into virtual clay」),用户首次启动会看到这段描述的授权弹窗(03:11)。

发现配件连接

GameController 框架里能携带空间追踪能力的两个类是 GCControllerGCStylus,都遵循 GCDevice 协议。注册连接通知后,必须用 productCategory 二次校验是否为空间配件(04:56):

// Check spatial accessory support

NotificationCenter.default.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: nil) {
  notification in
    if let controller = notification.object as? GCController,
       controller.productCategory == GCProductCategorySpatialController {

    }
}

关键点:

  • GCControllerDidConnect:监听控制器接入事件;笔类配件用 GCStylusDidConnect
  • notification.object as? GCController:把通知里的对象拆包成具体设备。
  • productCategory == GCProductCategorySpatialController:判断这台 controller 支持空间追踪;不是所有 game controller 都支持。Stylus 对应的常量是 GCProductCategorySpatialStylus
  • 断连用对称的 GCControllerDidDisconnect / GCStylusDidDisconnect,要在回调里清理虚拟内容,避免出现「幽灵刀头」。

把虚拟内容锚到配件

每款配件给出自己的命名锚点:PS VR2 Sense 暴露 aimgripgrip surface;Logitech Muse 只暴露 aim。雕刻 app 选择 aim06:34):

// Anchor virtual content to an accessory

func setupSpatialAccessory(device: GCDevice) async throws {

    let source = try await AnchoringComponent.AccessoryAnchoringSource(device: device)

    guard let location = source.locationName(named: "aim") else {
        return
    }

    let sculptingEntity = AnchorEntity(.accessory(from: source, location: location),
                                       trackingMode: .predicted)

}

关键点:

  • AccessoryAnchoringSource(device:):从 GCDevice 抽出可锚定源。
  • locationName(named: "aim"):拿配件自定义的锚点;如果配件不支持该位置,返回 nil 直接退出。
  • AnchorEntity(.accessory(from:location:), trackingMode:):把 AnchorEntity 钉到该锚点。
  • trackingMode: .predicted:用预测模型补偿渲染延迟,剧烈动作可能过冲(视频里第 4 帧紫色预测框偏离灰色真实框);需要精确坐标的场景换成 .continuous,延迟更高但不会过冲(07:46)。

拿到 transform:SpatialTrackingSession 加 .accessory

去年的 SpatialTrackingSession 可以为已配置的 AnchorEntity 提供 transform,今年新增了 .accessory 配置项(09:01):

// Get in-app transforms

let session = SpatialTrackingSession()

let configuration = SpatialTrackingSession.Configuration(tracking: [.accessory])

await session.run(configuration)

关键点:

  • SpatialTrackingSession():建立追踪会话。
  • Configuration(tracking: [.accessory]):声明要追踪空间配件类型;可以同时跟其他类型组合。
  • await session.run(configuration):启动会话。运行起来后,雕刻 app 才能在每帧从 anchor entity 读到刀头的世界坐标,进而判定是否切到虚拟黏土。

给雕刻加触感

切到黏土时同步播放触觉反馈,沉浸感立刻不一样(09:48):

// Add haptics to an accessory

let stylus: GCStylus = ...

guard let haptics = stylus.haptics else {
    return
}

guard let hapticsEngine: CHHapticEngine = haptics.createEngine(withLocality: .default) else {
    return
}

try? hapticsEngine.start()

关键点:

  • stylus.haptics:不是每个配件都有触觉,先判空。
  • createEngine(withLocality: .default):基于 CHHapticEngine,触觉模式的设计可以参考 Advancements in Game Controllers。
  • try? hapticsEngine.start():启动后就能在切割回调里 fire 一段 haptic pattern。

从 RealityKit 桥接到 ARKit AccessoryAnchor

RealityKit AnchorEntity 拿不到「左手还是右手握」、「相对运动」、「追踪质量」这些状态。今年开放了 ARKitAnchorComponent,只要 SpatialTrackingSession 在跑,就能从 entity 取到对应的 ARKit anchor(11:54):

// Access ARKit anchors from AnchorEntity

func getAccessoryAnchor(entity: AnchorEntity) -> AccessoryAnchor? {
    if let component = entity.components[ARKitAnchorComponent.self],
       let accessoryAnchor = component.anchor as? AccessoryAnchor {
        return accessoryAnchor
    }
    return nil
}

关键点:

  • entity.components[ARKitAnchorComponent.self]:从 entity 上取出桥接组件。
  • component.anchor as? AccessoryAnchor:拆包成 AccessoryAnchor,里面有 heldChirality(哪只手)、相对运动、相对旋转、追踪状态四个属性(10:43)。
  • 雕刻 app 用 heldChirality:左手握就把工具栏放到正 x 方向,右手反之,没握住就不偏移(12:35)。

走纯 ARKit 的路径

如果 app 用自定义渲染,可以绕开 RealityKit:用 GCStylusGCController 创建 Accessory,再用 accessory tracking provider 订阅 anchor 更新。配件接入或断开时要用新的配置 re-run ARKit session,否则会拿到失效的 anchor(14:06)。


核心启发

1. 在创作类 app 里加一个「空间配件模式」

为什么值得做:visionOS 26 的笔类配件 Logitech Muse 自带压感和触觉,是数字绘画、3D 雕刻、CAD 标注的天然输入。比手势精度高一个数量级,并且物理握持能让长时间操作不疲劳。

怎么开始:从勾选 Spatial Gamepad capability 入手,监听 GCStylusDidConnect,把当前手势工具的 hit-test 入口替换成 anchor entity 的 transform。先做一支虚拟笔尖跟着真实笔走,再叠加压感映射到笔触粗细。

2. 给游戏加一个「双握控制器」分支

为什么值得做:PS VR2 Sense controller 在 visionOS 上能像 sport racket 一样追踪。已有的 visionOS 游戏只要支持双手 controller anchor,就能从「捏合点击」升级成「实体挥拍」级别的体感。

怎么开始:实现 productCategory == GCProductCategorySpatialController 分支,挂两个 AnchorEntityaim 锚点;旧的捏合输入保留作为 fallback,让没买配件的玩家依然能玩。在 App Store 配上「Spatial game controller support」徽章。

3. 在 full space 沉浸应用里隐藏上肢和系统 UI

为什么值得做:使用配件的画面里,玩家想看到虚拟工具而不是自己的手腕和真实笔身;同时 home indicator 会破坏沉浸感。

怎么开始:用 .persistentSystemOverlays(.hidden) 隐藏 home indicator,用 .upperLimbVisibility(.hidden) 隐藏上肢和配件本体。在 SwiftUI view 上挂 .receivesEventsInView,让同一个视图同时接受 spatial event gesture 和 game controller 事件,无需为按钮和手势写两套逻辑(15:54)。

4. 测量类需求改用 Continuous 模式或 metric anchor

为什么值得做:.predicted 模式在快速动作下会过冲。CAD 标注、空间测量、医疗示意这类场景,过冲就是误差。

怎么开始:渲染层和「快速命中检测」继续用 .predicted;真正写入坐标的笔画或测量值切到 .continuous,或直接用 ARKit 的 metric anchor API(参考 Coordinate spaces 文档)。


关联 Session

评论

GitHub Issues · utterances