WWDC Quick Look 💓 By SwiftGGTeam
Build spatial experiences with RealityKit

Build spatial experiences with RealityKit

元の動画を見る

ハイライト

RealityKit は visionOS 向けに Model3D、RealityView、volumetric window、ImmersiveSpace などの API を追加し、開発者が SwiftUI の馴染みのある構文で 2D ウィンドウ、体積ウィンドウ、没入空間に 3D コンテンツを配置し、Entity-Component-System アーキテクチャで動作をカスタマイズできます。

主要内容

3D コンテンツ統合の 3 つの方法

2D ウィンドウに 3D を埋め込む: 最も簡単な方法。Model3D ビューで USD を読み込み、resizablescaledToFit 修飾子を使います(03:21)。モデルは非同期読み込みで、placeholder をカスタマイズできます。

Volumetric window(体積ウィンドウ): 3D モデル専用のウィンドウで、任意角度からの閲覧と実寸スケールを両立します。幅 1 メートルのモデルは常に 1 メートルで表示されます(05:01)。

ImmersiveSpace(没入空間): App は空間内の任意位置に 3D 要素を置け、ウィンドウ境界を超えられます。完全没入体験に適しています(07:05)。

Entity-Component-System アーキテクチャ

Entity はコンテナオブジェクトで、単体ではレンダリングも物理シミュレーションも行いません。Component を追加して振る舞いを与えます(09:36)。

よく使う Component:

  • ModelComponent: 3D メッシュとマテリアルを描画
  • TransformComponent: 位置、回転、スケールを制御
  • HoverEffectComponent: 注視時にハイライト
  • InputTargetComponent + CollisionComponent: ジェスチャ入力を受け取る
  • SpatialAudioComponent: 空間オーディオ

System は定期的に実行されるロジックを含み、特定条件に合う Entity に作用します。ECS アーキテクチャは振る舞いとデータを分離し、拡張しやすくします。

RealityView

RealityView は SwiftUI 内の 3D ビューコンテナで、3 つのクロージャを提供します(12:27):

  • make: Entity を非同期読み込み・初期化
  • update: SwiftUI 状態変化に応じて Entity を更新
  • subscribe: RealityKit イベント(アニメーション完了、物理衝突など)を購読

座標系の約束: 原点は RealityView 中心、Y 軸上、Z 軸はユーザー方向、X 軸は右、1 単位 = 1 メートル。RealityView は SwiftUI と RealityKit 座標空間を変換する convert 関数を提供します。

詳細

2D ウィンドウで 3D モデルを表示する Model3D

03:40

import SwiftUI
import RealityKit

struct GlobeModule: View {
    var body: some View {
        Model3D(named: "Globe") { model in
            model
                .resizable()
                .scaledToFit()
        } placeholder: {
            ProgressView()
        }
    }
}

キーポイント:

  • Model3D は USD/USDZ を非同期読み込み対応で読み込む
  • resizablescaledToFit で利用可能空間に合わせる
  • placeholder は読み込み中に ProgressView を表示
  • USD ファイルをプロジェクトリソースとして追加する

Volumetric window

05:52

import SwiftUI
import RealityKit

struct WorldApp: App {
    var body: some SwiftUI.Scene {
        WindowGroup(id: "planet-earth") {
            Model3D(named: "Globe")
        }
        .windowStyle(.volumetric)
        .defaultSize(width: 0.8, height: 0.8, depth: 0.8, in: .meters)
    }
}

キーポイント:

  • .windowStyle(.volumetric) で体積ウィンドウになる
  • .defaultSize は実世界単位(メートル)で寸法を定義
  • 体積ウィンドウ内のモデルは実寸比を保ち、任意角度から見られる
  • openWindow 環境値で開く

ImmersiveSpace

07:31

import SwiftUI
import RealityKit

struct WorldApp: App {
    var body: some SwiftUI.Scene {
        ImmersiveSpace(id: "objects-in-orbit") {
            RealityView { content in
                // 3D コンテンツをついか
            }
        }
    }
}

キーポイント:

  • ImmersiveSpace は新しい Scene タイプ
  • openImmersiveSpace 環境値で非同期に開く
  • コンテンツは空間内の任意位置に配置可能
  • 複数の immersion style(mixed、progressive、full)がある

RealityView の非同期読み込みとエンティティ配置

12:54

import SwiftUI
import RealityKit

struct Orbit: View {
    var body: some View {
        RealityView { content in
            async let earth = ModelEntity(named: "Earth")
            async let moon = ModelEntity(named: "Moon")

            if let earth = try? await earth, let moon = try? await moon {
                content.add(earth)
                content.add(moon)
                moon.position = [0.5, 0, 0]
            }
        }
    }
}

キーポイント:

  • async let で複数モデルを並行読み込み
  • content.add() で Entity をシーンに追加
  • position[x, y, z] 配列でメートル単位の位置を設定
  • RealityKit 座標: Y 上、Z はユーザー方向、X は右

ドラッグ操作

18:31

import SwiftUI
import RealityKit

struct DraggableModel: View {
    var earth: Entity

    var body: some View {
        RealityView { content in
            content.add(earth)
        }
        .gesture(DragGesture()
            .targetedToEntity(earth)
            .onChanged { value in
                earth.position = value.convert(value.location3D,
                                               from: .local, to: earth.parent!)
            })
    }
}

キーポイント:

  • Entity は InputTargetComponentCollisionComponent の両方が必要
  • .targetedToEntity で特定 Entity にのみジェスチャを適用
  • value.location3D はジェスチャ位置。SwiftUI 座標から Entity の親座標へ変換
  • CollisionComponent は Reality Composer Pro で簡単に追加できる

アニメーション再生

14:56

import SwiftUI
import RealityKit

struct AnimatedModel: View {
    @State var subscription: EventSubscription?

    var body: some View {
        RealityView { content in
            if let moon = try? await Entity(named: "Moon"),
               let animation = moon.availableAnimations.first {
                moon.playAnimation(animation)
                content.add(moon)
            }
            subscription = content.subscribe(to: AnimationEvents.PlaybackCompleted.self) {
                // アニメーションかんりょうごのしょり
            }
       }
   }
}

キーポイント:

  • availableAnimations でモデル内の組み込みアニメーションを取得
  • playAnimation() で再生
  • content.subscribe() でイベント購読。ここでは再生完了
  • @State で購読を保持し、早期解放を防ぐ

軌道アニメーション

20:20

let orbit = OrbitAnimation(name: "Orbit",
                           duration: 30,
                           axis: [0, 1, 0],
                           startTransform: moon.transform,
                           bindTarget: .transform,
                           repeatMode: .repeat)

if let animation = try? AnimationResource.generate(with: orbit) {
    moon.playAnimation(animation)
}

キーポイント:

  • OrbitAnimation で Entity を親の周りに回転
  • duration: 30 は 30 秒で 1 周
  • axis: [0, 1, 0] は Y 軸回り
  • startTransform は現在位置から開始しジャンプを防ぐ
  • repeatMode: .repeat でループ再生

空間オーディオ

22:12

// オーディオソースとしてからの Entity をつくる
let audioSource = Entity()

// オーディオをしこうせいエミッターにせっていし、フォーカスどを 0.75 にする
audioSource.spatialAudio = SpatialAudioComponent(directivity: .beam(focus: 0.75))

// オーディオソースをかいてんし、おとをとくていほうこうへだす
audioSource.orientation = .init(angle: .pi, axis: [0, 1, 0])

// ループオーディオをよみこみ、さいせい
if let audio = try? await AudioFileResource(named: "SatelliteLoop",
                                            configuration: .init(shouldLoop: true)) {
    satellite.addChild(audioSource)
    audioSource.playAudio(audio)
}

キーポイント:

  • オーディオはデフォルトで空間化され、空間に存在するように聞こえる
  • directivity: .beam(focus: 0.75) で指向性ビームを作成
  • orientation で放出方向を制御
  • shouldLoop: true でループ再生
  • 3 種類: Spatial(指向)、Ambient(環境)、Channel(スピーカー直送)

カスタム Component と System

23:47

import RealityKit

// Component は Entity にふよされるデータ
struct TraceComponent: Component {
    var mesh = TraceMesh()
}

// Entity から Component をよみとり、またはかきこむ
func updateTrace(for entity: Entity) {
    var component = entity.components[TraceComponent.self] ?? TraceComponent()
    component.update()
    entity.components[TraceComponent.self] = component
}

// Codable Component は Reality Composer Pro でつかえる
struct PointOfInterestComponent: Component, Codable {
    var name = ""
}

キーポイント:

  • Component は Component プロトコルに準拠
  • entity.components[ComponentType.self] で読み書き
  • Codable 準拠で Reality Composer Pro で編集可能

24:51

import SwiftUI
import RealityKit

// System はロジックコードをふくむ
struct TraceSystem: System {
    static let query = EntityQuery(where: .has(TraceComponent.self))
    
    init(scene: Scene) {
        // しょきか
    }

    func update(context: SceneUpdateContext) {
        for entity in context.entities(Self.query, updatingSystemWhen: .rendering) {
            addCurrentPositionToTrace(entity)
        }
    }
}

// System をとうろく
struct MyApp: App {
    init() {
        TraceSystem.registerSystem()
    }
}

キーポイント:

  • System は System プロトコルに準拠
  • EntityQuery で System が対象とする Entity を定義
  • updatingSystemWhen: .rendering はレンダーフレームレートで更新
  • App の init()registerSystem() を呼び登録
  • System は App 内のすべての RealityKit コンテンツに自動適用

重要ポイント

  • やること: 学生が惑星をドラッグし軌道運動を観察できる 3D 太陽系教育 App を作る。

  • 価値: RealityKit の OrbitAnimation とドラッグジェスチャで抽象的天文概念を触れられる体験にできる。

  • 始め方: RealityView で惑星モデルを読み込み、DragGesture でドラッグ、OrbitAnimation で公転アニメーションを実装。

  • やること: 顧客が商品 3D モデルをあらゆる角度から見られる製品展示 App を開発する。

  • 価値: Volumetric window は実寸を保ち、顧客が商品の周りを歩いて細部を確認できる。

  • 始め方: WindowGroup + .windowStyle(.volumetric) で体積ウィンドウを作り、内部で Model3D に USDZ を読み込む。

  • やること: 音符が空間に浮かび空間オーディオを放つ没入型音楽ビジュアライゼーション App を作る。

  • 価値: SpatialAudioComponent で音に方向感を与え、パーティクルと組み合わせて没入感を演出できる。

  • 始め方: ImmersiveSpace を作り、Entity + SpatialAudioComponent で音源を配置し、カスタム System でパーティクル位置を更新。

  • やること: ユーザーが実際の部屋に仮想家具を置けるインテリアプレビューツールを開発する。

  • 価値: RealityKit のアンカーで 3D モデルを壁や床に固定でき、空間オーディオで部屋の音響を模擬できる。

  • 始め方: AnchorEntity で家具を平面にアンカーし、CollisionComponent でドラッグ位置調整を実装。

関連セッション

コメント

GitHub Issues · utterances