WWDC Quick Look 💓 By SwiftGGTeam
Customize spatial Persona templates in SharePlay

Customize spatial Persona templates in SharePlay

元の動画を見る

ハイライト

visionOS 2 にカスタム spatial template が追加され、開発者は各座席の 3D 空間上の位置、向き、ロールラベルを精密に定義でき、spatial Personas がアクティビティ段階に応じて自動的にグループ化して着席します。


主要内容

visionOS の FaceTime は spatial Personas で通話者が同じ部屋にいる感覚を実現します。誰かが SharePlay アクティビティを開始すると、システムは spatial template(空間テンプレート)に従い全参加者を再配置——各参加者の共有 App に対する初期位置を定義します。visionOS 1 には 3 つの組み込みテンプレート:side-by-side(弧状配置、動画視聴向け)、conversational(開いた円、音楽向け)、surround(共有コンテンツを囲む閉じた円、3D モデリング向け)。

この 3 つではすべてのシナリオをカバーできません。ボードゲームでは 2 人のプレイヤーが向かい合い、残りは観戦。カードゲームではディーラーとプレイヤーが両側に分かれます。visionOS 2 のカスタム spatial template がこの課題を解決。開発者は各「座席」の 3D 空間上の位置と向きを精密に定義し、座席にロールラベル(「青チーム」「赤チーム」「現在のプレイヤー」など)を指定。参加者が参加すると、システムがロールに基づき spatial Persona を対応する座席へ移動します。

プレゼンター Ethan は「Guess Together」という言葉当てゲームで完全デモ。4 段階(ウェルカム、カテゴリ選択、チーム編成、ゲーム)があり、各段階で異なるテンプレート——カテゴリ選択はシステムデフォルトの side-by-side、チーム編成とゲーム段階は青チーム/赤チーム/現在のプレイヤー/相手チームの座席を分けるカスタム template。Kevin が後半でカスタム template 設計の核心原則を共有。

詳細

カスタム Spatial Template の定義

カスタムテンプレートは SpatialTemplate プロトコルに準拠する構造体。核心は elements 配列で、各要素は位置とオプションのロールを持つ seat(座席)(14:59)。

Guess Together のチーム編成段階テンプレートの例:

import GroupActivities

struct TeamSelectionTemplate: SpatialTemplate {
    enum Role: String, SpatialTemplateRole {
        case blueTeam
        case redTeam
    }

    let elements: [any SpatialTemplateElement] = [
        // Blue team:
        .seat(position: .app.offsetBy(x: -2.5, z: 3.5), role: Role.blueTeam),
        .seat(position: .app.offsetBy(x: -3.0, z: 3.0), role: Role.blueTeam),
        .seat(position: .app.offsetBy(x: -3.5, z: 2.5), role: Role.blueTeam),

        // Starting positions:
        .seat(position: .app.offsetBy(x: 0, z: 4)),
        .seat(position: .app.offsetBy(x: 1, z: 4)),
        .seat(position: .app.offsetBy(x: -1, z: 4)),
        .seat(position: .app.offsetBy(x: 2, z: 4)),
        .seat(position: .app.offsetBy(x: -2, z: 4)),

        // Red team:
        .seat(position: .app.offsetBy(x: 2.5, z: 3.5), role: Role.redTeam),
        .seat(position: .app.offsetBy(x: 3.0, z: 3.0), role: Role.redTeam),
        .seat(position: .app.offsetBy(x: 3.5, z: 2.5), role: Role.redTeam)
    ]
}

キーポイント:

  • SpatialTemplate プロトコルは elements 配列のみを要求(12:32
  • 座席位置は .app.offsetBy(x:z:) で定義。共有 App を原点とし、単位はメートル(12:09
  • ロールは SpatialTemplateRole プロトコルに準拠する String 列挙で定義。座席に付与すると特定ロールの参加者用に座席を確保(13:49
  • ロールのない座席は elements 配列の順序で埋まるため、順序が参加者の入場順序を決定(30:41

テンプレートの有効化とロール割り当て

カスタムテンプレートの設定は 1 行(14:59):

systemCoordinator.configuration.spatialTemplatePreference = .custom(TeamSelectionTemplate())

参加者がチームに参加したら assignRole を呼び、対応座席へ移動(15:39):

systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam)

ロールなし座席(観客席など)に戻るには resignRole16:00):

systemCoordinator.resignRole()

キーポイント:

  • systemCoordinatorGroupSession から取得。空間動作設定の核心オブジェクト
  • ロール割り当て後、FaceTime が参加者の spatial Persona をそのロールの空席へ自動移動
  • ロール退出時は resignRole()。システムがロールなし座席に戻す

座席の向き(Seat Direction)

デフォルトでは全座席が共有 App を向きますが、カスタムテンプレートで各座席の向きを精密制御可能。ゲームでは現在のプレイヤーがチームメイトを向き、チームメイトが現在のプレイヤーを向く必要がある(18:42):

struct GameTemplate: SpatialTemplate {
    enum Role: String, SpatialTemplateRole {
        case player
        case activeTeam
    }

    var elements: [any SpatialTemplateElement] {
        let activeTeamCenterPosition = SpatialTemplateElementPosition.app.offsetBy(x: 2, z: 3)

        let playerSeat = SpatialTemplateSeatElement(
            position: .app.offsetBy(x: -2, z: 3),
            direction: .lookingAt(activeTeamCenterPosition),
            role: Role.player
        )

        let activeTeamSeats: [any SpatialTemplateElement] = [
            .seat(
                position: activeTeamCenterPosition.offsetBy(x: 0, z: -0.5),
                direction: .lookingAt(playerSeat),
                role: Role.activeTeam
            ),
            .seat(
                position: activeTeamCenterPosition.offsetBy(x: 0, z: 0.5),
                direction: .lookingAt(playerSeat),
                role: Role.activeTeam
            )
        ]

        let audienceSeats: [any SpatialTemplateElement] = [
            .seat(position: .app.offsetBy(x: 0, z: 5)),
            .seat(position: .app.offsetBy(x: 1, z: 5)),
            .seat(position: .app.offsetBy(x: -1, z: 5)),
            .seat(position: .app.offsetBy(x: 2, z: 5)),
            .seat(position: .app.offsetBy(x: -2, z: 5))
        ]

        return audienceSeats + [playerSeat] + activeTeamSeats
    }
}

キーポイント:

  • direction: .lookingAt(...) で指定位置または別の座席オブジェクトを向く(31:35
  • direction: .alignedWith(appAxis: .z) で App の座標軸に沿って整列(31:46
  • direction: .lookingAt(.app).rotatedBy(.degrees(30)) で初期方向から指定角度回転(32:02
  • 観客席は direction 不要。デフォルトで App を向く

Group Immersive Space

ゲーム段階では現在のプレイヤーに現在のパズルを表示。group immersive space を有効にすると、没入空間の座標原点が spatial template の原点と一致し、座席位置で 3D 要素を直接配置可能(21:41):

systemCoordinator.configuration.supportsGroupImmersiveSpace = true

キーポイント:

  • デフォルトでは SharePlay の immersive space はプライベート。参加者は互いの spatial Personas を見られない
  • supportsGroupImmersiveSpace = true で空間が共有され、全参加者が同一空間内に
  • RealityKit と GroupActivities はデフォルトでメートル単位。座席位置を RealityKit エンティティ位置に変換不要で直接使用可能

Xcode 16 で FaceTime 通話をシミュレート

Xcode 16 は visionOS シミュレーターで FaceTime 通話をシミュレート可能(08:28)。Features > FaceTime でリモート参加者数を選択し、実機なしで SharePlay アプリを完全テスト。シミュレート参加者はロールを自動割り当てしないため、ロール付き座席は占有されない。

重要ポイント

  • 何をするか:ボード/カードゲーム向けに対戦型座席テンプレートを設計——2 人のプレイヤー座席を向かい合わせ、観客席を側面に。なぜ価値があるか:「対戦相手」と「観戦者」の空間区分が必要で、組み込みテンプレートでは不可能。始め方:2 つのロール付き(player1player2 など)向かい合わせ座席と、ロールなし観客席を定義。ゲーム開始時に assignRole

  • 何をするか:協力ホワイトボード/デザインツールで lookingAt を使い、参加者の座席を App ではなく互いに向ける。なぜ価値があるか:協作では画面より議論が重要。向かい合うことでジェスチャーと表情のコミュニケーションが自然に。始め方.lookingAt() で隣接座席の direction を向かい側の座席位置に向ける。

  • 何をするか:多段階アクティビティ(「チーム選択 → ゲーム → 集計」など)で段階ごとに異なるテンプレートに切り替え、UI 状態と同期。なぜ価値があるか:各段階で参加者の関係が異なる——選択時は差なし、ゲーム中はロール分担。マッチするテンプレートで空間配置が現在の関係を反映。始め方:状態切り替えメソッドで spatialTemplatePreference を更新。assignRole または resignRole を同期呼び出し。遷移中間状態でのテンプレート切り替えを避ける。

  • 何をするか:group immersive space で座席位置に基づき 3D ヒント(パズルパネル、小道具)を配置。なぜ価値があるか:共有空間の原点がテンプレート原点と一致。特定座席の前方に UI 要素を精密配置でき、参加者が首を回す必要がない。始め方supportsGroupImmersiveSpace を有効化。座席 position に小さなオフセットを加え RealityKit エンティティを配置。

関連セッション

コメント

GitHub Issues · utterances