WWDC Quick Look 💓 By SwiftGGTeam
Break into the RealityKit debugger

Break into the RealityKit debugger

观看原视频

Highlight

RealityKit Debugger 截取运行中 App 的 3D 快照,在 Xcode 里呈现 Entity Hierarchy、3D 视口和 Inspector 三栏布局,让你像用 View Debugger 调 UIKit 一样调试 spatial 应用。


核心内容

做 3D 应用最头疼的事情之一:代码里明明放了东西,跑起来却看不到、位置不对、形状变形。以前只能靠 print 和经验猜,RealityKit 的 Entity 层级和 Transform 继承关系错综复杂,肉眼根本看不出来哪里出了问题。

Apple 在 Xcode 里新加了 RealityKit Debugger。它的工作方式跟 View Debugger 一样:点击调试区的”Capture Entity Hierarchy”按钮,截取当前场景的 3D 快照,然后在 Xcode 里打开三栏视图——左侧 Entity Hierarchy 树状结构、中间 3D 视口、右侧 Inspector 面板。选中任何一个 Entity,三栏联动显示它的层级位置、空间位置和所有 Component 属性。

这场 Session 用一个”把植物园 App 改成机器人夜店”的真实项目,演示了四类最常见的 3D bug 怎么用这个工具定位:Transform 继承导致的形状变形、Component 忘记写回导致的行为异常、渲染管线问题导致的内容缺失、以及用自定义 Component 给 Debugger 加调试信息的方法。每一类都是 3D 开发里的高频问题,有了可视化工具之后排查效率完全不同。


详细内容

1. Transform 继承导致的变形

迪斯科球的线条看起来被压扁了。在 Debugger 里选中 Outline Entity,Inspector 的预览视口显示模型本身没变形,Transform 组件的 scale 也是均匀的 1。问题不在自己,就在祖先节点。逐级往上查:Background 正常,但 Support Entity 的 Transform 在 Y 轴上有一个很大的 scale 值——Support 被拉长来塑造形状没错,但它把变形传给了所有后代。

修复方法:把 Support 和 Background 从父子关系改成兄弟关系(06:50)。

// 修复前:Background 是 Support 的子节点,继承了 Y 轴 scale
support.addChild(background)

// 修复后:Background 和 Support 是兄弟节点,都在 discoBall 下
discoBall.addChild(background)

关键点:

  • support.addChild(background) 让 Background 继承了 Support 的 Y 轴 scale 变形
  • 改为 discoBall.addChild(background) 后,Background 只继承 discoBall 的 Transform,不再受 Support 影响
  • 两个 Entity 在场景里看起来仍然连在一起,但 Transform 不再相互干扰

2. Component 修改后忘记写回

传送系统应该不断生成机器人,但什么都没出现。在 Debugger 里检查 Teleporter Entity,Component 都在。再看 Teleportation Center 的 ControlCenterComponent,countdown 值一直等于初始值——说明 System 的 update 根本没生效。

回到代码发现问题:System 每次 update 都修改了 controlCenter.countdown,但修改后的 Component 没有写回 Entity(10:18)。

// TeleportationSystem.update 里遗漏的关键一步
// 5. Save updated component back to the entity
controlCenterEntity.components[ControlCenterComponent.self] = controlCenter

关键点:

  • RealityKit 的 Component 是值类型(struct),从 Entity 取出来修改后必须写回才能生效
  • 这跟 SwiftUI 里修改 @State 的思维习惯不同,容易遗漏
  • Debugger 捕获的是点击暂停那一刻的实时状态,countdown 值不变就说明 System 没有真正更新 Entity 上的数据

3. 渲染问题导致内容不可见

柜台上的 9 瓶机油只剩 1 瓶可见。用 Debugger 逐瓶排查,每一瓶的问题都不同:位置在柜台下方(被遮挡)、位置超出场景边界(被裁剪)、scale 过大导致摄像机在模型内部(三角面只渲染外表面)、Entity 未启用、残留了 AnchoringComponent 但场景里没有对应 ARKit anchor、ModelComponent 挂错了 Entity、Material 的 opacityThreshold 设为 1 同时 opacity 又小于 1(整瓶不可见)、Mesh 的法线方向反转(需要在 3D 工具里修复)、甚至完全忘记添加到场景。

修复方法是用一个创建循环替换掉所有复制粘贴的代码(18:15)。

private func stockBottles(placementRadius: Float) -> Entity {
    let bottleRadius: Float = 0.003
    let bottleHeight: Float = 0.022
    let angleIncrement: Float = -12
    let outOfStockBrands: Set = [3]

    let bottleGroup = Entity()
    bottleGroup.name = "Bottle Group"
    bottleGroup.position = [0, 0.04, 0]
    bottleGroup.orientation = .init(angle: 180 * (.pi / 180), axis: [0, 1, 0])

    var bottleMaterial = PhysicallyBasedMaterial()
    bottleMaterial.baseColor = .init(tint: .green)
    bottleMaterial.blending = .transparent(opacity: .init(floatLiteral: 0.5))

    for i in 0..<9 {
        let angle = Angle2D(degrees: angleIncrement * Float(i))
        let bottleMesh = MeshResource.generateCylinder(height: bottleHeight, radius: bottleRadius)
        let bottle = ModelEntity(mesh: bottleMesh, materials: [bottleMaterial])
        bottle.name = "BT\(i)"
        bottle.position = pointOnCircumference(angle: angle, radius: placementRadius, y: bottleHeight / 2)
        if outOfStockBrands.contains(i) {
            bottle.components[OutOfStockComponent.self] = OutOfStockComponent()
        }
        bottleGroup.addChild(bottle)
    }
    return bottleGroup
}

关键点:

  • 用循环创建代替手动复制粘贴,从根源消灭”复制时改错参数”的问题
  • pointOnCircumference 统一计算位置,避免手动设错坐标
  • outOfStockBrands 用 Set 管理缺货项,逻辑清晰
  • Session 建议:场景布局尽量在 Reality Composer Pro 里做,比纯代码定位更可靠

4. 自定义调试可视化

机器人传送到夜店后站着不动,不走向舞池。开发者用 #if DEBUG 编译块添加了调试 Entity:给每个不可见的吸引子加了半透明圆柱体可视化,用自定义 AttractorDebugComponent 在 Inspector 里显示状态和目标引用,还用 DanceSystemDebugComponent 渲染了一张 Swift Chart 饼图(保存为 UIImage 属性)。

Debugger 里一眼看到所有吸引子都处于 attracting 状态——正常应该只有一个。点开吸引子的 attractor 引用链接跳到目标机器人,发现它的 Newcomer Component 没被移除,导致每个吸引子都锁定同一个机器人。修复只需一行(22:48)。

// 4. Untag them as a Newcomer
visitor.components[Newcomer.self] = nil

关键点:

  • 自定义 Component 的属性会自动显示在 Inspector 里,包括 Entity 引用(变成可点击链接)
  • UIImage 类型的属性可以在 Inspector 里显示图片,比如用 Swift Chart 渲染的状态饼图
  • 整个调试系统放在 #if DEBUG 块里,Release 构建时会被编译器完全移除

核心启发

  • 做什么:把 RealityKit Debugger 纳入日常调试流程,遇到”显示不正常”第一反应是截取 Entity Hierarchy 而不是加 print。为什么值得做:3D 场景的层级关系和 Transform 继承靠代码阅读几乎无法推断,可视化工具能直接定位问题层级。怎么开始:在 visionOS 模拟器运行你的 App,点击 Xcode 调试区的”Capture Entity Hierarchy”按钮。

  • 做什么:给 Entity 取有意义的 name 属性,比如 "Disco Ball" 而不是默认的空字符串。为什么值得做:Debugger 的 Hierarchy 树按名字显示,名字空的 Entity 排查时完全无法辨识。怎么开始:创建 Entity 后立即设置 entity.name = "描述性名称"

  • 做什么:用 #if DEBUG 编译块给自定义 System 加调试可视化和 Inspector Component。为什么值得做:你的 ECS System 越复杂,运行时的状态越难追踪,自定义 Component 能让 Debugger 直接展示你的业务逻辑状态。怎么开始:参考 Session 的 DanceSystemDebugComponent 模式——用自定义 Component 存计数器和 Entity 引用,UIImage 属性还可以显示图表。

  • 做什么:场景布局优先在 Reality Composer Pro 里完成,避免纯代码定位 3D 资产。为什么值得做:Session 里 9 个瓶子 9 种错误,全部来自代码里的手动坐标和属性设置,用可视化编辑器可以从源头减少这类问题。怎么开始:在 Reality Composer Pro 里摆放和配置资产,导出后用 Entity.load(named:) 加载。


关联 Session

评论

GitHub Issues · utterances