ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
SharePlayでの空間Personaテンプレートのカスタマイズ
visionOSのSharePlay体験で空間Personaのカスタムテンプレートを使用し、アプリに応じてPersonaの配置を微調整する方法を確認しましょう。サンプルアプリでSharePlayを使用して、空間Personaのカスタムテンプレートを導入する方法、シート間で参加者を移動する方法、シミュレータで変更をテストする方法を説明します。体験の魅力を高めるカスタム空間テンプレートをデザインするためのベストプラクティスもご紹介します。
関連する章
- 0:00 - Introduction
- 1:01 - SharePlay on visionOS
- 4:50 - Build "Guess Together"
- 23:41 - Play Guess Together
- 25:13 - Design for Spatial Personas
リソース
関連ビデオ
WWDC23
WWDC22
WWDC21
-
ダウンロード
― こんにちはEthanです ― Kevinです 私たちは Spatial FaceTimeチームのエンジニアです 本セッションでは 空間Personaの臨場感を活用して visionOSアプリを構築するプロセスを ご紹介します その過程で SharePlayアプリの開発中に使用できる 新しいXcodeの機能と GroupActivities APIについて説明します このセッションは 3つのパートに分かれています 最初に visionOSの FaceTimeとSharePlayについて説明します そのあと「Guess Together」という サンプルアプリを構築しながら 空間Personaの カスタムテンプレートを定義するための 新しいAPIについて説明します また FaceTime通話をシミュレートする 新しいサポートを 使用して visionOS SharePlayアプリを シミュレータでテストする方法も紹介します 最後に Kevinが 空間Persona向けの優れたSharePlay体験の 設計方法を説明します では visionOSの FaceTimeとSharePlayから始めましょう
visionOSのFaceTimeは 空間Personaを活用して 迫力のある臨場感を生み出します FaceTimeと空間Personaによって まるで同じ部屋にいるような感覚になります SharePlayアプリは 本セッションで扱う フリーボードアプリと同様に この体験の核となる部分です SharePlayを採用することで FaceTimeが提供する共有コンテキストを 自分のアプリに拡張することができます
visionOSでSharePlayを採用する時は アプリの視覚的な 一貫性を保つ必要があります アプリのUIは 通話の参加者全員の環境で 同期されている必要があります そしてFaceTimeは 一貫性のある方法で 参加者をアプリ内に配置することで 空間的なコンテキストが共有されることを 維持する役割を果たします この視覚的および空間的な一貫性が 組み合わさることで 参加者が共有スペースで同じ1つのアプリを 使用しているという錯覚を生み出します コミュニケーションをとるには アプリのUIを指差したり ジェスチャを使うことができます 物理的な共有スペースで ホワイトボードを囲んで集まっている時と 何も変わりません 典型的なvisionOSでのFaceTime通話は 空間Personaが 円形に配置された状態から始まります 参加者同士がやり取りしやすい配置です この状態から 参加者はいつでも SharePlayアクティビティを開始でき その通話は 空間テンプレートに 移行します そして 各参加者の空間Personaが 新しく共有されたアプリを基準に 再配置されます 重要なのは このテンプレートは 各参加者の開始位置 または 座席を定義するだけだということです 参加者は1人 また1人と いつでも 最初のテンプレートの位置から離れ スペース内を動き回ることができます ただし いずれかの参加者がデバイスの Digital Crownを使用して 中央に再配置すると すべての参加者が元の開始位置に戻ります
GroupActivitiesフレームワークには SharePlayアプリで実装できる さまざまなシステム組み込みの 空間テンプレートが用意されています 例えば ビデオアプリでは これまで見てきた 横並びテンプレートがよく使われます このテンプレートでは 前面のアプリに対して 参加者が弧を描くように配置されます 一方 音楽アプリには 会話型のテンプレートがおすすめです このテンプレートでは 参加者は開いた円形に配置され その円の端の欠けている部分に アプリが配置されます また 3Dモデリングアプリを見る時は 円形テンプレートがよく使われます 参加者は中心に置かれた共有ボリュームを 取り囲むように円状に配置されます
これらのテンプレートはいずれも 共有するアプリを基準に席を定義します 空間Personaは 共有アクティビティの開始時に配置され その前に共有スペース内のどこにいたかは 関係ありません では アクティビティが これらの 組み込みのシステムテンプレートに 適さない場合はどうでしょうか 例えば チェスアプリを作成していて 2人のプレーヤーを向かい合わせに配置し 残りの参加者は観客として 脇に配置したい場合があります また カードゲームの場合は ディーラーが他のプレーヤーの向かい側に 配置されるべきでしょう visionOS 2では 新機能として カスタム空間テンプレートの作成ができます 共有するシーンに合わせて 空間Personaの配置を アプリで完全に制御できます カスタム空間テンプレートと それが解き放つ visionOSのユニークなソーシャル体験の 可能性に本当にワクワクしています
このセッションの次のパートでは より高度なSharePlayの概念に 焦点を当てます SharePlay、Group Activities visionOSを初めて使う方は 「Build custom experiences with Group Activities」と 「Build spatial SharePlay experiences」 を先にご覧いただくと このセッションをより深く理解できます 前者では アプリの状態を アクティビティの参加者間で同期する方法を 後者では 空間SharePlayの概要を説明しています
それでは 私が取り組んできたサンプルアプリの 「Guess Together」を見ていきましょう 「Guess Together」は チームで行う言葉当てゲームです FaceTimeで 空間Personaを使用してプレイします ジェスチャーゲームに少し似ています 2つのチームに分かれて順番に行います 自分の番になると 秘密のフレーズが与えられます 有名人の名前やイディオムなどです そしてチームのメンバーに そのフレーズを当ててもらいます フレーズそのものを言わなければ どんな手段を使ってもかまいません Guess Togetherは 4つのステージで構成されています まずアプリを起動すると ウェルカムUIが表示され 現在のFaceTime通話で SharePlayグループセッションを 作成するように招待されます SharePlayが始まると カテゴリ選択ステージに進みます ここで 使用するカテゴリを決めます 例えば 歴史上の出来事から引用したフレーズや 野菜や果物といった もっとシンプルなものもあります 次はチームの選択です 青チームか赤チームのどちらかを選択します これでゲームが始まります Guess Togetherの画面に スコアボードとタイマーが表示されます アクティブプレーヤーの前には 2つ目のビューが表示され チームメンバーが当てる 秘密のフレーズが表示されます ここで SharePlayの開始前に表示される ウェルカムステージを除いて それ以外の各ステージには 空間テンプレートが必要です アプリの各画面に ユーザーインターフェイスを 実装する時と同じように visionOSのSharePlayアプリで 共有するシーンに対して 空間Personaをどのように配置するかを 慎重に検討する必要があります
カテゴリ選択画面の ユーザーインターフェイスは すべての参加者で共有し 誰でも簡単に使えるようにします この時点では 参加者に何の区別もなく アクティブプレーヤーはおらず プレーヤーはチームに分かれていません したがって ここでは システムに用意されている 横並びテンプレートが適しています これはGuess Togetherのような ウインドウ表示アプリのデフォルト設定です このテンプレートはまさに この目的のために設計されており 参加者は共有するアプリの方を向いて 弧を描くように配置され 全員がインターフェイスに 簡単にアクセスできます チーム選択画面も要件は同様ですが 大きな違いが1つあり この時点で参加者が それぞれのチームに分かれます そのため私はここで チームメンバー同士を 隣の席にしたらどうかと考えました それにはチーム別の席が必要になるため チーム選択画面では カスタム空間テンプレートを使います
最初 参加者はアプリの方を向いて 5つの席のいずれかに座っています 横並び型と同様です しかしチームに参加すると Guess Togetherは参加者を 青チームか赤チームの席に割り当てます そしてFaceTimeは実際に 参加者を新しい席に再配置し チームの他のメンバーと 隣り合わせの席になります
ゲームステージには 明らかに カスタム空間テンプレートが必要です 参加者は ここで複数の役割に分かれます アクティブプレーヤーや 相手チームのプレーヤーなどです
アクティブプレーヤーは スコアボードウインドウの左側に座ります 台座が目の前にあります チームのメンバーは その向かい側に座ります 共有するアプリの右側です 相手チームは スコアボードのウインドウの 正面に座ります
これがGuess Togetherです このセッションの次のパートで それぞれのテンプレートを 一緒に実装していきましょう これには SharePlayアクティビティ自体の 反復とテストが少し必要です 幸い これは新しいXcode 16で実現できます
Xcode 16では visionOSシミュレータで FaceTime通話をシミュレートできます これは visionOS向けSharePlayアプリ 開発において大きな変革です デバイスで実際にFaceTime通話を行わずに アプリを完全に 構築できるようになりました FaceTime通話のシミュレーションを 開始するには まずメニューバーで メニューを開きます 次はサブメニューです そこから リモート参加者の構成を選択します アプリは様々な構成で 試してみることが重要です こんな感じです 試してみましょう
XcodeでGuess Togetherプロジェクトを 開きました シミュレータで実行します
これがGuess Togetherです まだSharePlayセッションに 参加していないため ウェルカムステージが表示されています
マウスをメニューに動かして FaceTimeをアクティブにします サブメニューで を選択します
このように FaceTime通話の シミュレーションが始まります 空間ボタンを使用して 空間Personaを有効にします
ボタンが アクティブになり SharePlayを始める準備ができました クリックしてみましょう
アプリは現在 SharePlayセッションに参加中で カテゴリ選択ステージを表示しています 左にパンすると
シミュレートされた参加者が 横並びテンプレートに移動したのがわかります ではコードでどのように設定されているか 見てみましょう
Guess Togetherでは 現在のSharePlay グループセッションとのやり取りを SessionControllerクラスで管理しています 開いてみましょう
SessionControllerは 特定のSharePlayセッションにおける アプリの状態をすべて追跡して同期します この部分で セッション自体と 他の参加者と状態を同期するための GroupSessionMessengerと 空間Personaを設定するためのセッションのSystemCoordinatorを保存します さらに アプリの各種の状態を 更新するためのメソッドがあります チーム選択ステージに入る チームへ参加する ゲームを開始するなどです すでに多くは完成しているため 残りの部分を確認していきましょう まず チーム選択ステージの カスタム空間テンプレートを 設計して構成する必要があります それが終わったら ゲームステージに進み そのテンプレートを構成します そのあと ゲームステージの グループイマーシブ空間を構成し 現在のフレーズを表示する台座を アクティブプレーヤーの席の前に配置します
最後に アプリが完成したら FaceTime通話に参加し Guess Togetherを実際にプレイします 全体の体験がどのようにまとまるかを 理解できるでしょう
前に示したように チーム選択の テンプレートの完成形はこのようになります アプリの前に5つの観客席と 3人掛けのチーム席が2セットあります
まずは この5つの席について テンプレートを作成していきます 空間テンプレートを定義するには SpatialTemplateプロトコルに 準拠した型を作成します ここでは TeamSelectionTemplateという SpatialTemplateに準拠した 構造体を作成します
SpatialTemplateの主な要件は テンプレート要素の配列です
これらの要素は 所定の位置の席として定義されます
席には1つの空間Personaを設定できます あとは席の位置を決めるだけです
席の位置は共有するアプリの位置に対して 相対的に定義します 最初の自分の席は アプリの正面に配置したいので Xオフセットを0に設定し アプリの中心と一致させます Zオフセットを4に設定し アプリの正面4メートルに配置します 2つ目の席の Zオフセットは同じままにしますが 今回はXオフセットを1に設定し 最初の席から右に 1メートルのところに配置します まず appに自分の席を配置し appの前からZ軸方向に 4メートルずらします 2つ目の席は まず同じ位置に配置してから Xオフセットを調整して 右に1メートル動かします
3つ目も同じようにします 今回は左に1メートルずらします 残りの2つの席は それぞれ左右に さらに1メートルずらして配置します
ここまでで チーム選択のテンプレートを定義するための SpatialTemplateプロトコルに準拠した 構造体の作成まで完了しました テンプレート内の各席の 位置を定義するために アプリからオフセットされる位置を X軸とZ軸の両方の値で指定しました これらの値はメートル単位で指定します ここまでは順調ですが まだ赤チームと青チームの席が必要です 元の5つの席から斜めの方向に あと6席設定する必要があります さらに それぞれ青チームと赤チームの メンバー用の予約席として マークする必要があります まずは テンプレートの一番左の席から 左に0.5メートル 前に0.5メートルずらした位置に 席を作成します
同じパターンで 青チームの残りの2席を作成します ここで それらの席に何らかの方法で 青チームのマークを付ける必要があります これには SpatialTemplateの役割を使用します 席に役割を割り当てることで その役割に割り当てれらた参加者用に 席を予約することができます 役割を定義するには SpatialTemplateRoleプロトコルに 準拠した型を作成します そこで 生の値であるStringを使用して SpatialTemplateRoleに準拠した Roleという列挙型を作成します そのあと blueTeamとredTeamという役割を作成します
できました これで 青チームの席に blueTeamの役割を割り当てて blueTeamのメンバー用に予約できます
ここでは 青チームの席用に blueTeamの役割を SpatialTemplateRoleに準拠した 列挙型を使用して作成しました さらに テンプレートの最初の席から 斜めの位置に席を3つ配置し それぞれにその役割を割り当てました これで 青チームの参加者用に予約されます
赤チームの席についても同様に行います
これでテンプレートは完成です! 次に これをチーム選択ステージで アクティブにする必要があります これはSessionControllerで行います Guess Togetherで空間テンプレートの 管理に使用しているメソッドは updateSpatialTemplatePreferenceです teamSelectionのケースを更新し SystemCoordinator構成にアクセスします
組み込みの環境設定を設定する代わりに 新しいカスタムメソッドを 使用して TeamSelectionTemplateを渡します
これで カスタムテンプレートの 作成と設定は完了しましたが まだ重要なステップが残っています 役割の割り当てです ローカル参加者のチームが変更された時に FaceTimeに通知し 新しい席に移動することを システムに認識させる必要があります
Guess Togetherで役割の 割り当て処理に使用するメソッドは updateLocalParticipantRoleです まず現在のゲームステージを切り替えます それに応じて必要な役割が変わるからです
teamSelectionステージでは ローカル参加者の 空間テンプレートの役割が チームに応じて変わります では切り替えてみます これで 青チームの役割を 割り当てる準備ができました systemCoordinatorにアクセスし assignRoleメソッドを使います 先ほど定義した blueTeamの役割を渡します
赤チームについても同じパターンに従います
チームがnilの場合 観客席に座ることになりますが この席には役割はありません そのため systemCoordinatorでは resignRole()メソッドを使うだけで済みます ここで categorySelectionステージの 役割の更新についても確認しておきましょう カテゴリ選択では組み込みの 横並びテンプレートを使用しており 役割はありません このステージになった時には 必ず役割を破棄するようにします 前のステージから役割を 引き継がないようにするためです
これでOKです 試してみましょう
アプリをもう一度実行して 今回はチーム選択に進みます
赤チームに参加すると 赤チームの席に移動します
青チームに参加すると このように青チームに切り替わります
簡単に復習しましょう 空間テンプレートの席に役割を追加すると その役割の参加者用に その席が予約されることになります FaceTimeでは 関連する役割が参加者に 割り当てられるまでは その席は空席となります ローカル参加者に役割を割り当てるには systemCoordinatorの assignRoleメソッドを使います FaceTimeで その役割の席が空いていれば ローカル参加者の空間Personaが その席に移ります ローカル参加者を役割のない席に戻すには そのローカル参加者の役割を破棄します これを行うには systemCoordinatorの resignRoleメソッドを呼び出します
これで最初の作業は完了です あとの2つは もっと簡単に進むはずです それでは ゲームステージの 空間テンプレートに進みましょう
ゲームのテンプレートの完成形は このようになります アクティブプレーヤーがチームメンバーと 向かい合って座るようにします 順番が変わると 参加者の席が入れ替わります 相手チームのプレーヤーは 脇にある観客席に座ります ここまでは新しいことは何もなく チーム選択のテンプレートの作成と同じです しかし このテンプレートに追加したい カスタマイズが1つあります 席の向きです デフォルトでは 空間テンプレートの席は 共有するアプリの方を向いていますが 席の向きをカスタマイズして 任意の方向を向くように設定できます このテンプレートでは アクティブプレーヤーが アクティブチームの方を向き アクティブチームも アクティブプレーヤーの方を向くようにします 観客席についてはデフォルトのまま アプリを正面から見るようにしておくのが 適しています では ゲームのテンプレートを開きます このテンプレートの大半は すでに完成しているので ここでは細かい部分を見る必要はありません それぞれの席の向きの設定だけ行います テンプレートでは 3種類の席のセットが定義されています プレーヤー席 アクティブチーム席 観客席です
ここで アクティブチームの位置は このテンプレートを作成する時に 独自の変数として定義してあります そのため プレーヤー席の向きの設定時には その位置を再利用できます ではプレーヤー席が その位置を向くように設定します directionパラメータを指定し lookingAtメソッドを使います
これでプレーヤー席の向きが変わり アクティブチーム席の真ん中を 向くようになります 次に チーム席の向きを設定しましょう ここでもdirectionパラメータを指定します 今回は lookingAtメソッドに プレーヤー席を直接渡しています
これでテンプレートは完成です
次に これをゲームステージでアクティブにし Guess Togetherの役割割り当てロジックを 更新する必要があります SessionControllerに戻りましょう
まずは ゲームのテンプレートを設定します チーム選択で使用したのと同じパターンで 構成することから始めます
次は役割を割り当てます この場合 役割は アクティブプレーヤーによって決まります 3つの分岐を持つ条件文を作成します
1つ目はアクティブプレーヤー用 2つ目はアクティブチーム用 3つ目は観客用です ここでも systemCoordinatorの assignRoleメソッドを使います
アクティブチームについても同じようにします
最後の観客については 現在の役割を破棄するだけです
ではゲームステージを見てみましょう
ステージが始まると 自分は チームメンバーが座る席の方を向いています スコアボードは左側にあります
右側にいるのは観客です これにより Guess Togetherのユーザーに 優れた体験を提供できます なぜなら 自分の番が来ても そのたびに向きを変える必要がないからです 部屋の反対側のチーム席に シミュレートされた参加者が 1人も座っていないのはなぜかと 疑問に思うかもしれません これはシミュレートされた参加者が テンプレートで自分に役割を 割り当てることはないからです そのため常に 役割がない最初の 空いている席に座ることになります
2番目の作業はこれで完了です 続いて ゲームステージの イマーシブ空間について見ていきましょう ゲームステージでは 現在の秘密のフレーズを アクティブプレーヤーに表示する方法が 必要です ここで 2つ目のプライベートウインドウを用意して アクティブプレーヤーにだけ 表示することもできますが オープンなイマーシブ空間を用意して 空間的な共有コンテキストを 維持できるようにするとよいと思います
これにより 追加の共有UI要素を用意して アクティブプレーヤーの前に 配置することができます
デフォルトでは SharePlayでイマーシブ空間を開くと そのスペースは非公開になり それぞれの参加者に固有となります つまり イマーシブ空間にいる間は 参加者はお互いの空間Personaを 見ることができません イマーシブ空間を構築して 参加者間で共有する場合は SystemCoordinator構成で グループイマーシブ空間を選択できます グループイマーシブ空間の カスタム空間テンプレートを開発する際 テンプレート内の席に対して相対的に UI要素を配置できます テンプレートの原点は 共有スペースの原点でもあるためです 試してみましょう
まずは 共有のグループイマーシブ空間を用意します SystemCoordinator構成にアクセスし supportsGroupImmersiveSpace プロパティを trueに設定します
これで 秘密のフレーズの台座を 追加する準備ができました これについてはすでに用意してあります 確かめてみましょう ImmersiveSpaceグループを展開し PhraseDeckPodiumViewを開きます
台座は SwiftUIビューをアタッチした RealityViewとして定義してあります 台座の位置の設定は このメソッド updatePodiumPositionで行います ではアクティブプレーヤーの席の前に 置いてみましょう まず 台座の位置を プレーヤーの席の位置に合わせます この位置は ゲームのテンプレートで定義されています そのあと 0.5メートルほどずらして アクティブプレーヤーの前に置きます ここでは アクティブプレーヤーの席の位置を そのまま使うことができます RealityKitとGroupActivitiesはどちらも デフォルトの長さの単位がメートルであり 変換の必要はありません グループイマーシブ空間を選んだので アプリのイマーシブ空間の原点は 空間テンプレートの原点でもあります
試してみてください
アクティブプレーヤーがゲームステージに入ると チームメンバーと向かい合わせになり すぐにヒントを表示する台座が正面に現れます
試すのが今から楽しみです これで完了です
あとは Guess Togetherを 実際にプレイするだけです では別の部屋からFaceTimeで連絡します
こんにちはKevin やあEthan GabbyとMiaも連れてきました こんにちは みなさんこんにちは Guess Togetherをプレイしてみましょう カテゴリはどれにしますか 私は野菜はやめておきます わかりました 映画とテレビも外しましょう
さて ではチームを選びましょうか 始めましょう
私は青チームにします
私は赤チームにします
私も赤チームにします
では私は青チームですね
決まりましたね みなさん準備はいいですか ではプレイしましょう
最初は私からのようです がんばってください では始めます
なるほど これは果物で 普通は赤か緑です とても甘くて 人気があって あまり大きくはありません りんごですね 正解です そうですねこれは楽器です 小さい音も大きい音も出せることが 名前の由来になっています 見当もつきません 鍵盤が88鍵あります ピアノですね
はい当たりです
楽しかったです みなさん ありがとうございました
Ethan 素晴らしいサンプルのアプリを 用意してくれてありがとう Guess Togetherは 友だちと遊ぶときっと盛り上がります ここまで カスタム空間テンプレートを 使ったアプリの作成方法を見てきたので 次は 空間Persona向けの SharePlay体験を設計するプロセスを 見ていきましょう visionOS向けの新しいSharePlay体験を 設計する最初のステップは 使用する空間Personaテンプレートの タイプを選ぶことです 具体的には カスタムテンプレートか 組み込みシステムテンプレートかを決めます
ここで 最初の問いとして カスタムテンプレートを使うことで 体験が向上するかどうかを 検討する必要があります 完全にカスタムの空間Personaテンプレートを 作成する前に その体験で本当に恩恵を得られると 確信する必要があります システムの空間Personaテンプレートは 空間Personaを使った体験が 優れたものになるように 様々な調整が施されています 参加者全員がアプリの前に座る場合は 横並びテンプレートがおすすめです このテンプレートは非常に便利で ウインドウ表示アプリや メディア視聴アクティビティに最適です 会話テンプレートは それぞれの参加者を アプリの近くに集めることで 互いに集中できるようにします 円形テンプレートは アプリがボリュームである場合 参加者をその周りに 円状に配置したい場合に最適です システムテンプレートを使用すると カスタムと比べていくつかの利点があります その1つは 使い慣れた より快適な体験を アプリのユーザーに提供できることです アプリのインターフェイスに なるべく標準のUIコントロールの使用が 推奨されるのと同じように ユーザーがすでに慣れ親しんでいる 空間Personaテンプレートを 使用することをおすすめします
さらに システムテンプレートは 通話の空間Personaの数に合わせて 自動的に調整されます アクティビティの途中での 参加者の増減にも対応できます また FaceTimeでサポートされる上限に 対応した十分な席が常に確保され 全員が互いに見えるように配置されます さらに システムテンプレートは 全員がアプリを使用できるように 共有するシーンのサイズに合わせて 動的に調整されます
すべてのシステムテンプレートを 検討したうえで それでも 完全なカスタムの方が 効果的であると判断した場合は 次の問いとして 複数のテンプレートを使うことで 体験が向上するかどうかを検討します
自分の体験を一枚岩として 扱わないことをおすすめします Guess Togetherを設計する時は アプリ全体に1つの カスタムテンプレートを使用するのでなく ゲームの各ステージを個別に検討し システム提供とカスタムのテンプレートを 組み合わせて使用します こうした問いに対する答えは アクティビティの段階によって 異なる場合があるため それぞれの部分に応じて 該当するテンプレートを 使用することをおすすめします 先に進む前に もう1つ問いかけるべき質問があります
空間Personaを使用しない参加者を どのようにサポートするかということです
visionOSを念頭に置いて SharePlay体験を設計する際には SharePlayアクティビティで 空間Personaを使用している 参加者のことだけを考えてしまいがちですが それでは十分でないことがあります マルチプラットフォームの SharePlayアクティビティであれば プラットフォームが空間Personaに 対応していない参加者もいるかもしれません visionOS向けの専用アプリであっても SharePlayアクティビティの間に 参加者はいつでも 空間Personaを切り替えることができます 体験を設計する時は 両方のケースを想定するようにしてください 以上がテンプレートの選択です 続いて 席の定義に進みましょう 席を定義するにあたり 鍵となる ベストプラクティスがいくつかあります すべての空間Personaの席を用意すること 席と席の間に十分な間隔を設けること そして席の順番を慎重に そして意図的に検討することです 例えば チェスのような2人用ボードゲームの テンプレートを作成するとします テンプレートの最初の ドラフトはこんな感じです アプリのボリュームを挟んで 2つの席を向かい合わせにしています SharePlayアクティビティに参加する 空間Personaが2つだけであれば これで問題ありません しかし 3人目の参加者が加わり 空間Personaを使うとなると 使い勝手がよくありません 追加の参加者の席を作成していないため FaceTimeはその空間Personaを 他の参加者と一緒に配置できません 代替手段として 3人目の参加者は組み込みのテンプレートに 単独で配置されますが それだと チェスをする2人から3人目は見えず 3人目から最初の2人も見えません そのため すべての空間Personaの 席を用意することが重要です テンプレートに3席追加すると FaceTime通話でサポートされる 空間Personaの最大数に対応した 十分な席が確保され アクティビティに参加する すべてのユーザーの体験が向上します 同様に重要なのは すべての空間Personaの席について 席と席の間に十分な間隔を設けることです それぞれの席を少なくとも1メートルは 離して配置することをおすすめします これにより 隣の席の人が多少動いても 十分なスペースがあるため 狭いと感じることはありません
空間Persona同士の距離が近すぎると その人たちは消え 静的な連絡先の画像に 置き換わってしまう点にも注意が必要です 席と席の間に十分な間隔を設けることで アクティビティの参加者に 互いの空間Personaが 常に快適に見えるようになります
最後に 席の順番は 意図を持って決めることが重要です 考えられるすべての空間Personaの 組み合わせをテンプレートで検討し 席が正しく埋まるように 確認することをおすすめします 例えば 空間Personaが3つの場合 カスタムテンプレートの設定時に 指定した最初の3席が順番に使用されます
この例では 新しい参加者が加わると 中央から順番に席が埋まっていきます
一方 左から右の順に席を定義した場合は その順番で席が埋まっていくため すべての席が埋まらない場合に バランスの悪いテンプレートになります
席が埋まる順番は テンプレートのelements配列での 席の指定順序で決まります これは アプリに向かって席を一列に並べる シンプルなテンプレートです Guess Togetherでチーム選択に 使用したものに似ています 参加者が通話に参加すると elementsリストの席の順序に従って 席が埋まっていくのがわかります
カスタム空間テンプレートの 席の定義については以上ですが 次に進む前に あと1つ非常に重要な問いがあります それぞれの席に最初に座った時に その人がどこを見るようにするか つまり 各席の焦点をどこにするかということです デフォルトでは 席はアプリの中心を向いていますが 席の向きは自由に変えることができます アプリを使う人が席に座った時に 何に焦点を当てるべきかが わかるようにする必要があります 別の参加者のアプリを 見ればよいでしょうか まったく別の場所でしょうか 新しいAPIには 席の向きを設定するための 様々なツールが用意されています Guess Togetherのゲームのテンプレートでは lookingAtメソッドを 使用して アクティブプレーヤーの席が チームのメンバーの方を向くようにしました lookingAtで空間テンプレートの 任意の要素の位置を指定して どこか別の具体的な場所を向くように 席の向きを変えることもできます また alignedWithメソッドを使うと X軸やZ軸など アプリの直交軸に沿って 席を並べることができます 例えば 席を一列に並べ すべての席がアプリの面に対して直角に アプリの方を向くようにするには 席をZ軸にそろえて配置します さらに 最初の向きを決めたあと いつでも席を回転させることができます 指定する値には 度数とラジアンのどちらも使用できます それぞれの席の向きと位置が決まったら 次は テンプレート内のどの席が 特別であるかについて検討します つまり いずれかの席を 予約する必要があるかどうかです
どの席に役割が必要かではなく どの席が特別であるか または どの席に予約が必要かを 検討するのです 先ほど見たチェスのテンプレートでは 対戦するアクティブプレーヤーが それぞれの席に座るまで プレーヤーの席は 空席になるようにしています 一方または両方のプレーヤーが 空間Personaを有効にしていない場合でも 空席のままにして あとで必要になった時のために 予約しておく必要があります 一方 観客席は特別ではないので 役割は割り当てません 観客用に3つ目の役割を 作ろうと考えがちですが 各参加者が観客の役割を リクエストすることで遅延が生じ ユーザー体験の低下に つながる可能性があるので 代わりに 役割がない席に FaceTimeで直接座らせるようにします 可能な限り 役割のない席を使用することで FaceTimeで参加者を遅滞なく 席に着かせることができます
参加者を特定の場所に 配置する必要がある場合は 席を予約する方法として役割を使います ただし すべての参加者が 役割を使えるわけではありません 別のプラットフォームを 使用していたり 空間Personaを無効にしていることもあります
ここまでに どのタイプのテンプレートを使用すべきか 席の位置や向きはどうするか そして特定の参加者用に 席を予約する必要があるかを検討しました これで SharePlay体験向けの優れた カスタム空間テンプレートができたはずです 最後にあと1つ 設計する時に ぜひ覚えておいてほしいことがあります
驚きは不要だということです 空間Persona向けの SharePlayアプリを設計する時に 設計者にできる重要なことの1つは アプリを使う人が想定していないような 空間Personaのテンプレートは 作らないということです カスタム空間テンプレートを使うと アプリで実に様々なことができます テンプレートの切り替えと役割の変更により 参加者の空間Personaを アプリで動かすことができます FaceTime通話の表現は 共有スペースのあちこちに現れます テンプレートが変わった時や 役割を使って別の席に割り当てられた時に 使う人がどのような反応をするかを よく検討することが非常に重要です 一般的なガイドラインを見てみましょう
テンプレートの切り替えは最小限にします
テンプレートを変更すると 席の移動や回転 役割の交代 テンプレートタイプの変更などで 方向感覚が失われることがあります そのため あまり頻繁に行うべきではありません Guess Togetherでは ステージごとに 席の位置が変わらない場合は 単一のテンプレートを使い 必要な時だけ役割を 使用して 参加者を別の席に移動しています
テンプレートを切り替える時は 参加者が向きを変えられるように 必ず視覚的な コンテキストの手掛かりを用意します これは席の向きに限らず 各席の視野に視覚的なアンカ点を持つように することが重要です アプリ自体でも別の参加者でもかまいません
最後に テンプレートの変更は参加者の 明示的な操作と結びつけるようにします Guess Togetherのチーム選択ステージは この典型的な例で 役割の変更は 誰かがボタンを押して 青チームか赤チームに参加する操作と
結びついています
設計プロセスのすべての段階で 不要な驚きは与えずに 驚きが本当に楽しい体験になるように 最善の方法を検討してください 最後になりますが Guess Togetherのサンプルコードは ぜひダウンロードして 友だちと使ってみてください 本当に楽しいだけでなく 空間テンプレートで何ができるか 優れたアイデアがたくさんつまっています またXcode 16の新しいvisionOSシミュレータで FaceTime通話のシミュレーションを使って SharePlay体験を試してみてください この素晴らしいツールは visionOS向けの優れたSharePlayアプリを 開発するのに役立ちます どの種類の空間テンプレートを 使うかに関係なく使用できます そしてカスタム空間テンプレートで 体験が向上するかどうかを検討してください 既存のシステムテンプレートを使った方が 効果的な場合もあります 最後に visionOS向けの優れた SharePlay体験の設計を始めるにあたり 不要な驚きは避けるようにしてください カスタム空間テンプレートは 本当に素晴らしい体験を FaceTimeで実現する優れたツールですが 使う時は慎重な検討が必要です
カスタム空間テンプレートを 使用して みなさんがvisionOSで 素晴らしい体験を構築されることを 楽しみにしています ご視聴ありがとうございました
-
-
12:32 - Initial team selection template
// Team selection template – custom spatial template import GroupActivities struct TeamSelectionTemplate: SpatialTemplate { let elements: [any SpatialTemplateElement] = [ .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)), ] }
-
13:31 - Completed team selection template with seat roles
import GroupActivities /// The custom spatial template used to arrange Spatial Personas /// during Guess Together's team-selection stage. /// /// The team selection template contains three sets of seats: /// /// 1. Five audience seats that participants are initially placed in. /// 2. Three Blue Team seats that participants are moved to /// when they join team Blue. /// 3. Three Red Team seats. /// /// ``` /// ┌────────────────────┐ /// │ Guess Together │ /// │ app window │ /// └────────────────────┘ /// /// /// % $ /// % $ /// Blue Team % $ Red Team /// * * * * * /// /// Audience /// ``` 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) ] }
-
14:59 - Configuring a custom spatial template
systemCoordinator.configuration.spatialTemplatePreference = .custom(TeamSelectionTemplate())
-
15:39 - Assigning the local participant a spatial template role
systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam)
-
16:00 - Resigning the local participant from a spatial template role
systemCoordinator.resignRole()
-
17:00 - Spatial template roles
// Associating a role with a seat .seat(position: .app.offsetBy(x: -2.5, z: 3.5), role: TeamSelectionTemplate.Role.blueTeam) // Assigning the local participant a role systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam) // Resigning the local participant from their current role systemCoordinator.resignRole()
-
18:42 - Game template with seat direction
import GroupActivities /// The custom spatial template used to arrange spatial Personas /// during Guess Together's game stage. /// /// The team selection template contains three sets of seats: /// /// 1. An seat to the left of the app window for the active player. /// 2. Two seats to the right of the app window for the active player's /// teammates. /// 3. Five seats in front of the app window for the inactive team-members /// and any audience members. /// /// ``` /// ┌────────────────────┐ /// │ Guess Together │ /// │ app window │ /// └────────────────────┘ /// /// /// Active Player % $ Active Team /// $ /// /// * * * * * /// /// Audience /// /// ``` 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 } }
-
21:41 - Configure group immersive space
// Configure group immersive space for await session in GuessingActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } systemCoordinator.configuration.supportsGroupImmersiveSpace = true }
-
30:35 - SimpleLine Template
// SimpleLine.swift struct SimpleLine: SpatialTemplate { let elements: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 2)), .seat(position: .app.offsetBy(x: 1, z: 2)), .seat(position: .app.offsetBy(x: -1, z: 2)), .seat(position: .app.offsetBy(x: 2, z: 2)), .seat(position: .app.offsetBy(x: -2, z: 2)) ] }
-
31:35 - lookingAt Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) )
-
31:46 - alignedWith Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) ) // Align with a given app axis .seat( position: teamSeatPosition, direction: .alignedWith(appAxis: .z) )
-
32:02 - rotatedBy Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) ) // Align with a given app axis .seat( position: teamSeatPosition, direction: .alignedWith(appAxis: .z) ) // Rotate by a given angle .seat( position: teamSeatPosition, direction: .lookingAt(.app).rotatedBy(.degrees(30)) )
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。