取り組みの背景 — なぜ今 visionOS 開発なのか
Apple Vision Pro の登場により、空間コンピューティングは開発者にとって新たなフロンティアとなりました。2026年現在、visionOS エコシステムは急速に成長しており、App Store には空間体験に特化したアプリが続々と登場しています。
しかし、visionOS アプリの開発は従来の iOS/macOS 開発とは大きく異なります。3D空間内でのUI設計、RealityKit によるコンテンツレンダリング、ハンドジェスチャーや視線入力といった新しいインタラクションモデルへの対応が求められます。
Rork Max は Swift ネイティブコードを AI が生成するプラットフォームです。visionOS フレームワーク(SwiftUI + RealityKit)を活用したアプリ開発においても、プロンプトベースで空間体験の設計から実装までを効率的に進められます。
ここで扱うのはRork Max を使って visionOS 対応の空間コンピューティングアプリを構築するための実践的な手法を、コード例とともに解説します。
対象読者 : SwiftUI の基礎知識があり、Rork Max で iOS アプリを開発した経験がある方
前提環境 :
Rork Max Plan($200/月)
Xcode 16 以降 + visionOS SDK
Apple Vision Pro 実機またはシミュレータ
visionOS アプリのアーキテクチャを理解する
Window・Volume・Immersive Space の3つのシーンタイプ
visionOS アプリは3つのシーンタイプで構成されます。これらの違いを理解することが空間アプリ設計の第一歩です。
Rork Max でプロジェクトを作成する際は、どのシーンタイプを主軸にするかをプロンプトで明示すると、AI が適切なアーキテクチャを提案してくれます。
// visionOS アプリのエントリポイント
// Window + Immersive Space の組み合わせパターン
import SwiftUI
@main
struct SpatialGalleryApp : App {
@State private var immersionStyle: ImmersionStyle = .mixed
var body: some Scene {
// メインウィンドウ(2D UI)
WindowGroup {
ContentView ()
}
// 没入型空間(3D体験)
ImmersiveSpace ( id : "gallerySpace" ) {
ImmersiveGalleryView ()
}
. immersionStyle ( selection : $immersionStyle, in : .mixed, .full)
}
}
// 期待される動作:
// - アプリ起動時に2Dウィンドウが表示される
// - ユーザーの操作で没入型空間に遷移する
Rork Max でのプロジェクト設定
Rork Max に visionOS プロジェクトを生成させるには、以下のようなプロンプト構造が効果的です。
Create a visionOS app with:
- A main window showing a gallery grid of 3D art pieces
- A volumetric view for previewing selected art in 3D
- An immersive space for the full exhibition experience
- Hand gesture support for rotating and scaling 3D objects
- Spatial audio tied to each art piece location
Target: visionOS 2.0+, Swift 6, SwiftUI + RealityKit
ポイントは、各シーンタイプの目的とインタラクション方式を具体的に記述することです。曖昧なプロンプトでは、AI が Window のみの平面的なアプリを生成してしまいがちです。
RealityKit による3Dコンテンツの配置と管理
Entity-Component-System(ECS)アーキテクチャ
RealityKit は Entity-Component-System(ECS)パターンを採用しています。このパターンでは、すべての3Dオブジェクト(Entity)にコンポーネントを組み合わせて機能を付与します。
import RealityKit
import RealityKitContent
struct ImmersiveGalleryView : View {
@State private var artPieces: [ArtPiece] = []
var body: some View {
RealityView { content in
// 環境光を設定
let environment = try? await EnvironmentResource (
named : "gallery_lighting"
)
if let env = environment {
content. add (env)
}
// 3Dモデルを円形に配置
for (index, piece) in artPieces. enumerated () {
let angle = Float (index) / Float (artPieces. count )
* 2 * . pi
let radius: Float = 3.0
let entity = try? await Entity (
named : piece.modelName,
in : realityKitContentBundle
)
if let model = entity {
model.position = SIMD3 < Float > (
cos (angle) * radius,
1.5 , // 目の高さに配置
sin (angle) * radius
)
// 中心を向くように回転
model. look (
at : SIMD3 < Float > ( 0 , 1.5 , 0 ),
from : model.position,
relativeTo : nil
)
// タップ可能にするコンポーネント
model.components. set (
InputTargetComponent ()
)
model.components. set (
CollisionComponent (
shapes : [. generateBox (
size : [ 0.5 , 0.5 , 0.5 ]
)]
)
)
content. add (model)
}
}
}
. gesture (tapGesture)
}
// 3Dオブジェクトへのタップジェスチャー
private var tapGesture: some Gesture {
SpatialTapGesture ()
. targetedToAnyEntity ()
. onEnded { value in
let entity = value.entity
// 選択アニメーションを再生
playSelectionAnimation ( on : entity)
}
}
private func playSelectionAnimation ( on entity: Entity) {
// スケールアニメーション
var transform = entity.transform
transform.scale = SIMD3 < Float > ( repeating : 1.3 )
entity. move (
to : transform,
relativeTo : entity.parent,
duration : 0.3 ,
timingFunction : .easeInOut
)
// 0.3秒後に元に戻す
Task {
try? await Task. sleep ( for : . seconds ( 0.3 ))
transform.scale = SIMD3 < Float > ( repeating : 1.0 )
entity. move (
to : transform,
relativeTo : entity.parent,
duration : 0.3 ,
timingFunction : .easeInOut
)
}
}
}
// 期待される動作:
// - 没入空間内に3Dモデルが円形に配置される
// - 各モデルは中心を向き、タップ操作で選択アニメーションが再生される
Reality Composer Pro との連携
3Dアセットの管理には Reality Composer Pro が不可欠です。Rork Max プロジェクトでは RealityKitContent パッケージとしてアセットバンドルを管理します。
// Reality Composer Pro で作成したシーンを読み込む
let sceneEntity = try await Entity (
named : "GalleryScene" ,
in : realityKitContentBundle
)
// シーン内の特定エンティティを検索
if let pedestal = sceneEntity. findEntity ( named : "Pedestal" ) {
pedestal.components. set (
HoverEffectComponent () // 視線ホバー時のエフェクト
)
}
// 期待される動作:
// - Reality Composer Pro で設計したシーンがそのまま表示される
// - Pedestal エンティティに視線ホバーエフェクトが適用される
ハンドトラッキングと空間ジェスチャーの実装
ARKit のハンドトラッキング API
Vision Pro の特徴的な入力方式であるハンドトラッキングを活用するには、ARKit の Hand Tracking Provider を使用します。
import ARKit
class HandTrackingManager : ObservableObject {
private let session = ARKitSession ()
private let handTracking = HandTrackingProvider ()
@Published var leftHandPosition: SIMD3< Float > ?
@Published var rightHandPosition: SIMD3< Float > ?
@Published var isPinching: Bool = false
func startTracking () async {
// ハンドトラッキングの認可チェック
guard HandTrackingProvider.isSupported else {
print ( "Hand tracking is not supported" )
return
}
do {
try await session. run ([handTracking])
} catch {
print ( "Failed to start hand tracking: \( error ) " )
return
}
// ハンドアンカーの更新をリアルタイム監視
for await update in handTracking.anchorUpdates {
let hand = update.anchor
guard hand.isTracked else { continue }
// 手首の位置を取得
if let wrist = hand.skeleton. joint (.wrist) {
let position = hand.originFromAnchorTransform
* wrist.anchorFromJointTransform
let worldPos = SIMD3 < Float > (
position.columns.3.x,
position.columns.3.y,
position.columns.3.z
)
await MainActor. run {
switch hand.chirality {
case .left :
self .leftHandPosition = worldPos
case .right :
self .rightHandPosition = worldPos
}
}
}
// ピンチジェスチャーの検出
// 親指と人差し指の距離で判定
if let thumbTip = hand.skeleton. joint (.thumbTip),
let indexTip = hand.skeleton. joint (
.indexFingerTip
) {
let thumbPos = hand.originFromAnchorTransform
* thumbTip.anchorFromJointTransform
let indexPos = hand.originFromAnchorTransform
* indexTip.anchorFromJointTransform
let distance = simd_distance (
SIMD3 < Float > (
thumbPos.columns.3.x,
thumbPos.columns.3.y,
thumbPos.columns.3.z
),
SIMD3 < Float > (
indexPos.columns.3.x,
indexPos.columns.3.y,
indexPos.columns.3.z
)
)
await MainActor. run {
self .isPinching = distance < 0.02
// 2cm以下でピンチと判定
}
}
}
}
}
// 期待される動作:
// - 左右の手の位置がリアルタイムで更新される
// - 親指と人差し指の距離が2cm以下になるとピンチ状態になる
SwiftUI ジェスチャーとの組み合わせ
RealityView 内のエンティティに対しては、SwiftUI のジェスチャーモディファイアを活用するのが最も簡潔です。
struct InteractiveModelView : View {
@State private var rotation: Rotation3D = . identity
@State private var scale: Double = 1.0
var body: some View {
RealityView { content in
if let model = try? await Entity (
named : "ProductModel" ,
in : realityKitContentBundle
) {
model.components. set ( InputTargetComponent ())
model.components. set (
CollisionComponent (
shapes : [. generateConvex (
from : model
)]
)
)
content. add (model)
}
}
// 回転ジェスチャー
. gesture (
RotateGesture3D ()
. targetedToAnyEntity ()
. onChanged { value in
let entity = value.entity
entity.orientation = simd_quatf (
value.rotation
)
}
)
// ピンチによるスケール変更
. gesture (
MagnifyGesture ()
. targetedToAnyEntity ()
. onChanged { value in
let entity = value.entity
let newScale = Float (
max ( 0.5 , min ( 3.0 , value.magnification))
)
entity.scale = SIMD3 < Float > (
repeating : newScale
)
}
)
}
}
// 期待される動作:
// - 3Dモデルを両手の回転ジェスチャーで回せる
// - ピンチイン・アウトでモデルのスケールを0.5〜3.0倍に変更できる
空間オーディオの実装
Vision Pro の空間オーディオは、3D空間内の音源位置に基づいて自然な立体音響を再現します。RealityKit のオーディオコンポーネントを使えば、エンティティに音源を紐づけるだけで空間オーディオが実現できます。
func attachSpatialAudio (
to entity: Entity,
audioFile : String
) async {
// 空間オーディオリソースの読み込み
guard let resource = try? await AudioFileResource (
named : audioFile,
configuration : AudioFileResource. Configuration (
shouldLoop : true ,
shouldRandomizeStartTime : false
)
) else {
print ( "Failed to load audio: \( audioFile ) " )
return
}
// 空間オーディオコンポーネントの設定
let audioSource = Entity ()
audioSource.spatialAudio = SpatialAudioComponent (
directivity : . beam ( focus : 0.5 )
// 指向性: beamは特定方向に音を集中させる
)
audioSource.spatialAudio ? .gain = -10 // 音量(dB)
entity. addChild (audioSource)
// 再生開始
audioSource. playAudio (resource)
}
// 期待される動作:
// - エンティティの位置に基づいて空間オーディオが再生される
// - ユーザーが音源に近づくと音量が上がり、遠ざかると下がる
// - 指向性設定により、正面からの音が最も大きく聞こえる
パフォーマンス最適化のベストプラクティス
visionOS アプリはリアルタイムレンダリングが前提です。フレームレートの低下はユーザーに不快感(VR酔い)を与えるため、パフォーマンス最適化は特に重要です。
3Dアセットの最適化チェックリスト
メモリ管理とエンティティのライフサイクル
class SceneManager : ObservableObject {
private var loadedEntities: [ String : Entity] = [ : ]
private let maxCachedEntities = 20
/// エンティティを遅延読み込み(LRUキャッシュ)
func loadEntity ( named name: String ) async -> Entity ? {
// キャッシュから取得
if let cached = loadedEntities[name] {
return cached. clone ( recursive : true )
}
// 新規読み込み
guard let entity = try? await Entity (
named : name,
in : realityKitContentBundle
) else {
return nil
}
// キャッシュが上限を超えたら古いものを除去
if loadedEntities. count >= maxCachedEntities {
let oldest = loadedEntities. keys . first !
loadedEntities. removeValue ( forKey : oldest)
}
loadedEntities[name] = entity
return entity. clone ( recursive : true )
}
/// シーン遷移時のクリーンアップ
func clearScene ( content : RealityViewContent) {
content.entities. forEach { entity in
entity. removeFromParent ()
}
// 不要なエンティティのキャッシュもクリア
loadedEntities. removeAll ()
}
}
// 期待される動作:
// - 3Dモデルが LRU キャッシュで管理され、メモリ使用量が一定以下に保たれる
// - シーン遷移時にエンティティとキャッシュが適切にクリーンアップされる
Instruments によるプロファイリング
Rork Max で生成したプロジェクトを Xcode で開いた後、Instruments の RealityKit Trace テンプレートを使用すると、以下の項目を詳細にプロファイリングできます。
Render Time : 各フレームのレンダリング時間(16.6ms 以内が目標)
Entity Count : シーン内のアクティブなエンティティ数
Texture Memory : テクスチャが消費するメモリ量
Physics Simulation : 物理演算の処理時間
90fps を安定的に維持することが、快適な空間体験の最低条件です。フレーム落ちが頻発する場合は、まずポリゴン数の削減とテクスチャの圧縮から着手するのが効果的です。
Rork Max プロンプト設計の実践テクニック
visionOS アプリを Rork Max で効率よく生成するために、プロンプトの書き方にはいくつかのコツがあります。
空間レイアウトを言語化する
3D空間のレイアウトは、テキストだけで伝えるのが難しい領域です。座標系(X: 左右、Y: 上下、Z: 前後)を明示し、配置関係を具体的に記述しましょう。
Place the main control panel as a Window 1.5 meters
in front of the user at eye level (Y=1.6m).
Add a Volume with a 3D globe model 0.8 meters
to the right of the panel.
The immersive space should display a starfield
environment at 10 meters distance in all directions.
インタラクション仕様を明確にする
When the user looks at a planet entity for 1.5 seconds,
show a tooltip Window anchored 20cm above the entity.
When the user pinches the planet,
start a rotation animation at 10 degrees/second.
Two-hand scale gesture should resize between 0.3x and 5.0x.
このように、トリガー条件・アクション・パラメータを数値で指定すると、AI が意図通りのインタラクションを実装してくれる確率が大幅に上がります。
個人開発者の視点から(実体験メモ)
ここまでの要点
visionOS の空間コンピューティングアプリ開発は、従来のモバイルアプリ開発とは異なるスキルセットが求められます。しかし、Rork Max の AI コード生成を活用すれば、SwiftUI + RealityKit の複雑なボイラープレートを大幅に省略し、体験設計とビジネスロジックに集中できます。
本記事で紹介した要点をまとめます。
visionOS の3つのシーンタイプ(Window・Volume・Immersive Space)を適切に使い分けること
RealityKit の ECS アーキテクチャを理解し、Entity にコンポーネントを組み合わせて機能を構築すること
ハンドトラッキングと SwiftUI ジェスチャーを組み合わせて直感的なインタラクションを実現すること
空間オーディオでリアルな没入感を演出すること
パフォーマンス最適化を怠らず、90fps を安定的に維持すること
空間コンピューティングの市場はまだ初期段階にあり、今参入することで先行者優位を確立できるチャンスがあります。まずは小さな Volume アプリから始めて、段階的に Immersive Space を活用した本格的な空間体験に発展させていくのが堅実なアプローチです。