mcpField
A
axiom-realitykit

axiom-realitykit

github.com/charleswiltgen开发1.2k153 次安装今天更新
代码仓库

关于 axiom-realitykit

axiom-realitykit 是使用 RealityKit 构建 3D、AR 和空间计算应用的开发指南,讲解实体-组件-系统模型,并涵盖 SwiftUI、交互、物理、渲染和多人同步。

主要功能

  1. 01讲解实体-组件-系统架构
  2. 02涵盖实体、组件和系统
  3. 03将 3D 内容集成到 SwiftUI
  4. 04介绍 AR 锚点与交互
  5. 05涵盖材质、渲染和物理
  6. 06包含多人同步

适用场景

  1. 01构建 RealityKit 3D 游戏或体验
  2. 02在 SwiftUI 应用中嵌入 3D 模型
  3. 03添加 AR 放置和手势交互
  4. 04实现物理或共享 AR 体验

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-realitykit

安装后在 Claude Code 中输入/axiom-realitykit确认已加载。

详细介绍

RealityKit 开发指南

目的:使用 RealityKit 的实体-组件-系统架构,构建 3D 内容、AR 体验和空间计算应用 iOS 版本:iOS 13+(基础版本)、iOS 18+(iOS 上的 RealityView)、visionOS 1.0+ Xcode:Xcode 15+

何时使用此技能

在以下情况使用此技能:

  • 构建任何 3D 体验(AR、游戏、可视化、空间计算)
  • 创建包含 3D 内容的 SwiftUI 应用(RealityView、Model3D)
  • 使用锚点实现 AR(世界、图像、人脸、身体跟踪)
  • 使用实体-组件-系统(ECS)架构
  • 设置物理、碰撞或空间交互
  • 构建多人或共享 AR 体验
  • 从 SceneKit 迁移到 RealityKit
  • 面向 visionOS

请勿在以下情况使用此技能:

  • 维护 SceneKit(使用 axiom-scenekit)
  • 开发 2D 游戏(使用 axiom-spritekit)
  • 编写 Metal 着色器(使用 axiom-metal-migration-ref)
  • 纯 GPU 计算(直接使用 Metal)

1. 思维模型:ECS 与场景图

场景图(SceneKit)

在 SceneKit 中,节点拥有自己的属性。节点本身就是可渲染、可碰撞、可动画的对象。

实体-组件-系统(RealityKit)

在 RealityKit 中,实体是空容器。组件添加数据,系统处理这些数据。

Entity (identity + hierarchy)
  ├── TransformComponent (position, rotation, scale)
  ├── ModelComponent (mesh + materials)
  ├── CollisionComponent (collision shapes)
  ├── PhysicsBodyComponent (mass, mode)
  └── [YourCustomComponent] (game-specific data)

System (processes entities with specific components each frame)

ECS 的重要性:

  • 组合优于继承:在任意实体上组合任意组件
  • 面向数据:系统高效地处理组件数组
  • 逻辑解耦:系统之间互不知晓
  • 易于测试:组件是纯数据,系统是纯逻辑
ECS 思维转变
场景图思维ECS 思维
“玩家节点会移动”“移动系统处理具有 MovementComponent 的实体”
“向节点子类添加方法”“添加组件,创建系统”
“在节点中重写 update(_:)”“注册一个查询组件的 System”
“节点知道自己的生命值”“HealthComponent 保存数据,DamageSystem 处理数据”

2. 实体层级结构

创建实体
// Empty entity
let entity = Entity()
entity.name = "player"

// Entity with components
let entity = Entity()
entity.components[ModelComponent.self] = ModelComponent(
    mesh: .generateBox(size: 0.1),
    materials: [SimpleMaterial(color: .blue, isMetallic: false)]
)

// ModelEntity convenience (has ModelComponent built in)
let box = ModelEntity(
    mesh: .generateBox(size: 0.1),
    materials: [SimpleMaterial(color: .red, isMetallic: true)]
)
层级管理
// Parent-child
parent.addChild(child)
child.removeFromParent()

// Find entities
let found = root.findEntity(named: "player")

// Enumerate
for child in entity.children {
    // Process children
}

// Clone
let clone = entity.clone(recursive: true)
变换
// Local transform (relative to parent)
entity.position = SIMD3<Float>(0, 1, 0)
entity.orientation = simd_quatf(angle: .pi / 4, axis: SIMD3(0, 1, 0))
entity.scale = SIMD3<Float>(repeating: 2.0)

// World-space queries
let worldPos = entity.position(relativeTo: nil)
let worldTransform = entity.transform(relativeTo: nil)

// Set world-space transform
entity.setPosition(SIMD3(1, 0, 0), relativeTo: nil)

// Look at a point
entity.look(at: targetPosition, from: entity.position, relativeTo: nil)

3. 组件

内置组件
组件用途
Transform位置、旋转、缩放
ModelComponent网格几何体 + 材质
CollisionComponent用于物理和交互的碰撞形状
PhysicsBodyComponent质量、物理模式(动态/静态/运动学)
PhysicsMotionComponent线速度和角速度
AnchoringComponentAR 锚点附着
SynchronizationComponent多人同步
PerspectiveCameraComponent摄像头设置
DirectionalLightComponent定向光
PointLightComponent点光源
SpotLightComponent聚光灯
CharacterControllerComponent角色物理控制器
AudioMixGroupsComponent音频混合
SpatialAudioComponent3D 空间定位音频
AmbientAudioComponent非空间定位音频
ChannelAudioComponent多声道音频
OpacityComponent实体透明度
GroundingShadowComponent接触阴影
InputTargetComponent手势输入(visionOS)
HoverEffectComponent悬停高亮(visionOS)
AccessibilityComponentVoiceOver 支持
自定义组件
struct HealthComponent: Component {
    var current: Int
    var maximum: Int

    var percentage: Float {
        Float(current) / Float(maximum)
    }
}

// Register before use (typically in app init)
HealthComponent.registerComponent()

// Attach to entity
entity.components[HealthComponent.self] = HealthComponent(current: 100, maximum: 100)

// Read
if let health = entity.components[HealthComponent.self] {
    print(health.current)
}

// Modify
entity.components[HealthComponent.self]?.current -= 10
组件生命周期

组件是值类型(结构体)。读取组件、修改它并写回时,实际上是在替换整个组件:

// Read-modify-write pattern
var health = entity.components[HealthComponent.self]!
health.current -= damage
entity.components[HealthComponent.self] = health

反面模式:持有组件的引用并期待修改会自动传播。读取组件时会复制它。


4. 系统

System 协议
struct DamageSystem: System {
    // Define which components this system needs
    static let query = EntityQuery(where: .has(HealthComponent.self))

    init(scene: RealityKit.Scene) {
        // One-time setup
    }

    func update(context: SceneUpdateContext) {
        for entity in context.entities(matching: Self.query,
                                        updatingSystemWhen: .rendering) {
            var health = entity.components[HealthComponent.self]!
            if health.current <= 0 {
                entity.removeFromParent()
            }
        }
    }
}

// Register system
DamageSystem.registerSystem()
系统最佳实践
  • 每个系统只负责一项职责:MovementSystem、DamageSystem、RenderingSystem,而不是 GameLogicSystem
  • 查询过滤:使用精确查询,避免处理无关实体
  • 顺序很重要:系统按注册顺序运行。请先注册依赖项。
  • 避免存储实体引用:每帧重新查询。实体引用可能会过期。
事件处理
// Subscribe to collision events
scene.subscribe(to: CollisionEvents.Began.self) { event in
    let entityA = event.entityA
    let entityB = event.entityB
    // Handle collision
}

// Subscribe to scene update
scene.subscribe(to: SceneEvents.Update.self) { event in
    let deltaTime = event.deltaTime
    // Per-frame logic
}

5. SwiftUI 集成

RealityView(iOS 18+、visionOS 1.0+)
struct ContentView: View {
    var body: some View {
        RealityView { content in
            // make closure — called once
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(color: .blue, isMetallic: false)]
            )
            content.add(box)

        } update: { content in
            // update closure — called when SwiftUI state changes
        }
    }
}
带摄像头的 RealityView(iOS)

在 iOS 上,RealityView 提供了用于配置 AR 或虚拟摄像头的摄像头内容参数:

RealityView { content, attachments in
    // Load 3D content
    if let model = try? await ModelEntity(named: "scene") {
        content.add(model)
    }
}
异步加载内容
RealityView { content in
    // Load from bundle
    if let entity = try? await Entity(named: "MyScene", in: .main) {
        content.add(entity)
    }

    // Load from URL
    if let entity = try? await Entity(contentsOf: modelURL) {
        content.add(entity)
    }
}
Model3D(简单显示)
// Simple 3D model display (no interaction)
Model3D(named: "toy_robot") { model in
    model
        .resizable()
        .scaledToFit()
} placeholder: {
    ProgressView()
}
SwiftUI 附加视图(visionOS)
RealityView { content, attachments in
    let entity = ModelEntity(mesh: .generateSphere(radius: 0.1))
    content.add(entity)

    if let label = attachments.entity(for: "priceTag") {
        label.position = SIMD3(0, 0.15, 0)
        entity.addChild(label)
    }
} attachments: {
    Attachment(id: "priceTag") {
        Text("$9.99")
            .padding()
            .glassBackgroundEffect()
    }
}
状态绑定模式
struct GameView: View {
    @State private var score = 0

    var body: some View {
        VStack {
            Text("Score: \(score)")

            RealityView { content in
                let scene = try! await Entity(named: "GameScene")
                content.add(scene)
            } update: { content in
                // React to state changes
                // Note: update is called when SwiftUI state changes,
                // not every frame. Use Systems for per-frame logic.
            }
        }
    }
}

6. iOS 上的 AR

AnchorEntity
// Horizontal plane
let anchor = AnchorEntity(.plane(.horizontal, classification: .table,
                                  minimumBounds: SIMD2(0.2, 0.2)))

// Vertical plane
let anchor = AnchorEntity(.plane(.vertical, classification: .wall,
                                  minimumBounds: SIMD2(0.5, 0.5)))

// World position
let anchor = AnchorEntity(world: SIMD3<Float>(0, 0, -1))

// Image anchor
let anchor = AnchorEntity(.image(group: "AR Resources", name: "poster"))

// Face anchor (front camera)
let anchor = AnchorEntity(.face)

// Body anchor
let anchor = AnchorEntity(.body)
SpatialTrackingSession(iOS 18+)
let session = SpatialTrackingSession()
let configuration = SpatialTrackingSession.Configuration(tracking: [.plane, .object])
let result = await session.run(configuration)

if let notSupported = result {
    // Handle unsupported tracking on this device
    for denied in notSupported.deniedTrackingModes {
        print("Not supported: \(denied)")
    }
}
AR 最佳实践
  • 将锚定实体放置在检测到的表面上,而非世界坐标位置,以提高稳定性
  • 使用平面分类(.table、.floor、.wall)将内容放置在合适的位置
  • 从水平平面检测开始——这是最可靠的方式
  • 在真机上测试;模拟器的 AR 功能有限
  • 在表面检测期间提供视觉反馈(引导叠加层)

7. 交互

ManipulationComponent(iOS、visionOS)
// Enable drag, rotate, scale gestures
entity.components[ManipulationComponent.self] = ManipulationComponent(
    allowedModes: .all  // .translate, .rotate, .scale
)

// Also requires CollisionComponent for hit testing
entity.generateCollisionShapes(recursive: true)
InputTargetComponent(visionOS)
// Required for visionOS gesture input
entity.components[InputTargetComponent.self] = InputTargetComponent()
entity.components[CollisionComponent.self] = CollisionComponent(
    shapes: [.generateBox(size: SIMD3(0.1, 0.1, 0.1))]
)
与 SwiftUI 集成手势
RealityView { content in
    let entity = ModelEntity(mesh: .generateBox(size: 0.1))
    entity.generateCollisionShapes(recursive: true)
    entity.components.set(InputTargetComponent())
    content.add(entity)
}
.gesture(
    TapGesture()
        .targetedToAnyEntity()
        .onEnded { value in
            let tappedEntity = value.entity
            // Handle tap
        }
)
.gesture(
    DragGesture()
        .targetedToAnyEntity()
        .onChanged { value in
            value.entity.position = value.convert(value.location3D,
                from: .local, to: .scene)
        }
)
命中测试
// Ray-cast from screen point
if let result = arView.raycast(from: screenPoint,
                                allowing: .estimatedPlane,
                                alignment: .horizontal).first {
    let worldPosition = result.worldTransform.columns.3
    // Place entity at worldPosition
}

8. 材质与渲染

材质类型
材质用途自定义
SimpleMaterial纯色或纹理颜色、金属度、粗糙度
PhysicallyBasedMaterial完整的 PBR所有 PBR 贴图(基础颜色、法线、金属度、粗糙度、AO、自发光)
UnlitMaterial不受光照影响颜色或纹理,始终完全受光
OcclusionMaterial不可见但可遮挡遮挡被真实物体挡住的 AR 内容
VideoMaterial在表面播放视频由 AVPlayer 驱动
ShaderGraphMaterial自定义着色器图Reality Composer Pro
CustomMaterialMetal 着色器函数完整的 Metal 控制
PhysicallyBasedMaterial
var material = PhysicallyBasedMaterial()
material.baseColor = .init(tint: .white,
    texture: .init(try! .load(named: "albedo")))
material.metallic = .init(floatLiteral: 0.0)
material.roughness = .init(floatLiteral: 0.5)
material.normal = .init(texture: .init(try! .load(named: "normal")))
material.ambientOcclusion = .init(texture: .init(try! .load(named: "ao")))
material.emissiveColor = .init(color: .blue)
material.emissiveIntensity = 2.0

let entity = ModelEntity(
    mesh: .generateSphere(radius: 0.1),
    materials: [material]
)
OcclusionMaterial(AR)
// Invisible plane that hides 3D content behind it
let occluder = ModelEntity(
    mesh: .generatePlane(width: 1, depth: 1),
    materials: [OcclusionMaterial()]
)
occluder.position = SIMD3(0, 0, 0)
anchor.addChild(occluder)
环境光照
// Image-based lighting
if let resource = try? await EnvironmentResource(named: "studio_lighting") {
    // Apply via RealityView content
}

9. 物理与碰撞

碰撞形状
// Generate from mesh (accurate but expensive)
entity.generateCollisionShapes(recursive: true)

// Manual shapes (prefer for performance)
entity.components[CollisionComponent.self] = CollisionComponent(
    shapes: [
        .generateBox(size: SIMD3(0.1, 0.2, 0.1)),     // Box
        .generateSphere(radius: 0.1),                   // Sphere
        .generateCapsule(height: 0.3, radius: 0.05)     // Capsule
    ]
)
物理实体
// Dynamic — physics simulation controls movement
entity.components[PhysicsBodyComponent.self] = PhysicsBodyComponent(
    massProperties: .init(mass: 1.0),
    material: .generate(staticFriction: 0.5,
                        dynamicFriction: 0.3,
                        restitution: 0.4),
    mode: .dynamic
)

// Static — immovable collision surface
ground.components[PhysicsBodyComponent.self] = PhysicsBodyComponent(
    mode: .static
)

// Kinematic — code-controlled, participates in collisions
platform.components[PhysicsBodyComponent.self] = PhysicsBodyComponent(
    mode: .kinematic
)
碰撞组与筛选器
// Define groups
let playerGroup = CollisionGroup(rawValue: 1 << 0)
let enemyGroup = CollisionGroup(rawValue: 1 << 1)
let bulletGroup = CollisionGroup(rawValue: 1 << 2)

// Filter: player collides with enemies and bullets
entity.components[CollisionComponent.self] = CollisionComponent(
    shapes: [.generateSphere(radius: 0.1)],
    filter: CollisionFilter(
        group: playerGroup,
        mask: enemyGroup | bulletGroup
    )
)
碰撞事件
// Subscribe in RealityView make closure or System
scene.subscribe(to: CollisionEvents.Began.self, on: playerEntity) { event in
    let otherEntity = event.entityA == playerEntity ? event.entityB : event.entityA
    handleCollision(with: otherEntity)
}
施加力
if var motion = entity.components[PhysicsMotionComponent.self] {
    motion.linearVelocity = SIMD3(0, 5, 0)  // Impulse up
    entity.components[PhysicsMotionComponent.self] = motion
}

10. 动画

变换动画
// Animate to position over duration
entity.move(
    to: Transform(
        scale: SIMD3(repeating: 1.5),
        rotation: simd_quatf(angle: .pi, axis: SIMD3(0, 1, 0)),
        translation: SIMD3(0, 2, 0)
    ),
    relativeTo: entity.parent,
    duration: 2.0,
    timingFunction: .easeInOut
)
播放 USD 动画
if let entity = try? await Entity(named: "character") {
    // Play all available animations
    for animation in entity.availableAnimations {
        entity.playAnimation(animation.repeat())
    }
}
动画播放控制
let controller = entity.playAnimation(animation)
controller.pause()
controller.resume()
controller.speed = 2.0      // 2x playback speed
controller.blendFactor = 0.5 // Blend with current state

11. 音频

空间音频
// Load audio resource
let resource = try! AudioFileResource.load(named: "engine.wav",
    configuration: .init(shouldLoop: true))

// Create entity with spatial audio
let audioEntity = Entity()
audioEntity.components[SpatialAudioComponent.self] = SpatialAudioComponent()
let controller = audioEntity.playAudio(resource)

// Position the audio source in 3D space
audioEntity.position = SIMD3(2, 0, -1)
环境音频
entity.components[AmbientAudioComponent.self] = AmbientAudioComponent()
entity.playAudio(backgroundMusic)

12. 性能

实体数量
  • 少于 100 个实体:无需担忧
  • 100–1000 个实体:使用 RealityKit 调试器监控
  • 1000 个以上实体:使用实例化和 LOD 策略
实例化
// Share mesh and material across many entities
let sharedMesh = MeshResource.generateSphere(radius: 0.01)
let sharedMaterial = SimpleMaterial(color: .white, isMetallic: false)

for i in 0..<1000 {
    let entity = ModelEntity(mesh: sharedMesh, materials: [sharedMaterial])
    entity.position = randomPosition()
    parent.addChild(entity)
}

RealityKit 会自动批处理网格和材质资源相同的实体。

组件变动

反模式:每帧都创建并替换组件。

// BAD — component allocation every frame
func update(context: SceneUpdateContext) {
    for entity in context.entities(matching: query, updatingSystemWhen: .rendering) {
        entity.components[ModelComponent.self] = ModelComponent(
            mesh: .generateBox(size: 0.1),
            materials: [newMaterial]  // New allocation every frame
        )
    }
}

// GOOD — modify existing component
func update(context: SceneUpdateContext) {
    for entity in context.entities(matching: query, updatingSystemWhen: .rendering) {
        // Only update when actually needed
        if needsUpdate {
            var model = entity.components[ModelComponent.self]!
            model.materials = [cachedMaterial]
            entity.components[ModelComponent.self] = model
        }
    }
}
碰撞形状优化
  • 使用简单形状(盒体、球体、胶囊体),而非基于网格的碰撞形状
  • generateCollisionShapes(recursive: true) 使用方便,但开销较大
  • 对于静态几何体,在设置期间只生成一次形状
性能分析

使用 Xcode 的 RealityKit 调试器:

  • 实体检查器:查看实体层级结构和组件
  • 统计信息叠加层:实体数量、绘制调用次数、三角形数量
  • 物理可视化:显示碰撞形状

13. 多人游戏

同步基础
// Components sync automatically if they conform to Codable
struct ScoreComponent: Component, Codable {
    var points: Int
}

// SynchronizationComponent controls what syncs
entity.components[SynchronizationComponent.self] = SynchronizationComponent()
MultipeerConnectivityService
let service = try MultipeerConnectivityService(session: mcSession)
// Entities with SynchronizationComponent auto-sync across peers
所有权
  • 只有实体的所有者才能修改该实体
  • 修改共享实体前,请先请求所有权
  • 不符合 Codable 的组件数据不会同步

14. 反模式

反模式 1:用 UIKit 的思维方式看待 ECS

时间成本:与架构对抗,令人沮丧数小时

// BAD — subclassing Entity for behavior
class PlayerEntity: Entity {
    func takeDamage(_ amount: Int) { /* logic in entity */ }
}

// GOOD — component holds data, system has logic
struct HealthComponent: Component { var hp: Int }
struct DamageSystem: System {
    static let query = EntityQuery(where: .has(HealthComponent.self))
    func update(context: SceneUpdateContext) {
        // Process damage here
    }
}
反模式 2:实体过于庞大

时间成本:架构无法测试,也不够灵活

不要把所有游戏逻辑都放在一种实体类型中。应拆分为可自由组合的组件。

反模式 3:没有系统的逐帧更新

时间成本:错过帧更新,行为不一致

// BAD — timer-based updates
Timer.scheduledTimer(withTimeInterval: 1/60, repeats: true) { _ in
    entity.position.x += 0.01
}

// GOOD — System update
struct MovementSystem: System {
    static let query = EntityQuery(where: .has(VelocityComponent.self))
    func update(context: SceneUpdateContext) {
        for entity in context.entities(matching: Self.query,
                                        updatingSystemWhen: .rendering) {
            let velocity = entity.components[VelocityComponent.self]!
            entity.position += velocity.value * Float(context.deltaTime)
        }
    }
}
反模式 4:不为可交互实体生成碰撞形状

时间成本:花 15-30 分钟排查“为什么点击不起作用”

手势需要 CollisionComponent。如果实体有 InputTargetComponent(visionOS)或 ManipulationComponent,但没有 CollisionComponent,手势就永远不会触发。

反模式 5:在系统中存储实体引用

时间成本:因引用过期而崩溃

// BAD — entity might be removed between frames
struct BadSystem: System {
    var playerEntity: Entity?  // Stale reference risk

    func update(context: SceneUpdateContext) {
        playerEntity?.position.x += 0.1  // May crash
    }
}

// GOOD — query each frame
struct GoodSystem: System {
    static let query = EntityQuery(where: .has(PlayerComponent.self))

    func update(context: SceneUpdateContext) {
        for entity in context.entities(matching: Self.query,
                                        updatingSystemWhen: .rendering) {
            entity.position.x += Float(context.deltaTime)
        }
    }
}

15. 代码审查清单

  • 使用自定义组件前,通过 registerComponent() 注册
  • 场景加载前,通过 registerSystem() 注册系统
  • 组件使用值类型(结构体),而非类
  • 更新组件时使用读取-修改-写入模式
  • 可交互实体具有 CollisionComponent
  • visionOS 上的可交互实体具有 InputTargetComponent
  • 尽可能使用简单的碰撞形状(盒体/球体/胶囊体)
  • 系统中不跨帧存储实体引用
  • 相同实体之间共享网格和材质资源
  • 仅在值实际发生变化时更新组件
  • 3D 资源使用 USD/USDZ 格式(而非 .scn)
  • 所有模型/场景加载都使用异步加载
  • 如果基于闭包的订阅会保留视图/控制器,则在闭包中使用 [weak self]

16. 压力场景

场景 1:“对于我们的简单应用,ECS 是大材小用”

压力:团队想避免学习 ECS,只需要显示一个 3D 模型

错误做法:跳过 ECS,把所有逻辑都塞进 RealityView 闭包。

正确做法:即使是简单的应用也能从 ECS 中受益。RealityView 中的单个 ModelEntity 已经在使用 ECS——只是还没有添加自定义组件。先从简单开始,随着复杂度增加再添加组件。

回应模板:“我们已经在使用 ECS——Entity 和 ModelComponent。这个模式可以扩展。需要行为时,添加自定义组件只需定义一个结构体,并不涉及架构变更。”

场景 2:“直接用 SceneKit,我们熟悉它”

压力:团队有 SceneKit 使用经验,对 RealityKit 不熟悉

错误做法:用 SceneKit 开发新功能。

正确做法:SceneKit 已基本弃用,不会再添加新功能。现在投入学习 RealityKit——如果有需要,ECS 概念也能迁移到其他游戏引擎(Unity、Unreal、Bevy)。

回应模板:“SceneKit 已进入维护模式——不会添加新功能,只提供安全补丁。我们写下的每一行 SceneKit 代码都会增加迁移负担。RealityKit 的概念(Entity、Component、System)是业界标准的 ECS。”

场景 3:“不使用碰撞形状也能实现”

压力:截止日期临近,设置碰撞形状似乎很复杂

错误做法:跳过碰撞形状,改用基于位置的邻近检测。

正确做法:entity.generateCollisionShapes(recursive: true) 只需一行。没有它,手势就无法生效,物理实体也不会发生碰撞。这个“捷径”带来的调试时间比省下的时间更多。

回应模板:“手势和物理模拟都需要碰撞形状。只需一行:entity.generateCollisionShapes(recursive: true)。跳过这一步会导致手势悄无声息地失效——这类问题更难诊断。”


资源

WWDC:2019-603、2019-605、2021-10074、2022-10074、2023-10080、2023-10081、2024-10103、2024-10153

文档:/realitykit、/realitykit/entity、/realitykit/realityview、/realitykit/modelentity、/realitykit/anchorentity、/realitykit/component

技能:axiom-realitykit-ref、axiom-realitykit-diag、axiom-scenekit、axiom-scenekit-ref

评论

支持 Markdown