mcpField
R
realitykit-ar

realitykit-ar

github.com/dpearson2699开发1.2k407 次安装13 周前更新
代码仓库

关于 realitykit-ar

面向 iOS 开发者的 RealityKit 与 ARKit 技能,介绍使用 RealityView 构建 AR 场景、放置和交互 3D 实体,以及场景理解。

主要功能

  1. 01使用 RealityView 呈现 AR 内容
  2. 02加载或生成 3D 实体
  3. 03将实体锚定到平面或世界位置
  4. 04通过射线检测放置对象
  5. 05使用手势选择和拖动实体
  6. 06处理场景更新事件

适用场景

  1. 01在检测到的表面放置 3D 模型
  2. 02构建可点击和拖动的 AR 对象
  3. 03按场景更新处理连续逻辑

安装

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

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill realitykit-ar

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

附带文件references/realitykit-patterns.md

详细介绍

RealityKit + ARKit

使用 RealityKit 进行渲染、使用 ARKit 进行世界跟踪,在 iOS 上构建 AR 体验。 涵盖 RealityView、实体管理、射线投射、场景理解和基于手势的交互。面向 Swift 6.2 / iOS 26+。

目录

设置

项目配置
  1. 将 NSCameraUsageDescription 添加到 Info.plist
  2. 在 iOS 上,RealityKit 默认通过 RealityViewCameraContent 使用设备摄像头(iOS 18+、macOS 15+)
  3. 在 iOS 上实现基本 AR 无需额外功能
设备要求

AR 功能要求设备搭载 A9 芯片或更新型号。显示 AR 界面前,请务必确认设备支持。

import ARKit

guard ARWorldTrackingConfiguration.isSupported else {
    showUnsupportedDeviceMessage()
    return
}
关键类型
类型平台作用
RealityViewiOS 18+、visionOS 1+承载 RealityKit 内容的 SwiftUI 视图
RealityViewCameraContentiOS 18+、macOS 15+通过设备摄像头显示的内容
Entity全部所有场景对象的基类
ModelEntity全部带有可见 3D 模型的实体
AnchorEntity全部将实体固定到现实世界中的锚点

RealityView 基础知识

RealityView 是 RealityKit 在 SwiftUI 中的入口。在 iOS 上,它提供 RealityViewCameraContent,通过设备摄像头进行渲染,以实现 AR。

import SwiftUI
import RealityKit

struct ARExperienceView: View {
    var body: some View {
        RealityView { content in
            // content is RealityViewCameraContent on iOS
            let sphere = ModelEntity(
                mesh: .generateSphere(radius: 0.05),
                materials: [SimpleMaterial(
                    color: .blue,
                    isMetallic: true
                )]
            )
            sphere.position = [0, 0, -0.5]  // 50cm in front of camera
            content.add(sphere)
        }
    }
}
创建和更新模式

使用 update 闭包响应 SwiftUI 状态变化:

struct PlacementView: View {
    @State private var modelColor: UIColor = .red

    var body: some View {
        RealityView { content in
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(
                    color: .red,
                    isMetallic: false
                )]
            )
            box.name = "colorBox"
            box.position = [0, 0, -0.5]
            content.add(box)
        } update: { content in
            if let box = content.entities.first(
                where: { $0.name == "colorBox" }
            ) as? ModelEntity {
                box.model?.materials = [SimpleMaterial(
                    color: modelColor,
                    isMetallic: false
                )]
            }
        }

        Button("Change Color") {
            modelColor = modelColor == .red ? .green : .red
        }
    }
}

加载和创建实体

从 USDZ 文件加载

异步加载 3D 模型,避免阻塞主线程:

RealityView { content in
    if let robot = try? await ModelEntity(named: "robot") {
        robot.position = [0, -0.2, -0.8]
        robot.scale = [0.01, 0.01, 0.01]
        content.add(robot)
    }
}
以编程方式生成网格
// Box
let box = ModelEntity(
    mesh: .generateBox(size: [0.1, 0.2, 0.1], cornerRadius: 0.005),
    materials: [SimpleMaterial(color: .gray, isMetallic: true)]
)

// Sphere
let sphere = ModelEntity(
    mesh: .generateSphere(radius: 0.05),
    materials: [SimpleMaterial(color: .blue, roughness: 0.2, isMetallic: true)]
)

// Plane
let plane = ModelEntity(
    mesh: .generatePlane(width: 0.3, depth: 0.3),
    materials: [SimpleMaterial(color: .green, isMetallic: false)]
)
添加组件

实体使用 ECS(实体组件系统)架构。为实体添加组件以赋予其行为:

let box = ModelEntity(
    mesh: .generateBox(size: 0.1),
    materials: [SimpleMaterial(color: .red, isMetallic: false)]
)

// Make it respond to physics
box.components.set(PhysicsBodyComponent(
    massProperties: .default,
    material: .default,
    mode: .dynamic
))

// Add collision shape for interaction
box.components.set(CollisionComponent(
    shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
))

// Enable input targeting for gestures
box.components.set(InputTargetComponent())

锚定和放置

AnchorEntity

使用 AnchorEntity 将内容锚定到检测到的表面或世界坐标位置:

RealityView { content in
    // Anchor to a horizontal surface
    let floorAnchor = AnchorEntity(.plane(
        .horizontal,
        classification: .floor,
        minimumBounds: [0.2, 0.2]
    ))

    let model = ModelEntity(
        mesh: .generateBox(size: 0.1),
        materials: [SimpleMaterial(color: .orange, isMetallic: false)]
    )
    floorAnchor.addChild(model)
    content.add(floorAnchor)
}
锚点目标
目标说明
.plane(.horizontal, ...)水平表面(地板、桌面)
.plane(.vertical, ...)垂直表面(墙壁)
.plane(.any, ...)任意检测到的平面
.world(transform:)固定的世界空间位置

射线投射

使用 RealityViewCameraContent 在 SwiftUI 视图坐标和 RealityKit 世界空间之间进行转换。结合 SpatialTapGesture,即可在用户点击检测到的表面时放置对象。

手势和交互

对实体使用拖动手势
struct DraggableARView: View {
    var body: some View {
        RealityView { content in
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(color: .blue, isMetallic: true)]
            )
            box.position = [0, 0, -0.5]
            box.components.set(CollisionComponent(
                shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
            ))
            box.components.set(InputTargetComponent())
            box.name = "draggable"
            content.add(box)
        }
        .gesture(
            DragGesture()
                .targetedToAnyEntity()
                .onChanged { value in
                    let entity = value.entity
                    guard let parent = entity.parent else { return }
                    entity.position = value.convert(
                        value.location3D,
                        from: .local,
                        to: parent
                    )
                }
        )
    }
}
点击以选择
.gesture(
    SpatialTapGesture()
        .targetedToAnyEntity()
        .onEnded { value in
            let tappedEntity = value.entity
            highlightEntity(tappedEntity)
        }
)

场景理解

逐帧更新

订阅场景更新事件以持续处理:

RealityView { content in
    let entity = ModelEntity(
        mesh: .generateSphere(radius: 0.05),
        materials: [SimpleMaterial(color: .yellow, isMetallic: false)]
    )
    entity.position = [0, 0, -0.5]
    content.add(entity)

    _ = content.subscribe(to: SceneEvents.Update.self) { event in
        let time = Float(event.deltaTime)
        entity.position.y += sin(Float(Date().timeIntervalSince1970)) * time * 0.1
    }
}
visionOS 说明

在 visionOS 上,ARKit 提供了不同的 API,包括 ARKitSession、 WorldTrackingProvider 和 PlaneDetectionProvider。这些 visionOS 专用类型在 iOS 上不可用。在 iOS 上,RealityKit 会通过 RealityViewCameraContent 自动处理世界跟踪。

常见错误

不要:跳过 AR 功能检查

并非所有设备都支持 AR。显示黑色摄像头画面且不提供任何反馈会让用户感到困惑。

// WRONG -- no device check
struct MyARView: View {
    var body: some View {
        RealityView { content in
            // Fails silently on unsupported devices
        }
    }
}

// CORRECT -- check support and show fallback
struct MyARView: View {
    var body: some View {
        if ARWorldTrackingConfiguration.isSupported {
            RealityView { content in
                // AR content
            }
        } else {
            ContentUnavailableView(
                "AR Not Supported",
                systemImage: "arkit",
                description: Text("This device does not support AR.")
            )
        }
    }
}
不要:同步加载大型模型

在主线程上加载大型 USDZ 文件会导致帧率下降和卡顿。 RealityView 的 make 闭包是 async 的——请使用它。

// WRONG -- synchronous load blocks the main thread
RealityView { content in
    let model = try! Entity.load(named: "large-scene")
    content.add(model)
}

// CORRECT -- async load
RealityView { content in
    if let model = try? await ModelEntity(named: "large-scene") {
        content.add(model)
    }
}
不要:忘记为可交互实体添加碰撞和输入目标组件

手势仅适用于同时具有 CollisionComponent 和 InputTargetComponent 的实体。没有这些组件,点击和拖动操作会穿透实体。

// WRONG -- entity ignores gestures
let box = ModelEntity(mesh: .generateBox(size: 0.1))
content.add(box)

// CORRECT -- add collision and input components
let box = ModelEntity(
    mesh: .generateBox(size: 0.1),
    materials: [SimpleMaterial(color: .red, isMetallic: false)]
)
box.components.set(CollisionComponent(
    shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
))
box.components.set(InputTargetComponent())
content.add(box)
不要:在 update 闭包中创建新实体

SwiftUI 每次状态变化时都会运行 update 闭包。在其中创建实体会在每次渲染过程中重复添加内容。

// WRONG -- duplicates entities on every state change
RealityView { content in
    // empty
} update: { content in
    let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
    content.add(sphere)  // Added again on every update
}

// CORRECT -- create in make, modify in update
RealityView { content in
    let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
    sphere.name = "mySphere"
    content.add(sphere)
} update: { content in
    if let sphere = content.entities.first(
        where: { $0.name == "mySphere" }
    ) as? ModelEntity {
        // Modify existing entity
        sphere.position.y = newYPosition
    }
}
不要:忽略相机权限

iOS 上的 RealityKit 需要访问相机。如果用户拒绝授权,视图会显示黑屏,且不提供任何说明。

// WRONG -- no permission handling
RealityView { content in
    // Black screen if camera denied
}

// CORRECT -- check and request permission
struct ARContainerView: View {
    @State private var cameraAuthorized = false

    var body: some View {
        Group {
            if cameraAuthorized {
                RealityView { content in
                    // AR content
                }
            } else {
                ContentUnavailableView(
                    "Camera Access Required",
                    systemImage: "camera.fill",
                    description: Text("Enable camera in Settings to use AR.")
                )
            }
        }
        .task {
            let status = AVCaptureDevice.authorizationStatus(for: .video)
            if status == .authorized {
                cameraAuthorized = true
            } else if status == .notDetermined {
                cameraAuthorized = await AVCaptureDevice
                    .requestAccess(for: .video)
            }
        }
    }
}

审核清单

  • 在 Info.plist 中设置 NSCameraUsageDescription
  • 在展示 AR 视图前检查 AR 设备能力
  • 请求相机权限,并在用户拒绝时通过备用界面妥善处理
  • 在 make 闭包中异步加载 3D 模型
  • 在 make 中创建实体,在 update 中修改实体(不要在 update 中创建实体)
  • 交互式实体同时具有 CollisionComponent 和 InputTargetComponent
  • 碰撞形状与实体的视觉尺寸相符
  • 使用 SceneEvents.Update 订阅处理逐帧逻辑(不要使用 SwiftUI 计时器)
  • 大型场景使用 ModelEntity(named:) 异步加载,而不是 Entity.load(named:)
  • 锚点实体根据使用场景指定合适的表面类型
  • 设置实体名称,以便在 update 闭包中查找

参考资料

评论

支持 Markdown