ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Reality Composer ProコンテンツのXcodeでの使用
Reality Composer ProのコンテンツをXcodeに持ち込む方法を確認しましょう。Xcodeに3Dシーンをロードし、コンテンツとコードを統合し、アプリにインタラクティブ性を追加する方法を紹介します。また、開発ワークフローでこれらのツールを一緒に使用するためのベストプラクティスとヒントについても共有します。 このセッションを最大限に活用するためには、まず「Reality Composer Proについて」と「Reality Composer Proにおけるマテリアルの詳細」を見て、3Dシーンの作成について確認することをお勧めします。
関連する章
- 0:00 - Introduction
- 2:37 - Load 3D content
- 6:27 - Components
- 12:00 - User Interface
- 27:51 - Play audio
- 30:18 - Material properties
- 33:25 - Wrap-up
リソース
関連ビデオ
WWDC23
- はじめてのイマーシブなアプリの開発
- Reality Composer Proにおけるマテリアルの詳細
- Reality Composer Proについて
- RealityKitによる空間コンピューティンングアプリの拡張
- RealityKitを使用した空間体験の構築
WWDC21
-
ダウンロード
♪♪ ♪ こんにちはAmandaです エンジニアとしてRealityKitと Reality Composer Pro を担当しています このセッションではReality Composer Proで構築した 3Dコンテンツによる 空間体験の構築方法をお伝えします Reality Composer Proは空間 コンピューティングアプリで使用する RealityKitコンテンツを準備するための デベロッパツールです このセッションでは 同僚のEricとNielsが 彼らのセッションで作成した プロジェクトを基に コードでインタラクティブに それを活用する方法を確認します 彼らのセッションをまだ見ていない場合 確認されることをお勧めします セッションで解説されている Reality Composer Proの エディターのUIと機能について 一通り確認いただけます まず完成品を見て各パーツを どのように作成したかを説明します ご覧いただいてるのは ヨセミテ国立公園の地形図です ヘッドセットで見ることで 実際に現地に行かないと 味わえなかったような 広大さを実感できます 以前のReality Composer Proのセッションでは Ericがこのシーンを組み立て Nielsが地形で使用している マテリアルを作成しました ここに カリフォルニアの 2つのランドマークの間をモーフィング するためのスライダを追加しました 今見ているのは ロサンゼルス沖にあるカタリナ島です 3D空間に配置された 2DのSwiftUIボタンもあり マップ内のさまざまな 特定のポイントで ホバーするようにしています このセッションでは 体験をより良いものにするために Reality Composer Proでコンテンツを どのようにアレンジしたかについて 解説します そして このスライダや 特定ポイントを Reality Composer Pro 作成したシーンと シーンにおけるエフェクトを どのように関連付けたかについても ご紹介します まずReality Composer Proの プロジェクトから 3Dコンテンツをプログラムで ロードすることからスタートします RealityKit コンポーネントが どのように機能するのか それらをコードでどのように 利用できるのかや 独自のカスタムコンポーネントの 作成についても見ていきます SwiftUIの新しい RealityView APIについて またユーザーインターフェイス要素を Attachments API を使用して シーンに追加する方法も確認します またReality Composer Proで 設定した オーディオ操作についても確認します Nielsがシェーダーグラフを使用して 作成したカスタムマテリアルを接続し コードからその要素を 駆動するようにします では早速始めましょう Ericのセッションでは ジオラマのすべてのアセットを含む Reality Composer Pro プロジェクトを作成し 良いものができました 上部のこれらのタブはそれぞれ 実行時にロードできる 1つのルートエンティティを表します シーンに多くのものを入れましたが それを完全に整理されたシーンとして 取り扱うことができます あるいはいくつかだけを 配置して そのシーンを再利用可能な 集合体のように扱うことも可能です 数も必要なだけ作れます DioramaAssembled というシーンを実行時に どうやって読み込むのかについて 見ていきましょう エンティティの非同期の イニシャライザを使用して Reality Composer Pro パッケージにあるコンテンツを持つ エンティティを作成します 文字列名を使用してロードする エンティティを指定し パッケージが生成するバンドルを それに与えます Reality Composer Pro プロジェクト内で その名称が見つからない 場合はスローされます realityKitContentBundle はReality Composer Pro パッケージ内で自動生成 される定数値です これはRealityViewのmake クロージャに組み込まれます RealityViewは新しい種類の SwiftUIビューです これがRealityKitへの 入り口です これがSwiftUIとRealityKitの 世界の間の架け橋となります このRealityViewについては このセッションの後半で さらに詳しく説明します Xcodeプロジェクトで使用していて Reality Composer Pro プロジェクトに追加していない USDアセットがある場合は これらのアセットをSwift パッケージに格納し その中の.rkassetsディレクトリに 入れておくことを強く お勧めします Xcodeは.rkassetsフォルダを 実行時の読み込みが より速い形式になるように コンパイルします 先ほどロードしたエンティティは 実際には より大きなエンティティ階層の ルートとなります これには子エンティティがあり その子エンティティも存在します これはReality Composer Pro シーンでアレンジしたもの全体です 階層の下位のエンティティに アドレス指定したい場合は Reality Composer Proで 適宜名称を設定することができ ランタイムでシーンに対して その名称でエンティティを検索するよう 依頼することができます エンティティは Entity Component System すなわち ECSの一部を 構成するものです ECSはRealityKitとReality Composer Proを支えるものです ここでステップバックし ECSの理解を深めることにしましょう ECSはオブジェクト指向 プログラミングに類似するものですが いくつかの重要な点で違いがあります オブジェクト指向プログラミングの 世界では オブジェクトには その性質を定義する属性である プロパティと 関数によって構成されます これらのプロパティと関数は オブジェクトを定義する クラスに記述します ECSではエンティティとはシーン内に 表示されるものを指します それが非表示の場合もあります 属性やデータは保持しません 代わりにデータを コンポーネントに格納します コンポーネントは アプリの実行中にいつでも エンティティに追加または 削除できるので エンティティの性質を動的に 変更できます システムは ビヘイビアが 存在する場所となります フレームごとに1回呼び出される update関数があります そこに進行中のロジックが 配置されます システムでは特定のコンポーネントが 含まれるすべてのエンティティ またはコンポーネントの 構成をクエリしてから 次に何らかのアクションを実行して 更新されたデータを コンポーネントに格納し直します ECSに関する詳細は 2021年の「RealityKit 2の詳細」 セッションや 本年度の「RealityKitによる 空間体験の構築」を チェックしてみてください 次にコンポーネントについて 見ていきます まずReality Composer Pro プロジェクトのエンティティに コンポーネントを追加する 方法を見ていきます 次にジオラマ上に位置マーカーを 作成するための カスタムコンポーネント 作成法について確認します Swiftでコンポーネントを エンティティに追加するには entity.components.set() と定義ししてから コンポーネントの値を付与します Reality Composer Proで 同じ作業を行うには ビューポートまたは階層内で 必要なエンティティを選択します
次にインスペクタパネルの下部にある ボタンを クリックしてリストを表示し RealityKitの利用可能な コンポーネントを表示します
コンポーネントは必要なだけ エンティティに追加できますが 追加できるのは各タイプごとに1つだけです セットだからです このリストには作成したカスタム コンポーネントも表示されます Reality Composer Proを 使用して独自の カスタムコンポーネントを作成する 方法を見てみましょう 地形上の特定の点の上に 浮かぶフローティング ボタンを作成してそれらを 選択できるようにし そのスポットに関する 詳細情報を表示させます 多くのUIと機能をコードで 準備しますが これらのエンティティをマークする 方法をご紹介したいと思います Reality Composer Proで フローティング ボタンを表示する 位置として使用します これを行うには地形図上の 場所に エンティティを追加して アプリにフローティング ボタンを表示したい 場所であることを 明示させます 次に各場所に関する情報を 格納するための POIコンポーネントを 作成します 次にXcodeで PointOfInterestComponent.swiftを 開いて編集し 名称や説明などの プロパティを追加します Reality Composer Proでは 新しい PointOfInterestComponentを エンティティの それぞれに追加し プロパティの値を入力します では最初の位置マーカー エンティティである カタリナ島のリボン ビーチを作成しましょう プラスメニューをクリックし を選択して 新しい非表示のエンティティを 作成します
エンティティにRibbon_Beachと 名称設定できます このエンティティを島のリボン ビーチが実際にある場所に 配置してみましょう 通常「コンポーネントの追加」 ボタンをクリックしますが 今回は「新規コンポーネント」を 選択します 自分たちで1から作るためです
「PointOfInterest」という 名前を付けましょう
これで他のコンポーネントと同様 インスペクタパネルに表示されます
ところでこのcount プロパティとは何でしょうか Xcodeで新しいコンポーネントを 開いてみましょう XcodeではReality Composer Proが PointOfInterestComponent.swiftを 作成したことが確認できます Reality Composer Pro プロジェクトは Swiftパッケージであり 生成したばかりのSwiftコードで このパッケージに存在します テンプレート コードを見ると countプロパティが そこに由来している ことがわかります 別のプロパティも 用意してみましょう 興味のあるポイントがどの マップに関連付けられているか マップを変更すると 名所をフェードアウトできるよう 適切なものをフェードインします ここで列挙プロパティである var regionを追加します enumのregionを作成して 2つのマップしか構築 していないため ここでは2つのケースを 考えます カタリナとヨセミテです 文字列としてシリアライズできます またReality Composer Proが 認識できるよう Codableプロトコルにも 準拠しており インスタンスを シリアライズ可能にします Reality Composer Proに戻ると countプロパティが消え 新しいregionプロパティが表示されます デフォルト値はyosemiteです これはコード内で初期化 したものであるためです このエンティティについては ここでオーバーライド可能です これをオーバーライドすると この値はこのエンティティに対してのみ 有効になります 残りのPOIコンポーネントの デフォルト値は オーバーライドしない限りyosemiteです PointOfInterestComponentを シグニファイア つまりこれらのエンティティへの マーカーのように使用します これらのエンティティは 実行時にSwiftUIボタンを 配置する場所のプレース ホルダのように機能します リボン・ビーチを追加したのと 同じ方法で 他のカタリナ島の観光 スポットを追加します アプリを実行して新しいカスタム コンポーネントの挙動をチェックします
何も起こりませんね これはPOIコンポーネントを 処理するコードが まだ記述されていないためです ということで設定しましょう SwiftUIコンテンツを RealityKitシーンに 配置する新しい方法があります。 それが Attachements APIと呼ばれるものです アタッチメントを PointOfInterestComponentと 組み合わせてホバリング ボタンを作成します 実行時にカスタムデータを使用します まずこれをコードで見てから データフローについて説明します アタッチメントはRealityViewの一部です まずRealityViewの構造を示す 簡略化された例を見てから SwiftUIビューのRealityKitシーン への取り込みについて確認します これから使用するRealityView イニシャライザは 3つのパラメータを取ります makeクロージャと updateクロージャ およびattachementsビュービルダです 少し具体化して最低限の 機能を追加してみましょう 緑色のSwiftUIボタンで Attachment Viewを作成し それをRealityKit シーンに 追加します attachmentsビュービルダで 通常のSwiftUIビューを作成します ビューモディファイアやジェスチャーなど SwiftUIが提供するすべての機能を 利用できます ビューに一意のhashableタグを付けます このボタンビューに魚の絵文字を タグ付けすることにしました その後SwiftUIが更新 クロージャを呼び出すと ボタンViewが エンティティになります これはこのクロージャのattachments パラメータに保存されており 前に付けたタグを使用して 取り出します その後他のエンティティと 同様に扱うことができます これをシーン内の既存の エンティティの子として追加します 新規のトップレベルの エンティティとして追加することも エンティティ コレクション内への 追加も可能です しかも通常のエンティティに なるので 3Dで希望する場所に表示されるよう 位置を設定したり 必要なコンポーネントを 追加することもできます RealityViewから別の部分に データがどのように 流れるかを次に示します このRealityView イニシャライザの 3つのパラメータを 見てみましょう 1つ目はmake で ここでロードし Reality Composer Pro バンドルの初期セットアップシーンの エンティティとして作成してから RealityKit シーンに追加します 2番目はupdateで ビューの状態に変化があったとき 呼び出されます ここではコンポーネントの プロパティや位置 エンティティに関する変更を 行えます さらにシーンにエンティティの 追加や削除もできます このupdateクロージャはフレームごとに 実行されるわけではありません SwiftUIのビューステートが 変化するたびに呼び出されます 3番目はattachementsビュービルダです ここでRealityKitシーンに 挿入する SwiftUIビューを作成できます SwiftUIビューは attachementsビュービルダから始まり updateクロージャの attachementsパラメータを通じて 伝えられる形になります ここではattachementsビュービルダで ボタンに指定したのと同じタグの エンティティがあるかどうかを attachmentsパラメータに 問い合わせます 存在する場合はRealityKit エンティティが提供されます updateクロージャで3D位置を設定し RealityKitシーンに追加します どこにいても宙に浮かんでいるのを 見られます ここではボタンエンティティを 球体エンティティの 子として追加しました 親の0.2メートル上に配置しました makeクロージャには attachmentsパラメータもあります これはこのビューが最初に 評価されるときに 準備ができているアタッチメントを 追加するためのものですが それはmakeクロージャが 1回しか実行されないためです RealityViewの一般的な フローが理解できたので updateクロージャについて さらに詳しく見ていきましょう makeおよびupdate クロージャのパラメータは RealityKitContentです RealityKit コンテンツに エンティティを追加すると シーン内のトップレベルの エンティティになります 同様にupdate関数からコンテンツに エンティティを追加することで シーン内の新しいトップレベルの エンティティを提供します makeクロージャは1回だけ 呼び出されますが updateクロージャは 複数回呼び出されます updateクロージャで新しい エンティティを作成し そこにあるコンテンツに追加すると そのエンティティのコピーが 取得されますが これは希望どおりにならない 可能性があります これを防ぐには一度だけ実行 される場所で作成された エンティティのみをコンテンツに 追加する必要があります content.entitiesにエンティティが 含まれているかどうかを 確認する必要はありません 同じエンティティでaddを2回 呼び出した場合はセットのように 何も処理されません エンティティを既存のエンティティの 親にする場合も同様で シーン内に2度追加される ことはありません アタッチメントエンティティは みなさんが作成するのではなく attachementsビュービルダ内で提供した アタッチメントビューごとに RealityViewによって作成されます つまり既存どうかを確認せずに updateクロージャの コンテンツにそれらを追加 しても安全です 以上が名所をattachementsビュービルダに ハードコーディングしたい場合の attachementsの コードの書き方です ただしReality Composer Pro プロジェクトのデータで 体験をドライブするようにしたいので もっと柔軟にしたいと思います そうすることでデザイナーや プロデューサーは Reality Composer Proプロジェクトで 関心のあるポイントを作成でき コードは追加されるどんな データにも対応できるようになります データ駆動型にするには Reality Composer Proシーンで 設定したデータコードを 読み取る必要があります アタッチメントビューを 動的に作成しましょう 大まかに言うと作業は 次のとおりです Reality Composer Proでは すでにリボン・ビーチの プレースホルダエンティティが 設定されており ジオラマで強調表示したい他の 名所についても同様です 名称や関連づけを行うマップなど 必要とするすべての情報を入力します 次にコードでこれらの エンティティをクエリし それぞれに新しいSwiftUI ボタンを作成します SwiftUIで attachementsビュービルダを 呼び出すには 新しいボタンをコレクションに 追加するたびに @Stateプロパティラッパを コレクションに追加します これらのボタンはattachements ビュービルダに提供されます そして最後にRealityViewの updateクロージャで ボタンをエンティティとして 受理します そして新しいボタンエンティティを シーンに追加します それぞれを Reality Composer Proで 設定したマーカーエンティティの 子として追加します より詳細な図を通してこれら 6つのステップを理解してから コードを見てみましょう まずReality Composer Pro シーンに 非表示のエンティティを 追加します 非表示エンティティを 希望する場所に配置し X軸とY軸とZ軸上に ボタンを表示させます ここではすべてのエンティティに デフォルトで備わっている Transformコンポーネントを 利用しています 次に PointOfInterestComponentを それぞれに追加します このコードではシーン内のすべての エンティティをクエリすることで PointOfInterestComponentへの エンティティの 参照を取得できます クエリはReality Composer Proで 設定した 3つの非表示エンティティを 返します それぞれに対して新しい SwiftUIビューを 作成してコレクションに 保存します ボタンをRealityViewに 取り込むには SwiftUIのビュー更新フローを 利用します これはビュー内のボタンの コレクションに @Stateプロパティラッパを 追加することを意味します @Stateプロパティラッパは このコレクションに項目を追加するときに SwiftUIに次のように指示します SwiftUIはImmersiveViewでビューの 更新をトリガーする必要があります これによりSwiftUIは attachementsビュービルダと updateクロージャを再度評価します RealityViewの attachementsビュービルダは SwiftUIに宣言する場所で これらのボタンをエンティティに するのに必要です 次にRealityViewのupdate クロージャが呼び出され ボタンがエンティティとして 配信されます これらはもうSwiftUIビューではありません そのためエンティティ階層に 追加可能です updateクロージャでは アタッチメントエンティティを シーンに追加して 非表示エンティティ上に それぞれフローティングして 配置します これでジオラマシーンを 見るときに 視覚的に表示されるようになります 各ステップがどのように行われるかを 見てみましょう まずReality Composer Proシーンで 非表示エンティティを マークします マークしたエンティティを 見つけるために EntityQueryを作成します これを使用して PointOfInterestComponentを 持つすべてのエンティティを 要求します 次にQueryResultを反復処理し PointOfInterestComponent シーン内のエンティティごとに 新しいSwiftUIビューを作成します 次にReality Composer Pro コンポーネントから 取得したデータを入力します そのビューはattachementsの 1つになるので それにタグを付けます 今回は少し真面目に行くとして 魚の絵文字ではなく ObjectIdentifierを使用します ここはSwiftUIビューのコレクションを 作成する部分です アタッチメントを提供するための これをattachmentsProviderと 呼んでおり RealityViewのattachements ビュービルダに追加します ビューをattachmentsProviderに 保存します コレクションタイプを 見てみましょう AttachmentsProviderには ビューへの アタッチメントタグの辞書があります ビューをタイプ消去することで LearnMoreView以外の ビューの挿入を可能にします sortedTagViewPairsという プロパティは タプルの配列を返し タグとそれに対応するビューも 毎回同じ順番で返します 次にattachements ビュービルダで attachementsのコレクションに 対しForEachを 実行します これはSwiftUIにビューが 1つ必要であることを伝え 与えたそれぞれの ペアに対して コレクションからのビューを 提供します ここではObjectIdentifierに ビューの アタッチメンtタグとしての 2つの役割を付与し ForEach構造体の識別子 としても認識させます なぜ代わりに PointOfInterestComponentに タグプロパティを追加 しなかったのでしょうか アタッチメントタグは ForEach構造体に対しても アタッチメントのメカニズムが 機能するためには一意である 必要があります またカスタムコンポーネントの すべてのプロパティが Reality Composer Proの インスペクタパネルに表示されるため コンポーネントをエンティティに 追加すると attachmentTag もそこに 表示されることになります Reality Composer Proで 名所を追加するとき すべてのタグを一意化することを 覚えておくことは 負担になるかと思います でも都合の良いことに エンティティは Identifiableプロトコルに 準拠のため識別子があり 自動的に一意になります この識別子は事前に 知っておく必要がなく Reality Composer Proで シーンをデザイン しているときエンティティから 取得できます AttachmentTagプロパティを Reality Composer Proに表示 しないようにするには 次のテクニックを使用します 「デザイン時と 実行時のコンポーネント」 データを2つの異なる コンポーネントに分割し 2つは配置したいデザイン時の データ用として Reality Composer Pro内に 配置し もう1つは同じエンティティに 添付する実行時のデータ用として 実行時に動的に配置します これはReality Composer Proのインスペクタパネルに 表示したくないプロパティ用です そこで新しいコンポーネントを 定義します PointOfInterestRuntimeComponentを 作成してその中にアタッチメントタグを 移動します Reality Composer Proは 読み取った内容に基づいて Swiftパッケージに含まれる コンポーネントUIを 自動的に構築します パッケージ内のSwift コードを検査し codableなコンポーネントを作成し シーンに応じて利用します ここでは4つの コンポーネントを示します コンポーネントAとBは Xcodeプロジェクト内にありますが Reality Composer Pro パッケージには 含まれないため Reality Composer Proの エンティティにアタッチできません コンポーネントCは パッケージ内にありますが codableではないため Reality Composer Proは無視します ここに表示されている4つの コンポーネントのうち コンポーネントDのみが Reality Composer Proリストに表示され その理由というのは これがSwiftパッケージ内にあり codableコンポーネントであるためです これはデザイン時のコンポーネントで 他は実行時のコンポーネント として使用できます デザイン時のコンポーネントは より単純なデータを格納し intやstringやSIMD値など 3Dアーティストや デザイナーが活用するものです カスタムコンポーネントに プロパティを追加すると Xcodeプロジェクトで エラーが表示され Reality Composer Proが シリアライズしません さてコードに戻りましょう まずPointOfInterestの 実行時のコンポーネントを エンティティに追加します 次に実行時のコンポーネントを 使用して アタッチメントエンティティを 照合することで ジオラマ上の対応する 名所も表示されます ここで実行時のコンポーネントの 出番です PointOfInterest エンティティを読み取り アタッチメントビューを作成 している箇所にいます すべてのデザイン時のコンポーネントを クエリしました 次にそれぞれに対応する新しい 実行時のコンポーネントを作成します AttachmentTagを実行時の コンポーネントに保存し 実行時のコンポーネントを同じ エンティティに保存します このようにデザイン時のコンポーネントは シグニファイアのようなものです これはアプリにattachementsの作成を 要求していることを伝えます 実行時に必要なものの デザイン時のコンポーネントには 保存したくないようなものを 実行時のコンポーネントが処理します RealityViewでは アタッチメントエンティティが シーンに表示されるのを確認するまでに もう1つのステップがあります attachementsViewBuilderで SwiftUIビューを 提供すると SwiftUIはRealityViewの updateクロージャーを作成し attachementsを RealityKitエンティティとして提供します しかし配置せずに単に コンテンツに追加すると シーンの原点の位置である 0, 0, 0の位置に 表示されてしまいます 想定している挙動ではありません 地形上の各関心のある地点の上に それらを浮かせたいわけです アタッチメントエンティティと 非表示の名所のエンティティを Reality Composer Proで設定したのと 一致させる必要があります 非表示のエンティティに配置した ランタイムコンポーネントには タグが含まれています これによりどのattachmentEntityが 名所のエンティティに 対応するかを照合する ことが可能になります すべての PointOfInterestRuntimeComponentsを クエリし各エンティティからランタイム コンポーネントを取得 クエリによって返された場合は コンポーネントのattachmentTag プロパティを使用して updateクロージャへのattachmentsパラメータから attachmentEntityを取得 次にattachmentEntityを コンテンツに追加し 対象エンティティの 0.5メートル上に配置します アプリを再度実行してどのように なるかを確認してみましょう いい感じですね! Reality Composer Pro プロジェクトで地名を配置した場所の上に それぞれの地名が浮かんで いるのがわかります 次にReality Composer Proで 設定した オーディオを再生する方法を 見てみましょう Reality Composer Proで オーディオをセットアップするには プラスボタンをクリックしてオーディオ エンティティを取り込みます 「オーディオ」を選択してから 「アンビエントオーディオ」を選択 これにより AmbientAudioComponentを含む 通常の非表示 エンティティが作成されます カタリナ島の音を再生するために 使用するので エンティティにOceanEmitterという 名称を付けます オーディオファイルもシーンに 追加する必要があります 海の音を取り入れてみましょう
コンポーネントの「プレビュー」メニューで サウンドを選択すると オーディオコンポーネントを インスペクタパネルで プレビューできますが 選択したサウンドはエンティティが アプリに読み込まれるとき 自動的には再生されません 再生するにはオーディオ リソースをロードし 再生するよう指示する必要があります このサウンドを再生するには オーディオコンポーネントを配置する エンティティへの参照を取得し エンティティに OceanEmitter という名前を付けたので その名前のエンティティを 見つけます AudioFileResource イニシャライザを使用して サウンドファイルをロードし オーディオファイルリソースプリムへの フルパスを渡します これにReality Composer Pro プロジェクトに 含まれる.usdaファイルの 名前を付けます この場合 DioramaAssembled.usda という名前のメインシーンです entity.prepareAudioを呼び出して audioPlaybackControllerを作成します このサウンドを再生や 停止できるようにします これでplayを呼び出す準備が 整いました これがアプリで再生されている 海の音です アプリのスライダは 2つの異なる地形マップの間で モーフィングしますが ヨセミテとカタリナ島です
シーンにオーディオを導入したので 2つのオーディオ ソース間で クロスフェードします ocean emitterエンティティを 追加したのと同じ方法で forest audio emitterを 追加します スライダを使用して地形を どのように変形させるかを見ますが このトランジションには音声も 含まれます シェーダーグラフマテリアルの プロパティを使用して 2つの地形間をモーフィングします その方法を見てみましょう NielsのセッションでReality Composer Proのシェーダー グラフを使用してこの美しいジオメトリ モディファイアを作成してくれました これでシーンに接続し 実行時に いくつかのパラメーターを 駆動できるようになりました このシェーダーグラフマテリアルを スライダで接続したいと思います それには入力ノードを プロモートする必要があります コマンドを押しながらノードをクリックし 「プロモート」を選択します。 これによりマテリアルの この部分に実行時に データを供給することが プロジェクトに伝えられます このプロモートされたノードに Progressという名前を付け 実行時にその名前でアドレス 指定できるようにします コード内でこの値を動的に 変更できるようになりました マテリアルが存在するエンティティへの 参照を取得します 次にマテリアルを格納する RealityKitコンポーネントである ModelComponentを取得し ModelComponentから最初の マテリアルを取得します この特定のエンティティは 1つだけあります これをShaderGraphMaterial型に キャストします これでProgressという名前の パラメータに新しい値を 設定できるようになりました 最後にマテリアルを ModelComponentに格納し直し ModelComponentを terrainエンティティに格納し直します 次にこれをSwiftUIの スライダに接続します スライダの値が変化するたびに 0から1の範囲の値を取得し それをShaderGraphMaterialに フィードします 次に2つの地形のambientオーディオ トラック間を クロスフェードしてみましょう 2つのオーディオエンティティ 海と森にも AmbientAudioComponentを 配置しているため サウンドのゲインプロパティを 使用して サウンドの再生音量を 調整できます すべてのエンティティを クエリしますが この時点では海と森の2つとも AmbientAudioComponentが 含まれています さらにRegionSpecificComponent という別のカスタム コンポーネントを追加して エンティティをマークできるようにし どちらかの地域に 当てはめられます audioComponentを変更して 保存するため audioComponentの変更 可能なコピーを取得し エンティティに戻ります regionとsliderValueに 与えられる ゲインを計算する関数を 呼び出します ゲイン値をAmbientAudio Componentに設定し コンポーネントをエンティティに 再度格納します 実際にそれを見てみましょう
素晴らしい! スライダを動かすと シェーダー グラフ マテリアルがジオメトリを 変更しているのがわかり 地形図を見ると森の音が消え 海の音が入ってくるのが聞こえます
今日はたくさんの情報を 取り上げました まとめます Reality Composer Pro コンテンツをXcodeの アプリにロードする方法について Reality Composer Proで 独自のカスタムコンポーネントを 作成する方法について SwiftUI Attachments APIがどのように機能し それらがエンティティとしてどのように 提供されるかも見ました オーディオを設定しコード内で その音声を再生する方法も そして最後にプロモートされた マテリアルプロパティを取得して コードから駆動する方法も これらのワークフローは 空間体験を現実のものに するのに役立ちます みなさんが構築するものをぜひ 見てみたいと思います ありがとうございました ♪
-
-
3:12 - Loading an entity
RealityView { content in do { let entity = try await Entity(named: "DioramaAssembled", in: realityKitContentBundle) content.add(entity) } catch { // Handle error } }
-
6:39 - Adding a component
let component = MyComponent() entity.components.set(component)
-
12:21 - Attachments data flow
RealityView { _, _ in // load entities from your Reality Composer Pro package bundle } update: { content, attachments in if let attachmentEntity = attachments.entity(for: "🐠") { content.add(attachmentEntity) } } attachments: { Button { ... } .background(.green) .tag("🐠") }
-
15:48 - Adding attachments
let myEntity = Entity() RealityView { content, _ in if let entity = try? await Entity(named: "MyScene", in: realityKitContentBundle) { content.add(entity) } } update: { content, attachments in if let attachmentEntity = attachments.entity(for: "🐠") { content.add(attachmentEntity) } content.add(myEntity) } attachments: { Button { ... } .background(.green) .tag("🐠") }
-
20:43 - Adding point of interest attachment entities
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self)) @State var attachmentsProvider = AttachmentsProvider() rootEntity.scene?.performQuery(Self.markersQuery).forEach { entity in guard let pointOfInterest = entity.components[PointOfInterestComponent.self] else { return } let attachmentTag: ObjectIdentifier = entity.id let view = LearnMoreView(name: pointOfInterest.name, description: pointOfInterest.description) .tag(attachmentTag) attachmentsProvider.attachments[attachmentTag] = AnyView(view) }
-
21:40 - AttachmentsProvider
@Observable final class AttachmentsProvider { var attachments: [ObjectIdentifier: AnyView] = [:] var sortedTagViewPairs: [(tag: ObjectIdentifier, view: AnyView)] { ... } } ... @State var attachmentsProvider = AttachmentsProvider() RealityView { _, _ in } update: { _, _ in } attachments: { ForEach(attachmentsProvider.sortedTagViewPairs, id: \.tag) { pair in pair.view } }
-
22:31 - Design-time and Run-time components
// Design-time component public struct PointOfInterestComponent: Component, Codable { public var region: Region = .yosemite public var name: String = "Ribbon Beach" public var description: String? } // Run-time component public struct PointOfInterestRuntimeComponent: Component { public let attachmentTag: ObjectIdentifier }
-
25:38 - Adding a run-time component for each design-time component
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self)) @State var attachmentsProvider = AttachmentsProvider() rootEntity.scene?.performQuery(Self.markersQuery).forEach { entity in guard let pointOfInterest = entity.components[PointOfInterestComponent.self] else { return } let attachmentTag: ObjectIdentifier = entity.id let view = LearnMoreView(name: pointOfInterest.name, description: pointOfInterest.description) .tag(attachmentTag) attachmentsProvider.attachments[attachmentTag] = AnyView(view) let runtimeComponent = PointOfInterestRuntimeComponent(attachmentTag: attachmentTag) entity.components.set(runtimeComponent) }
-
26:19 - Adding and positioning the attachment entities
static let runtimeQuery = EntityQuery(where: .has(PointOfInterestRuntimeComponent.self)) RealityView { _, _ in } update: { content, attachments in x rootEntity.scene?.performQuery(Self.runtimeQuery).forEach { entity in guard let component = entity.components[PointOfInterestRuntimeComponent.self], let attachmentEntity = attachments.entity(for: component.attachmentTag) else { return } content.add(attachmentEntity) attachmentEntity.setPosition([0, 0.5, 0], relativeTo: entity) } } attachments: { ForEach(attachmentsProvider.sortedTagViewPairs, id: \.tag) { pair in pair.view } }
-
28:55 - Audio Playback
func playOceanSound() { guard let entity = entity.findEntity(named: "OceanEmitter"), let resource = try? AudioFileResource(named: "/Root/Resources/Ocean_Sounds_wav", from: "DioramaAssembled.usda", in: RealityContent.realityContentBundle) else { return } let audioPlaybackController = entity.prepareAudio(resource) audioPlaybackController.play() }
-
31:02 - Terrain material transition using the slider
@State private var sliderValue: Float = 0.0 Slider(value: $sliderValue, in: (0.0)...(1.0)) .onChange(of: sliderValue) { _, _ in guard let terrain = rootEntity.findEntity(named: "DioramaTerrain"), var modelComponent = terrain.components[ModelComponent.self], var shaderGraphMaterial = modelComponent.materials.first as? ShaderGraphMaterial else { return } do { try shaderGraphMaterial.setParameter(name: "Progress", value: .float(sliderValue)) modelComponent.materials = [shaderGraphMaterial] terrain.components.set(modelComponent) } catch { } } }
-
31:57 - Audio transition using the slider
@State private var sliderValue: Float = 0.0 static let audioQuery = EntityQuery(where: .has(RegionSpecificComponent.self) && .has(AmbientAudioComponent.self)) Slider(value: $sliderValue, in: (0.0)...(1.0)) .onChange(of: sliderValue) { _, _ in // ... Change the terrain material property ... rootEntity?.scene?.performQuery(Self.audioQuery).forEach({ audioEmitter in guard var audioComponent = audioEmitter.components[AmbientAudioComponent.self], let regionComponent = audioEmitter.components[RegionSpecificComponent.self] else { return } let gain = regionComponent.region.gain(forSliderValue: sliderValue) audioComponent.gain = gain audioEmitter.components.set(audioComponent) }) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。