mcpField
R
realitykit-ar

realitykit-ar

github.com/dpearson2699開発1.2k407 回インストール13 週間前に更新
リポジトリ

realitykit-ar について

iOS開発者向けにRealityKitとARKitの使い方を解説するスキルです。RealityViewによるARシーンの構築、3Dエンティティの配置と操作、シーン理解を扱います。

主な機能

  1. 01RealityViewによるARコンテンツの表示
  2. 023Dエンティティの読み込みと生成
  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. Info.plist に NSCameraUsageDescription を追加します
  2. iOS では、RealityKit はデフォルトで RealityViewCameraContent を介してデバイスのカメラを使用します(iOS 18 以降、macOS 15 以降)
  3. iOS で基本的な AR を利用するために、追加の機能は必要ありません
デバイス要件

AR 機能には、A9 チップ以降を搭載したデバイスが必要です。AR UI を表示する前に、必ず対応状況を確認してください。

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)
        }
    }
}
Make と Update のパターン

SwiftUI の状態変化に応じて処理するには、update クロージャを使用します。

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 は ARKitSession、WorldTrackingProvider、PlaneDetectionProvider を使用する別の API 群を提供します。これらの 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 クロージャ内で新しいエンティティを作成する

update クロージャは SwiftUI の状態が変化するたびに実行されます。そこでエンティティを作成すると、レンダリングのたびにコンテンツが重複します。

// 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 デバイスの機能を確認している
  • カメラの許可をリクエストし、拒否された場合はフォールバック UI で対応している
  • 3D モデルを make クロージャ内で非同期に読み込んでいる
  • エンティティは make 内で作成し、update 内で変更している(update 内では作成していない)
  • インタラクティブなエンティティに CollisionComponent と InputTargetComponent の両方がある
  • コリジョンシェイプがエンティティの見た目のサイズと一致している
  • フレームごとのロジックに SceneEvents.Update のサブスクリプションを使用している(SwiftUI タイマーは使用していない)
  • 大規模なシーンでは Entity.load(named:) ではなく、ModelEntity(named:) による非同期読み込みを使用している
  • アンカーエンティティが用途に適したサーフェスタイプを対象にしている
  • update クロージャ内で検索できるよう、エンティティ名を設定している

参考資料

コメント