Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AIフル活用で挑む!空間アプリ開発のリアル

Avatar for TAAT TAAT
October 16, 2025

 AIフル活用で挑む!空間アプリ開発のリアル

LODGE XR “Tech” Talk - visionOSでの発表資料です
https://vrtokyo.connpass.com/event/368195/

Avatar for TAAT

TAAT

October 16, 2025
Tweet

More Decks by TAAT

Other Decks in Technology

Transcript

  1. タイトル 目次 1. 自己紹介 2. AIコーディングで作ったアプリ 紹介 3. AIコーディング セットアップ

    4. アプリ ベース実装と試行錯誤 5. Blender MCPで3Dモデルを作る 6. 仕上げと追加機能 7. まとめ
  2. タイトル # プロジェクト概要 これ visionOSアプリ「太陽系シミュレータ」 プロジェクトです。 ユーザー Immersive SpaceやVolumeで太陽系 シミュレーションを体験でき、天体にタッチすることで説明を見ることができます。

    ## 技術スタック - Swift - SwiftUI - RealityKit ## コーディングガイドライン @.claude/RealityKit_ECS_Guide.md @.claude/visionOSWindowManagement.md ## レビュー コードを変更した場合 `coding-guideline-reviewer`にレビューを依頼すること ## ビルド&実行 **作業完了時 必ず`build-and-run-executor`を使ってビルド&実行すること** ## 作業ログ 作業完了時に`./Logs`に作業ログファイルを新規作成すること - ファイル名: yyyy-mm-dd_HH:mm_1行に要約された作業内容 - 記載内容: プロンプト、概要、変更点、副作用、関連ファイル … プロジェクト CLAUDE.md
  3. タイトル サブエージェント 活用 Claude Code サブエージェント機能を使って、実装したコード レビューやビルド 実行を行う • coding-guideline-reviewer

    ◦ 実装したコードがコーディングガイドラインに沿っているかをレビューして、必要なら修正する • build-and-run-executor ◦ 実装が終わったら、xcodebuildやxcrunなどを使って、接続中 実機とシミュレータを自動判 別してビルド&実行を行う
  4. タイトル 赤い球体を追加させる # プロンプト Immersive Spaceに入った時、サイズ1.0 赤い球体を表示してください RealityView { content

    in // サイズ1.0 赤い球体を作成 let sphereMesh = MeshResource.generateSphere(radius: 0.5) let redMaterial = SimpleMaterial(color: .red, isMetallic: false) let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [redMaterial]) content.add(sphereEntity) }
  5. タイトル 赤い球体を円周運動させる # プロンプト 赤い球体が原点を中心として半径 1.0 円周運動を行うようにしてください。 1周する時間 10秒とする。 @State

    private var sphereEntity: ModelEntity? var body: some View { RealityView { content in ... } update: { content in // 円周運動を実装(半径1、周期10秒) guard let sphere = sphereEntity else { return } let currentTime = CACurrentMediaTime() let period: Double = 10.0 // 1周する時間(秒) let angle = Float(currentTime / period * 2.0 * .pi) // 円周運動 座標計算 let x = cos(angle) let z = sin(angle) sphere.position = SIMD3<Float>(x, 0, z) } } updateクロージャー 参照する state 更 新時に実行されるが、毎フレーム実行される と勘違いされている?
  6. タイトル 赤い球体を円周運動させる # プロンプト updateクロージャーで 実装方法だと全く動かなかった で、別 方法に修正してください TimelineView(.animation) {

    context in let elapsed = context.date.timeIntervalSince(startTime) // 10秒で1周 (角 度 = 2π / 10) let angularVelocity = 2.0 * .pi / 10.0 let angle = Float(elapsed * angularVelocity) // 半径1 円周運動 let radius: Float = 1.0 let x = radius * cos(angle) let z = radius * sin(angle) RealityView { content in ... } update: { content in // 球体 位置を更新 if let sphereEntity = content.entities.first(where: { $0.name == "redSphere" }) { sphereEntity.position = [x, 0, z] } } } 動いているが、TimelineView 中に RealityViewを入れて、RealityView自体 を再描画してしまうという強引な実装 ...
  7. タイトル 赤い球体を円周運動させる # プロンプト TimelineViewだとRealityView自体が再描画されてしまう で、別 方法を考えて修正してください RealityView { content

    in ... // 半径1m 位置に配置(X軸方向) sphereEntity.transform.translation = SIMD3<Float>(1, 0, 0) // 原点まわりにY軸で10秒/周 軌道アニメーション let orbit = OrbitAnimation( duration: 10.0, // 1周 = 10秒 axis: [0, 1, 0], // Y軸まわり startTransform: sphereEntity.transform, bindTarget: .transform, repeatMode: .repeat // 無限ループ ) if let animationResource = try? AnimationResource.generate(with: orbit) { sphereEntity.playAnimation(animationResource) } } RealityKit OrbitAnimationを使って、 Entityを円周軌道上でアニメーションさせて いる 良さそうだが、複雑な構成 シーン や動きだと厳しそう
  8. タイトル RealityKit コーディングガイドラインを作成 # RealityKit Development Rules ## ECS (Entity

    Component System) について ## System 実装 - RealityKit System `System`プロトコルに準拠する - 必須メソッド: - `init(scene: RealityKit.Scene)` - `func update(context: SceneUpdateContext)` - EntityQuery 静的プロパティとして定義 : `static let query = EntityQuery(where: .has(ComponentType.self))` ## System 登録 - System 登録 `SystemType.registerSystem()`を使用する - RealityKit.Systemに標準で用意されているメソッドを使用 - カスタム registerSystemメソッド 作成しない ## エンティティ 取得 - System update内でエンティティを取得する際 `context.entities(matching: query, updatingSystemWhen: .rendering)`を 使用 - `context.scene.performQuery()` 使用しない(古いAPI) - `updatingSystemWhen: .rendering`パラメータでレンダリング時 更新を指定 ## Component 実装 - Component `Component`プロトコルに準拠する構 体として実装 - データ 格納とパラメータ 初期化を担当 - Systemがロジックを処理し、 Componentがデータを保持する役割分担を明確にする ## RealityKit コード例 ```swift // Component struct OrbitComponent: Component { let radius: Float let angularVelocity: Float var currentAngle: Float init(radius: Float, angularVelocity: Float) { self.radius = radius self.angularVelocity = angularVelocity self.currentAngle = 0.0 } } // System struct OrbitSystem: System { static let query = EntityQuery(where: .has(OrbitComponent.self)) init(scene: RealityKit.Scene) {} func update(context: SceneUpdateContext) { for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) { guard var orbitComponent = entity.components[OrbitComponent.self] else { continue } // Update orbital position orbitComponent.currentAngle += orbitComponent.angularVelocity * Float(context.deltaTime) let x = cos(orbitComponent.currentAngle) * orbitComponent.radius let z = sin(orbitComponent.currentAngle) * orbitComponent.radius entity.position = SIMD3<Float>(x, 0, z) // Update component entity.components[OrbitComponent.self] = orbitComponent } } } // System登録例 OrbitSystem.registerSystem() ```
  9. タイトル RealityView { content in // OrbitSystemを登録 OrbitSystem.registerSystem() ... //

    OrbitComponentを追加(半径1m、周期10秒、Y軸まわり) sphereEntity.components.set(OrbitComponent(radius: 1.0, period: 10.0, axis: [0, 1, 0])) content.add(sphereEntity) } struct OrbitComponent: Component { let radius: Float let angularVelocity: Float var currentAngle: Float let axis: SIMD3<Float> init(radius: Float, period: Double, axis: SIMD3<Float> = [0, 1, 0]) { self.radius = radius // 周期から角 度を計算( 1周 = 2π rad) self.angularVelocity = Float(2.0 * .pi / period) self.currentAngle = 0.0 self.axis = normalize(axis) } } コーディングガイドラインを参照させてリトライ ## コーディングガイドライン @.claude/RealityKitDevelopmentRules.md RealityKit OrbitAnimationを使って、 Entityを円周軌道上でアニメーションさせてい る 良さそうだが、複雑な構成 シーンや 動きだと厳しそう struct OrbitSystem: System { static let query = EntityQuery(where: .has(OrbitComponent.self)) init(scene: RealityKit.Scene) {} func update(context: SceneUpdateContext) { for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) { guard var orbitComponent = entity.components[OrbitComponent.self] else { continue } // 角度を更新 orbitComponent.currentAngle += orbitComponent.angularVelocity * Float(context.deltaTime) // 軌道上 位置を計算 // Y軸まわり 回転 場合: X-Z平面上で円運動 if orbitComponent.axis == SIMD3<Float>(0, 1, 0) { let x = cos(orbitComponent.currentAngle) * orbitComponent.radius let z = sin(orbitComponent.currentAngle) * orbitComponent.radius entity.position = SIMD3<Float>(x, 0, z) } else { // 他 軸 場合 回転行列を使用 let rotationMatrix = simd_float3x3( simd_quatf(angle: orbitComponent.currentAngle, axis: orbitComponent.axis) ) let initialPosition = SIMD3<Float>(orbitComponent.radius, 0, 0) entity.position = rotationMatrix * initialPosition } // コンポーネントを更新 entity.components[OrbitComponent.self] = orbitComponent } } } ちゃんとSystemを登録したり、 Componentを追加している System.update内でもちゃんとXZ平面で 座 標を計算して更新している
  10. タイトル Deep Researchでコーディングガイドラインを作る ChatGPT Deep Researchで調査・整理させる という方法もある # RealityKit ECS(visionOS

    26.0)Swiftコーディングガイド ## RealityKitにおけるECS 基本概念 RealityKitで **Entity-Component-System (ECS)**アーキテクチャが採用されており、アプリ内 オブジェクトを3つ 要素に分離して扱います。各要素 役割 以下 とおりです。 - **Entity(エンティティ) ** – 3D空間上に配置するオブジェクトを表すコンテナです。自身 データや振る舞いを持たず、**識別子**・**親子構 **として機能し、複数 Componentをまとめます。 - **Component(コンポーネント) ** – Entityに付与できる属性や機能を表すデータです。**見た目(モデルやマテリアル) **や**物理特性**、**動作パラメータ **などを定義します。 - **System(システム) ** – 複数 Entityにまたがる共通 振る舞いを定義します。各フレームで実行され、シーン内 特定条件を満たすEntityを検索して更新します。 これらにより、RealityKitで オブジェクト データとロジックを分離して再利用性や拡張性を高めています。 ## Entity 作成と階層構 ...
  11. タイトル 自転しながら公転させる # プロンプト 赤い球体を赤いキューブに変更して、垂直方向から 30°傾いて自転しながら円周運動するようにしてください struct RotationSystem: System {

    static let query = EntityQuery(where: .has(RotationComponent.self)) init(scene: RealityKit.Scene) {} func update(context: SceneUpdateContext) { for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) { guard let rotationComponent = entity.components[RotationComponent.self] else { continue } // 現在 回転に追加 回転を適用 let deltaRotation = simd_quatf( angle: rotationComponent.angularVelocity * Float(context.deltaTime), axis: rotationComponent.axis ) entity.orientation = entity.orientation * deltaRotation } } } 自転軸まわり 回転を相対的に加えようとして、 傾きがどんどん変化してしまっている
  12. タイトル 自転しながら公転させる # プロンプト RotationSystem 実装だと、対象Entityに対してどんどん相対的に傾いた回転を加えてしまう で、予め cubeEntityをY軸から 30°傾けてから、Y軸周りに自転させるようにしてください。 RealityView

    { content in ... // RotationComponentを追加(垂直方向から30°傾けた軸で自転、周期5秒) // 垂直方向(Y軸)から30°傾けた軸を計算 let tiltAngle = Float(30.0 * .pi / 180.0) // 30度をラジアンに変換 let rotationAxis = SIMD3<Float>(sin(tiltAngle), cos(tiltAngle), 0) cubeEntity.components.set(RotationComponent(axis: rotationAxis, period: 5.0)) origin.addChild(cubeEntity) } struct RotationSystem: System { ... func update(context: SceneUpdateContext) { for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) { guard let rotationComponent = entity.components[RotationComponent.self] else { continue } // 現在 回転に追加 回転を適用 let deltaRotation = simd_quatf( angle: rotationComponent.angularVelocity * Float(context.deltaTime), axis: rotationComponent.axis // [0, 1, 0] ) entity.orientation = entity.orientation * deltaRotation } } } 予め傾かせてから、SystemでY 軸回りに自転させる 「自転軸を固定させて」などと指 示しても全く解消できなかった で、明示的に指示した
  13. タイトル 衛星も追加してみよう # プロンプト 赤いキューブ 実装を参考にして、さらに赤いキューブを周りを回る白いキューブを追加してください 白いキューブ 詳細設定... RealityView {

    content in ... // サイズ0.5 白いキューブを作成 let whiteCubeMesh = MeshResource.generateBox(size: 0.5) ... // y軸から30°傾けた回転を設定 let whiteTiltAngle = Float(30.0 * .pi / 180.0) let whiteTiltRotation = simd_quatf(angle: whiteTiltAngle, axis: SIMD3<Float>(1, 0, 0)) whiteCubeEntity.transform.rotation = whiteTiltRotation // OrbitComponentを追加(半径2.0m、周期1.0秒、Y軸まわり) whiteCubeEntity.components.set(OrbitComponent(radius: 1.5, period: 10.0, axis: [0, 1, 0])) // RotationComponentを追加(y軸方向で自転、周期1.0秒) whiteCubeEntity.components.set(RotationComponent(axis: [0, 1, 0], period: 1.0)) cubeEntity.addChild(whiteCubeEntity) } 赤いキューブ 自転によって、白いキューブが振 り回されて自転軸も変化してしまっている
  14. タイトル 衛星も追加してみよう # プロンプト 赤いキューブが自転すると、白いキューブが影響を受けて自転軸が変化してしまう問題を解決するため、それぞれ キューブ 親に 公転用 Container Entityを追加し、そ

    ContainerにOrbitComponentを、キューブ本体 Entityに自転軸 傾きや RotationComponentを適用するように変更してください RealityView { content in ... // === 赤いキューブ セットアップ === // 公転用 Container Entity let redOrbitContainer = Entity() redOrbitContainer.components.set(OrbitComponent(radius: 2.0, period: 10.0, axis: [0, 1, 0])) origin.addChild(redOrbitContainer) // サイズ1.0 赤いキューブを作成 let cubeEntity = ModelEntity(mesh: cubeMesh, materials: [redMaterial]) cubeEntity.components.set(RotationComponent(axis: [0, 1, 0], period: 1.0)) redOrbitContainer.addChild(cubeEntity) // === 白いキューブ セットアップ === // 公転用 Container Entity let whiteOrbitContainer = Entity() whiteOrbitContainer.components.set(OrbitComponent(radius: 1.5, period: 5.0, axis: [0, 1, 0])) redOrbitContainer.addChild(whiteOrbitContainer) // サイズ0.5 白いキューブを作成 let whiteCubeEntity = ModelEntity(mesh: whiteCubeMesh, materials: [whiteMaterial]) whiteCubeEntity.components.set(RotationComponent(axis: [0, 1, 0], period: 1.0)) whiteOrbitContainer.addChild(whiteCubeEntity) } 公転用 Container Entityと自転用 Entityを分けて、親 子関係 Entityでも自転軸 傾きによる影響が出ないように した これもなかなか解決できなかった で、明 示的に修正方針を指示した
  15. タイトル Blender MCPで天体 3Dモデルを作る # プロンプト あなた プロ 3Dモデラーです。 Blenderを使って太陽系

    太陽と各惑星、月 3Dモデルを作ってください。 なお、各天体 サイズ 同じ1.0とすること(モデル利用側でリサイズするため)、土星 リング トーラスで なく円盤で作ること。 プロシージャルテクスチャで あるが、太陽系 各天体 3D モデルとして いい感じ!土星 リングも出来てる! ※テクスチャや縞模様 調整で何度かやり取りした
  16. タイトル 太陽系シミュレーションを仕上げる # プロンプト 赤と白 キューブ 実装を参考に、太陽系 各天体を追加してください。 各天体 大きさ

    (太陽を1.0とする)や公転半径、自転半径、自転軸 傾き 実際 天文データを参考にしつつ、見やすさ ため適宜調整してください。
  17. タイトル 太陽系シミュレーションを仕上げる # プロンプト 太陽系 各天体 3DモデルをReality Composer Proで作成したSolarSystemというシーンから読み込んで、そ まま

    サイズで使うようにしてください。 なお、それぞれ 天体 Entity名 Sun, Mercury, Venus, etc. ようになっています。
  18. タイトル 天体をタッチして説明を表示 # プロンプト 各天体をタッチしたら、Attachmentでそ 天体 説明を2~2行程度で表示するようにしてください。 なお、同じ天体をタッチしたらAttachmentを非表示にしてください。 RealityView {

    content, attachments in ... } update: { content, attachments in // 選択された天体がある場合、新しいパネルを追加 if selectedCelestialBodyName != nil, let attachmentEntity = attachments.entity(for: "info") { attachmentEntity.name = "InfoPanel" attachmentEntity.position = [0, 1.5, 0] attachmentEntity.components.set(BillboardComponent()) content.add(attachmentEntity) } } attachments: { if let name = selectedCelestialBodyName, let description = selectedCelestialBodyDescription { Attachment(id: "info") { VStack(spacing: 8) { Text(name).font(.callout).bold() Text(description).font(.caption) } ... } } } Attachment 古い方法で実装されていたが、 ChatGPT Deep ResearchでvisionOS 26 新APIについて整理 させて、Claude Codeで参照させると、ちゃんと ViewAttachmentComponentを使ってくれた!
  19. タイトル Volume対応で手元でも見れる # プロンプト Immersive Spaceで表示しているも を、Volumeとしても表示したい で、Volume用 Windowを追加して、 origin.scaleを調整してVolumeで表示できるようにしてください

    WindowGroup(id: "SolarSystemVolume") { VolumeView() .environment(appModel) .frame(width: 600, height: 600) .frame(depth: 600) } .windowStyle(.volumetric) .windowResizability(.contentSize) .volumeWorldAlignment(.gravityAligned)
  20. タイトル • Claude Codeを使って空間アプリを作ってみたが、苦手そうな部分 あった が、思った以上 完成度だった • RealityKit ECSや最新

    APIなど 、Deep Researchなどでガイドライン を用意して参照させれ 、実装精度が上がった • 生成されたコードで 、3D空間内で 動作が意図通りにならないこともあり 、 そこ 手動で対応する必要がありそう • Blender MCPを使え 、簡単な 3Dモデルなら生成できて 、 シーン 操作や細かい変更作業もしてくれる • AIコーディング 空間アプリ開発でも大活躍! どんどん活用していきましょう! まとめ
  21. タイトル パノラマ画像生成 AI Skybox AIという360°パノラマ画像を生成できる AIがあり、Skyboxや球体 テ クスチャが生成できそうだが、これもダウンロードで有料プランへ 加入が必要でそ こそこ高い

    ChatGPTでも試したが、パノラマ画像自体 生成で きたが、繋ぎ目ができてしまって、連続的な 360°パノ ラマ画像 生成できなかった
  22. タイトル ChatGPT MCP Claude Code Web検索が苦手な で、WebSearchが必要な時 ChatGPT MCPを使ってChatGPT経由で検索させている ※具体的なやり方

    こちら 記事を参照 ショートカット経由で ChatGPTアプ リを操作する で、 Plusなど 定額 プランで利用できる!
  23. タイトル • Solar Textures • Blender MCP を試す • Claude

    CodeでiOS開発 - 実機とシミュレータを自動判別してビルド&実行さ せる • Claude Code 検索で ChatGPT Mac アプリ o3 Web検索を使用する 参考