
-
SwiftUIとRealityKitの連係
visionOS 26でSwiftUIとRealityKitをシームレスに融合させる方法について説明します。アニメーションやConfigurationCatalogのサポートなど、さらに強化されたModel3Dの機能を紹介し、RealityViewへのスムーズな移行のデモを行います。SwiftUIのアニメーションを利用してRealityKitコンポーネントの変更を適用する方法、インタラクティブな操作を実装する方法、新しいSwiftUIコンポーネントを使ってリッチなインタラクションを実現する方法、SwiftUIコードからRealityKitの変更を確認する方法を紹介します。また、フレームワーク座標変換のための統一座標変換の使用方法についても解説します。
関連する章
- 0:00 - イントロダクション
- 1:24 - Model3Dの機能強化
- 6:13 - RealityViewへの移行
- 11:52 - オブジェクトの操作
- 15:35 - SwiftUIのコンポーネント
- 19:08 - 情報のフロー
- 24:56 - 統合座標への変換
- 27:01 - アニメーション
- 29:41 - 次のステップ
リソース
- Canyon Crosser: Building a volumetric hike-planning app
- Rendering hover effects in Metal immersive apps
関連ビデオ
WWDC24
WWDC23
WWDC21
WWDC20
-
このビデオを検索
こんにちはRealityKitのエンジニアの Amandaです Maksです SwiftUIのエンジニアです 本日はSwiftUIとRealityKitの両方の 素晴らしい機能強化についてご紹介します 2つを一緒に使用するとさらに効果的です この愛らしいシーンをご覧ください 空中に浮かぶチャーミングな SwiftUIロボットと 地面に固定された RealityKitロボットがあり どちらもつながろうとしています 近づくと火花が散ります でも本当に触れ合えるくらい 近づくにはどうすればよいでしょう このセッションでは従来のUIと インタラクティブ3Dコンテンツの 世界を組み合わせる方法を説明します まずModel3Dの機能強化について 説明します 次にModel3Dの使用からRealityViewの使用に 移行する方法を提示し どの場面でどちらを 選べばよいのかについて話します ここからは新しい Object Manipulation APIを説明します
RealityKitに新しいコンポーネント型が加わり SwiftUIのさらに多くの面が統合されました SwiftUIとRealityKitの間で情報を双方向に 流せるようになったことを後ほど説明します
座標空間の変換が これまで以上に簡単になりました SwiftUIのアニメーションを利用して RealityKitコンポーネントの変更を制御します では始めましょう
Model3Dを使用して1行のコードで アプリに3Dモデルを表示します visionOS 26では2つの機能強化により Model3Dはさらに用途が増えました アニメーションの再生と ConfigurationCatalogからの読み込みです Model3DはSwiftUIビューであるため SwiftUIレイアウトシステムに参加しています そのレイアウトシステムを使い ロボットの名前を表示する 小さなサインを作成します
ロボットの名前はSparkyだという サインができました
Sparkyはかわいいダンスもできます アーティストはこのアニメーションを ロボットモデルアセットにバンドルしました visionOS 26の新機能は Model3DAssetタイプです 3Dコンテンツにアニメーションを ロードして制御するには Model3DAssetを使用して Model3Dを構築します モデルはアセットから アニメーションをロードして どれをプレイするかを選択します 「モデル」という語は多義的です 今回はUIフレームワークと3Dゲーム フレームワークを一緒に扱うため 特に注意が必要です UIフレームワークでは「モデル」は データ構造を指し アプリが使用する情報を表します このモデルにはデータと ビジネスロジックが保持されるため ビューはこの情報を 表示できるようになります RealityKitなど3Dフレームワークでは モデルはシーンに配置できる 3Dオブジェクトを指します これにはその形状を定義する メッシュリソースと外観を決定する材料で 構成されるModelComponentを介して アクセスします 時にはそういうこともあります 2つの世界がぶつかり合って それぞれの用語を持ち込み 時には重複することもあります Sparkyとそのアニメーションに戻りましょう
Model3Dをピッカーと再生ボタン およびタイムスクラバーの上に配置します RobotViewではアニメーション化された ロボット自体を表示しており その下に再生するアニメーションを選択する ピッカーとアニメーション 再生コントロールを配置しています 最初にバンドルから読み込む シーン名でModel3DAssetを 初期化します 次にアセットが現れたら Model3D初期化子に渡します その下のVStackにこのモデルアセットで 使用可能なアニメーションを 一覧表示するカスタマイズ済みの ピッカーを表示します 一覧から項目を選択すると ピッカーはアセットの 「selectedAnimation」を新しい値に設定します 次にModel3DAssetは AnimationPlaybackControllerを作成し 選択したアニメーションの 再生をコントロールします アセットは「animationPlaybackController」を 提供します オブジェクトを使いアニメーションの 一時停止、再開、シークを行います
そのanimationControllerを RobotAnimationControlsビューに渡します これについては後ほど説明します visionOS 26では既存のRealityKitクラス 「AnimationPlaybackController」は Observableになりました SwiftUIビューでは「time」プロパティを監視して アニメーションの進行状況を表示します
ここに「controller」という @Bindableプロパティがあります 「AnimationPlaybackController」を ビューのデータモデルとして 使用していることを意味します コントローラーのisPlaying値が変化すると SwiftUIによって RobotAnimationControlsビューが再評価されます このスライダはアニメーションの 持続時間に対する現在の時間を アニメーション中に表示する ものです このスライダをドラッグすると アニメーションがスクラブされます これがSparkyの お祝いアニメーションです スライダを使って早送りと 巻き戻しができます さあSparkyの 誕生日です ダンスの動きができたら Sparkyをドレスアップします その後は温室にいる 別のロボットに会いに行きます それを手助けするためRealityKitの ConfigurationCatalog型を強化します この型の中にはエンティティの別の 表示方法が格納されています さまざまなメッシュジオメトリ 成分値やマテリアルプロパティなどです visionOS 26では ConfigurationCatalogを使って Model3Dを初期化できます 様々な表現方法に切り替えることもできます
Sparkyがさまざまな衣装を試着できるように アーティストは様々なタイプの Reality Fileをバンドルしました このファイルをアプリのメインバンドルから ConfigurationCatalogとしてロードします 次に構成を使用して Model3Dを作成します このポップオーバーには 設定オプションが表示されます ポップオーバーから選択すると Sparkyの見た目が変わります ダンスの動きを 確認 洋服を 確認 SparkyはRealityKitの温室で 新しい友達に出会う準備ができました 火花が飛び散ります 火花を散らすには パーティクルエミッタを使います しかしこれはModel3Dタイプでは 実行時間にできることではありません パーティクルエミッタはRealityKitエンティティに 追加するコンポーネントです これについては後ほど詳しく 説明します 重要なのはModel3Dがコンポーネントの 追加をサポートしていないことです パーティクルエミッタを追加するには RealityViewに切り替えます レイアウトを変えずにModel3Dを RealityViewにスムーズに置き換える 方法を紹介します まずビューをModel3Dから RealityViewに切り替えます RealityViewの 「make」クロージャ内の App Bundleから Botanistモデルをロードして エンティティを作成します そのエンティティをRealityViewの コンテンツに追加し Sparkyを画面に登場させます
しかし名前のサインが横に はみ出してしまいました 以前Model3Dを使用していた時は こうではありませんでした これはSwiftUIレイアウトシステムが 与えた使用可能な スペースを RealityViewがデフォルトで すべて占有するからです 対照的にModel3Dは 基になるモデルファイルの 固有のサイズに基づいた サイズになります 修正します 新しい realityViewLayoutBehavior修飾子と fixedSizeを適用し RealityViewがモデルの初期境界を 厳密に取り囲むようにします ずっと良くなりましたね RealityViewではエンティティの コンテンツの中の視覚的境界を 使用してサイズを把握します このサイズは「make」クロージャが 実行された直後に一度だけ評価されます その他の 「realityViewLayoutBehavior」のオプションは .flexibleと.centeredです これら3つのRealityViewすべてにおいて Sparkyモデルの底部が シーンの原点に位置しています その原点をギズモでマークしました 小さな多色の十字で 軸と原点を示します 左側の「.flexible」オプションでは RealityViewは修飾子が 適用されていないかのように動作します 原点はビューの左上のままです 「.centered」オプションは RealityViewの原点を移動し コンテンツがビューの中央に 配置されるようにします 「.fixedSize」はRealityViewが コンテンツの境界を密接に取り囲み RealityViewがModel3Dと 同じように動作します
これらのオプションはいずれも RealityViewContentに関して エンティティを再配置 または拡大縮小しません RealityView自体の原点を 再配置するだけです RealityViewでSparkyの サイズを整理しました 次にSparkyを再び アニメーション化します エンティティ上で直接 Model3Dの新しいアニメーションAPIから RealityKitアニメーションAPIに 移行します RealityKitのアニメーションを取り扱う 様々な方法について詳しくは 「Compose interactive 3D content in Reality Composer Pro」のセッションをご覧ください Model3Dから RealityViewに切り替えてSparkyに ParticleEmitterComponentを与えましたが それはこの2つのロボットが接近すると 火花が飛ぶ必要があるからです パーティクルエミッタで エフェクトを作成できます 数百の小さなパーティクルが一度に アニメーション化するエフェクトで 花火や雨、キラキラなどです RealityKitはこれらの プリセット値を提供します 皆さんがプリセットを調整することで 狙った効果が得られます Reality Composer Proを使用して デザインしたりコードで設定したりできます ParticleEmitterをComponentとして エンティティに追加します コンポーネントは RealityKitの中心的な部分であり 「エンティティコンポーネントシステム」 パラダイムに基づいています シーン内の各オブジェクトはエンティティで 皆さんはコンポーネントを追加して そのオブジェクトが持つ 特性や動作を指定します コンポーネントはエンティティに 関するデータを保持する型です システムは特定のコンポーネントを 持つエンティティを処理し そのデータを含むロジックを実行します パーティクルのアニメーション化 物理演算の処理、レンダリングなどの システムが組み込まれています RealityKitを使用してゲームやアプリ用の 独自のロジックを実行するための カスタムシステムを自作できます RealityKitのエンティティコンポーネント システムの詳細は 「Dive into RealityKit 2」を ご覧ください パーティクルエミッタをSparkyの頭の 両側に追加します まず見えないエンティティを2つ作成します これは火花効果のコンテナとして機能します エミッタは右を向くようにデザインしました Sparkyの右側の見えない エンティティに直接追加します
反対側ではエンティティを y軸の周りで180度回転させ 左を向くようにしました
RealityViewですべてをまとめます これがSparkyのアニメーションです 名前のサインが正しい位置にあり 火花が飛んでいます
RealityKitはこのような 細部の作成に最適です ゲームやプレイ指向の体験を 制作している場合は または3Dコンテンツの動作を きめ細かく制御する必要がある場合 RealityViewを選択しましょう 一方Model3Dは自己完結型の 3Dアセットを単独で表示するために使用します 3Dアセット用のSwiftUIのイメージビューの ようなものだと考えてください
Model3Dの新しいアニメーションと構成 カタログでModel3Dをさらに活用できます 設計が進化してエンティティ コンポーネント、システムに 直接アクセスする必要がある場合は realityViewLayoutBehaviorを使うと Model3DからRealityViewに簡単に移行します 次に新しいオブジェクト操作の 詳細を共有します visionOS 26のAPIによりユーザーはアプリ内で バーチャルオブジェクトを選択できます オブジェクトの操作はSwiftUIと RealityKitの両方から行えます オブジェクト操作を使い 片手でオブジェクトを動かし 片手または両手で回転させ 両手でつまんだりドラッグしたりして 拡大縮小します オブジェクトを片方の手から もう片方の手に渡すこともできます
それには2つの方法あります オブジェクトがRealityKitエンティティか SwiftUIビューのどちらかによって異なります SwiftUIでは新しい 「manipulable」修飾子を追加します スケーリングを禁止しながら 移動機能は維持し どちらかの手でロボットを回転させるために サポートされる操作を指定します
ロボットに重量感を持たせるために 慣性が高いことを指定します
.manipulable修飾子はSparkyが Model3Dビューに表示されているときに機能します Model3D全体またはアタッチされている すべてのビューに適用されます SparkyがRealityViewにいるときは 操作を有効にするのは RealityView全体ではなく ロボットエンティティ本体のみです visionOS 26ではManipulationComponentは エンティティに設定できる新しい型で オブジェクト操作を有効にできます 静的関数「configureEntity」はエンティティに ManipulationComponentを追加します またCollisionComponentも追加して エンティティがタップされた時に インタラクションシステムが 認識できるようにします InputTargetComponentを追加しシステムに このエンティティがジェスチャーに 応答することを伝えます 最後にユーザーが見たときや マウスをその上に置いたときの 視覚効果を適用する HoverEffectComponentを追加します これはシーン内のエンティティの操作を 有効にするために必要な唯一の行です 体験をさらにカスタマイズするために 渡すことができるパラメータが数個あります ここでは紫色のスポットライト効果を 指定しています 直接的タッチ、間接的視線、ピンチの すべてのタイプの入力を許可しています またロボットの外形寸法を定義する コリジョンシェイプを提供しています ユーザーがアプリ内のオブジェクトを 操作したときに応答するために オブジェクト操作システムは 重要なタイミングでイベントを発生させます 例えばインタラクションが開始/終了する時 エンティティの移動、回転、拡大縮小で 更新される時、エンティティが解放される時 および片方の手から 反対の手に渡される時などです これらのイベントをサブスクライブして 状態を更新します デフォルトではインタラクションが 始まった時ハンドオフが起こった時 またはオブジェクトがリリースされた時に 標準のサウンドが再生されます カスタムサウンドを適用するには まずaudioConfigurationを 「none」に設定します これにより標準のサウンドが無効になります 次にManipulationEvent DidHandOffを サブスクライブします これはロボットが片方の手から もう片方の手に渡った時に提供されます そのクロージャで自分の オーディオリソースを再生します さてMaks Sparkyは楽しい体験をしてきました: Model3Dでアニメーション化したり RealityViewで新しい家を見つけたり 火花が飛ぶ性格も紹介したりしてきました 手を伸ばして交流できることも説明しました RealityKit温室への長い道のりを 歩んできました 確かにそうです しかしSparkyがそこで待っている ロボットと本当につながるためには 仮想空間内のオブジェクトには 新しい機能が必要です ジェスチャーに反応し 自分自身に関する情報を提示し SwiftUIにネイティブに感じられる方法で アクションをトリガーできる必要があります
RealityKitの温室への Sparkyの道のりでは つながりを築くことが大切です 深くつながるには豊富な インタラクションが必要です 新しいSwiftUI RealityKit コンポーネントのデザインの目的は それをできるようにすることです visionOS 26の新しいコンポーネントは パワフルで使い慣れたSwiftUIの機能を RealityKitエンティティに直接統合しています RealityKitには次の3つの 主要コンポーネントが導入されています まずViewAttachmentComponentを使用すると SwiftUIビューをエンティティに 直接追加できます 次にGestureComponentはエンティティが タッチやジェスチャーに応答するようにします 最後にPresentationComponentは ポップオーバーのように RealityKitシーン内から SwiftUIビューを表示します
visionOS 1によりアタッチメントを RealityView初期化子の一部として 前もって宣言することができます 添付ファイルビュービルダーを評価した後 システムは結果をエンティティとして 更新クロージャを呼び出しました これらのエンティティをシーンに追加し 3D空間に配置することができます visionOS 26ではこれが 簡素化されています これでRealityKitコンポーネントを 使用してアプリ内のどこからでも 添付ファイルを作成できます ViewAttachmentComponentを 作成するには 任意のSwiftUIビューを指定します 次にそれをエンティティのコンポーネント コレクションに追加します
そのようにしてNameSignを SwiftUIからRealityKitに移動しました 次はジェスチャーについて説明します 「targetedToEntity」ジェスチャー修飾子を 使用することで RealityViewにジェスチャーを 添付できることは説明しました visionOS 26の新機能に GestureComponentがあります ViewAttachmentComponentと同様に GestureComponentをエンティティに直接追加し 通常のSwiftUIジェスチャーを渡します ジェスチャー値はデフォルトでは エンティティの座標空間で報告されます 非常に便利です GestureComponentをタップジェスチャーで使用して 名前のサインのオンとオフを切り替えます
ご覧ください このロボットの名前はBoltsです
ヒントですが ジェスチャーの ターゲットである任意のエンティティには InputTargetComponentと CollisionComponentの両方も追加します このアドバイスはGestureComponentと targeted gestures APIの 両方に適用されます
GestureComponentと ViewAttachmentComponentで Boltsの名前サインが作れます しかしBoltsは特別な訪問者 Sparkyのために準備をしています Boltsは温室での会う時に 最高の姿を見せようとしています 着替えをしましょう Boltsの名前サインをBoltsが 着るものを選ぶUIに置き換えます 本当に重大な決断です
それを強調するためにこのUIを ポップオーバーで表示しますが これにはRealityKitから直接 PresentationComponentを使用します
まず「ViewAttachmentComponent」を 「PresentationComponent」に置き換えます コンポーネントはポップオーバーがいつ表示 されるかを制御し 誰かがポップオーバーを 閉じたときに通知するために ブールバインディングを受け取ります 「configuration」パラメーターは 表示するプレゼンテーションのタイプです 「ポップオーバー」を指定しています ポップオーバー内に Boltsをドレスアップするための構成の Catalogオプションのビューを示します これでBoltsがSparkyの訪問に備えて 最高の色を選ぶのを手伝うことができます
Maks、Boltsに似合うのは夏の色だと思いますか? それとも秋の色?
ファッションのジョークでした
Boltsは好印象を与える服装をしています しかし先に仕事があります Boltsは温室内の植物に水をやります ゲームのヘッドアップディスプレイの ようなミニマップを作り 温室内のBoltsの位置を トラッキングします そのにはロボットの Transformコンポーネントの監視が必要です visionOS 26ではエンティティが 監視可能になりました プロパティが変更されたときに 他のコードに通知できます 通知を受け取るにはエンティティの 「observable」プロパティを読み取るだけです
「observable」プロパティからは エンティティの様々な変化を監視できます これには位置、拡大縮小、回転 その子のコレクションおよび カスタムコンポーネントを含む コンポーネントの変化が含まれます 「withObservationTracking」ブロックを使用して これらのプロパティを直接監視します またはSwiftUIに組み込みの監視 トラッキングに頼ることもできます SwiftUIを使ってミニマップを実装します Observationの詳細については 「Discover Observation in SwiftUI」をご覧ください このビューではエンティティの位置を ミニマップ上に表示します 自分のエンティティのobservable値に アクセスしています これは私のビューがこの値に 依存していることをSwiftUIに伝えます
Boltsが温室内を移動して 植物に水をやると その位置が変わります 位置が変わるたびSwiftUIは 再びビューの本体を呼び出し ミニマップの対応するシンボルを動かします SwiftUIのデータフローについての 詳しい説明は 「Data Essentials in SwiftUI」セッションを ご覧ください ロボットの友達同士の出会いは もうすぐですね それが夢です 先ほどの「モデル」と「モデル」の違いの 説明は良かったと思います データモデルから3Dオブジェクトモデルに データを渡す必要がある時と その逆が必要な時があります visionOS 26では監視可能なエンティティが それを行うための新しいツールを提供します 当初からRealityViewの 「update」クロージャでSwiftUIから RealityKitに情報を渡すことが可能でした エンティティの「observable」プロパティを 使うと逆方向に情報を送信できます RealityKitエンティティは モデルオブジェクトのように 機能して SwiftUIビューの更新を行います 情報は双方向に流れるようになりました SwiftUIからRealityKitへ そしてRealityKitからSwiftUIへ しかし これにより無限ループが発生する 可能性はありますか はい SwiftUIとRealityKitの間に 無限ループを 作らないようにする方法を説明します ビューの本体内の observableプロパティを読み取ると ビューがそのプロパティに依存するという 依存関係が作成されます プロパティの値が変更されると SwiftUIはビューを更新して 本体を再実行します RealityViewには特別な動作があります 例えば その更新クロージャをビュー本体の 拡張と考えてください
SwiftUIはクロージャのビューの状態が 変更されるたびにクロージャを呼び出します クロージャで明示的に監視される状態が 変化するときだけではありません RealityViewのアップデートクロージャで これから位置を変更します これが位置の値に書き込まれ SwiftUIによってビューが更新され 本体が再実行されます これが無限ループを起こします
無限ループを発生させないよう 監視された状態をupdateクロージャ内で 変更しないでください
監視対象でないエンティティは 自由に変更できます その変更は無限ループを発生させません なぜなら SwiftUIによるビュー本体の再評価を トリガーしないからです 監視対象のプロパティを変更する 必要がある場合は そのプロパティの既存の値を確認し 同じ値を書き戻さないようにします これによりサイクルが中断され 無限ループが回避されます RealityViewのmakeクロージャは 特殊なため注意してください makeクロージャでobservableプロパティに アクセスすることは 依存関係を作りません 収容するビューの監視範囲には含まれません また「make」クロージャは 変更時に再実行されません 収容するビューが最初に表示された ときにのみ実行されます 監視対象エンティティのプロパティは 独自のカスタムシステム内から 更新することもできます システムの更新関数は SwiftUIビュー本体評価の 範囲内にないので これは監視対象エンティティの値を 変更する場所として適しています
GesturesクロージャもSwiftUIビューの 本体評価の範囲内にありません これらはユーザー入力に応答して 呼び出されます ここでも監視対象エンティティの値を 変更できます 要約すると監視対象エンティティを ある場所では修正でき他の場所では できないということです
アプリに無限ループがあることに 気付いた場合の 修正のヒントです 大きなビューを小さな 自己完結型のビュー ビューに分割し それぞれが必要な 状態のみを持つようにします そうすれば無関係な エンティティの変更により 小さなビューが再評価されることは ありません これはパフォーマンスの面でも最適です これからは「update」クロージャを使う 必要がないかも しれませんね エンティティをビューの状態に することができるようになったため これまで状態の変更をしていた 通常の場所で変更できるので updateクロージャを 完全にやめることができます そうですね 無限ループを避ける方法は 何度も学ばなければならない ことのような気がします しかしupdateクロージャを使わなければ それに遭遇する可能性は低くなるでしょう そろそろBoltsとSparkyを 会わせましょう Boltsの仕事も終わり Sparkyとデートする時間です Sparkyを抱き上げて持っていくと 2体のロボットは近づき 2体の間の距離の短縮の 関数として火花を飛ばします 新しいUnified Coordinate Conversion API を使いこれを有効にします SparkyはModel3D SwiftUIビューにあり BoltsはRealityKit温室の エンティティです この2つのロボットは異なる 座標空間にありますがその間の 絶対距離を取得する必要があります これを解決するためSpatialフレームワークで 「CoordinateSpace3D」プロトコルを定義できます これは抽象座標空間を表します 異なるフレームワークからでも CoordinateSpace3Dに準拠する 任意の2つの型の間で値を 簡単に変換できます RealityKitの「Entity」型と「Scene」型は CoordinateSpace3Dに準拠しています SwiftUI側のGeometryProxy3Dには 新しい.coordinateSpace3D()関数があり それが座標空間を提供します さらにGesture型は 特定のCoordinateSpace3Dに対する 相対値を指定できます CoordinateSpace3Dプロトコルは まずSparkyの座標空間の値を RealityKitとSwiftUIの両方で 共有される座標空間に 変換することで機能します その後共有空間から Boltの座標空間に変換しますが その際ポイントからメーターへの 変換と 軸方向など 低レベルの詳細を考慮に入れます SparkyのModel3Dビューでは ビューのジオメトリが変更されるたびに システムは「onGeometryChange3D」関数を 呼び出します 座標空間を取得するために使用する GeometryProxy3Dを渡します 次にビューの位置をエンティティの 空間内の点に変換すると 2体のロボットが互いから どれだけ離れているかがわかります AmandaがBoltsとSparkyを 近づけると火花が増えます 遠ざけると火花は減少します
次にこれらのロボットに一緒に動いて アクションを調和させることを教えます RealityKitコンポーネントには SwiftUIによるアニメーションを使います SwiftUIには優れた アニメーションAPIが搭載されていて ビュープロパティの変更を 暗黙的にアニメーション化します ここではSparkyがいるModel3Dビューを アニメーション化しています 切り替えると左に動き もう一度切り替えると 元の位置に跳ね返ります 「isOffset」バインディングに アニメーションを追加して それに特によく弾むアニメーションを 指定しています visionOS 26では SwiftUIアニメーションを使用して RealityKitコンポーネントへの変更を 暗黙的にアニメーション化できます 必要なのは RealityKitアニメーションブロックで エンティティにサポートされている コンポーネントを設定することだけで 残りはフレームワークが処理します アニメーションを状態変化に 関連付ける方法は2つあります RealityView内からは 「content.animate()」を使用して animateブロック内で コンポーネントに新しい値を設定できます RealityKitは「update」クロージャを トリガしたSwiftUIトランザクションに 関連付いたアニメーションを使用します この場合これは 特によく弾むアニメーションです
もう1つの方法は新しい Entity.animate()関数を呼び出し SwiftUIアニメーションと コンポーネントに新しい値を 設定するクロージャを渡すことです ここでは「isOffset」プロパティが 変更されるたびに エンティティの位置を使用して Sparkyを左右に動かします animateブロック内の位置を設定すると Transformコンポーネントの暗黙的な アニメーション化が開始され エンティティを新しい位置に スムーズに移動させます 暗黙的なアニメーションの力は Amandaが紹介した Object Manipulation APIと 組み合わせたときに本当に発揮されます SwiftUIアニメーションを使用してBoltsの カスタムリリース動作を実装できます まずオブジェクト操作のデフォルト リリース動作を.stayに設定して 無効にします 次に操作インタラクションの WillReleaseイベントをサブスクライブします オブジェクトが解放されそうになったら Sparkyを元に戻すために Sparkyの変換を恒等に設定します この設定はエンティティのサイズ 移動、回転をリセットするものです animateブロック内のSparkyの 変換を修正しているので Sparkyはデフォルトの位置に跳ね返ります Sparkyが元の位置に戻るアニメーションが はるかに楽しくなりました 内蔵のRealityKitコンポーネントはすべて 暗黙的アニメーションをサポートしています それにはトランスフォームコンポーネントや オーディオコンポーネント カラープロパティがある モデルやライトのコンポーネントもあります SparkyとBoltsの 説明は以上です SwiftUIとRealityKitの機能の連携は すばらしいですね さらに 仮想空間と実空間の 実際のつながりを育むことで 真に優れた空間アプリを 開発できるようになりました SwiftUIコンポーネントを RealityKitシーンにシームレスに 統合することや エンティティが SwiftUIの状態の変化を動的に 促すことの可能性を想像してみてください 皆さんがSparkyとBoltsのように SwiftUIとRealityKitを 想像もできないような方法で 連携させるきっかけになれば幸いです 一緒に未来を構築しましょう
-
-
1:42 - Sparky in Model3D
struct ContentView: View { var body: some View { Model3D(named: "sparky") } }
-
1:52 - Sparky in Model3D with a name sign
struct ContentView: View { var body: some View { HStack { NameSign() Model3D(named: "sparky") } } }
-
struct RobotView: View { @State private var asset: Model3DAsset? var body: some View { if asset == nil { ProgressView().task { asset = try? await Model3DAsset(named: "sparky") } } } }
-
struct RobotView: View { @State private var asset: Model3DAsset? var body: some View { if asset == nil { ProgressView().task { asset = try? await Model3DAsset(named: "sparky") } } else if let asset { VStack { Model3D(asset: asset) AnimationPicker(asset: asset) } } } }
-
struct RobotView: View { @State private var asset: Model3DAsset? var body: some View { if asset == nil { ProgressView().task { asset = try? await Model3DAsset(named: "sparky") } } else if let asset { VStack { Model3D(asset: asset) AnimationPicker(asset: asset) if let animationController = asset.animationPlaybackController { RobotAnimationControls(playbackController: animationController) } } } } }
-
struct RobotAnimationControls: View { @Bindable var controller: AnimationPlaybackController var body: some View { HStack { Button(controller.isPlaying ? "Pause" : "Play") { if controller.isPlaying { controller.pause() } else { controller.resume() } } Slider( value: $controller.time, in: 0...controller.duration ).id(controller) } } }
-
5:41 - Load a Model3D using a ConfigurationCatalog
struct ConfigCatalogExample: View { @State private var configCatalog: Entity.ConfigurationCatalog? @State private var configurations = [String: String]() @State private var showConfig = false var body: some View { if let configCatalog { Model3D(from: configCatalog, configurations: configurations) .popover(isPresented: $showConfig, arrowEdge: .leading) { ConfigPicker( name: "outfits", configCatalog: configCatalog, chosenConfig: $configurations["outfits"]) } } else { ProgressView() .task { await loadConfigurationCatalog() } } } }
-
6:51 - Switching from Model3D to RealityView
struct RobotView: View { let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")! var body: some View { HStack { NameSign() RealityView { content in if let sparky = try? await Entity(contentsOf: url) { content.add(sparky) } } } } }
-
7:25 - Switching from Model3D to RealityView with layout behavior
struct RobotView: View { let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")! var body: some View { HStack { NameSign() RealityView { content in if let sparky = try? await Entity(contentsOf: url) { content.add(sparky) } } .realityViewLayoutBehavior(.fixedSize) } } }
-
8:48 - Switching from Model3D to RealityView with layout behavior and RealityKit animation
struct RobotView: View { let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")! var body: some View { HStack { NameSign() RealityView { content in if let sparky = try? await Entity(contentsOf: url) { content.add(sparky) sparky.playAnimation(getAnimation()) } } .realityViewLayoutBehavior(.fixedSize) } } }
-
10:34 - Add 2 particle emitters; one to each side of the robot's head
func setupSparks(robotHead: Entity) { let leftSparks = Entity() let rightSparks = Entity() robotHead.addChild(leftSparks) robotHead.addChild(rightSparks) rightSparks.components.set(sparksComponent()) leftSparks.components.set(sparksComponent()) leftSparks.transform.rotation = simd_quatf(Rotation3D( angle: .degrees(180), axis: .y)) leftSparks.transform.translation = leftEarOffset() rightSparks.transform.translation = rightEarOffset() } // Create and configure the ParticleEmitterComponent func sparksComponent() -> ParticleEmitterComponent { ... }
-
12:30 - Apply the manipulable view modifier
struct RobotView: View { let url: URL var body: some View { HStack { NameSign() Model3D(url: url) .manipulable() } } }
-
12:33 - Allow translate, 1- and 2-handed rotation, but not scaling
struct RobotView: View { let url: URL var body: some View { HStack { NameSign() Model3D(url: url) .manipulable( operations: [.translation, .primaryRotation, .secondaryRotation] ) } } }
-
12:41 - The model feels heavy with high inertia
struct RobotView: View { let url: URL var body: some View { HStack { NameSign() Model3D(url: url) .manipulable(inertia: .high) } } }
-
13:18 - Add a ManipulationComponent to an entity
RealityView { content in let sparky = await loadSparky() content.add(sparky) ManipulationComponent.configureEntity(sparky) }
-
RealityView { content in let sparky = await loadSparky() content.add(sparky) ManipulationComponent.configureEntity( sparky, hoverEffect: .spotlight(.init(color: .purple)), allowedInputTypes: .all, collisionShapes: myCollisionShapes() ) }
-
14:08 - Manipulation interaction events
public enum ManipulationEvents { /// When an interaction is about to begin on a ManipulationComponent's entity public struct WillBegin: Event { } /// When an entity's transform was updated during a ManipulationComponent public struct DidUpdateTransform: Event { } /// When an entity was released public struct WillRelease: Event { } /// When the object has reached its destination and will no longer be updated public struct WillEnd: Event { } /// When the object is directly handed off from one hand to another public struct DidHandOff: Event { } }
-
14:32 - Replace the standard sounds with custom ones
RealityView { content in let sparky = await loadSparky() content.add(sparky) var manipulation = ManipulationComponent() manipulation.audioConfiguration = .none sparky.components.set(manipulation) didHandOff = content.subscribe(to: ManipulationEvents.DidHandOff.self) { event in sparky.playAudio(handoffSound) } }
-
16:19 - Builder based attachments
struct RealityViewAttachments: View { var body: some View { RealityView { content, attachments in let bolts = await loadAndSetupBolts() if let nameSign = attachments.entity( for: "name-sign" ) { content.add(nameSign) place(nameSign, above: bolts) } content.add(bolts) } attachments: { Attachment(id: "name-sign") { NameSign("Bolts") } } .realityViewLayoutBehavior(.centered) } }
-
16:37 - Attachments created with ViewAttachmentComponent
struct AttachmentComponentAttachments: View { var body: some View { RealityView { content in let bolts = await loadAndSetupBolts() let attachment = ViewAttachmentComponent( rootView: NameSign("Bolts")) let nameSign = Entity(components: attachment) place(nameSign, above: bolts) content.add(bolts) content.add(nameSign) } .realityViewLayoutBehavior(.centered) } }
-
17:04 - Targeted to entity gesture API
struct AttachmentComponentAttachments: View { @State private var bolts = Entity() @State private var nameSign = Entity() var body: some View { RealityView { ... } .realityViewLayoutBehavior(.centered) .gesture( TapGesture() .targetedToEntity(bolts) .onEnded { value in nameSign.isEnabled.toggle() } ) } }
-
17:10 - Gestures with GestureComponent
struct AttachmentComponentAttachments: View { var body: some View { RealityView { content in let bolts = await loadAndSetupBolts() let attachment = ViewAttachmentComponent( rootView: NameSign("Bolts")) let nameSign = Entity(components: attachment) place(nameSign, above: bolts) bolts.components.set(GestureComponent( TapGesture().onEnded { nameSign.isEnabled.toggle() } )) content.add(bolts) content.add(nameSign) } .realityViewLayoutBehavior(.centered) } }
-
-
- 0:00 - イントロダクション
Learn about RealityKit SwiftUI enhancements that enable seamless integration of traditional UI and interactive 3D content. Key updates include enhancements to Model3D and RealityView, the introduction of the Object Manipulation API, new Component types, bidirectional data flow between SwiftUI and RealityKit, easier coordinate space conversion, and SwiftUI-driven animations for RealityKit components.
- 1:24 - Model3Dの機能強化
In visionOS 26, Model3D enables the display of 3D models with animations and loading from a 'ConfigurationCatalog'. You can now create interactive 3D experiences with just a few lines of code. Use the new 'Model3DAsset' type to load and control animations, and 'ConfigurationCatalog' enables switching between different representations of an entity, such as outfits or body types. The example of a robot named Sparky, who you can animate, dress up in different outfits, and control using SwiftUI views and pickers, making it ready to interact with other robots in a virtual greenhouse, demonstrates these features.
- 6:13 - RealityViewへの移行
To add a particle emitter to a 3D model at runtime, you must switch from using 'Model3D' to 'RealityView' in RealityKit because Model3D doesn't support adding components. RealityView is loaded with the model entity, but this causes layout issues as it defaults to taking up all available space. The 'realityViewLayoutBehavior' modifier is applied with 'fixedSize' to resolve this, making RealityView wrap the model's initial bounds tightly. There are three different 'realityViewLayoutBehavior' options: 'flexible', 'centered', and 'fixedSize', each affecting the RealityView's origin point without repositioning the entities. Use Reality Composer Pro to design particle emitters that are configurable in code. Particle emitters are added to entities as components. RealityKit is based on the "Entity Component System" paradigm. The example adds particle emitters to create spark effects, involving invisible entities as containers for the sparks. RealityView is great for detailed creation and fine-grained control over 3D content behavior, while Model3D is suitable for displaying self-contained 3D assets. You can transition smoothly between the two as needed.
- 11:52 - オブジェクトの操作
The new Object Manipulation API in visionOS 26, enables people to interact with virtual objects in apps that use SwiftUI and RealityKit. People can move, rotate, and scale objects with their hands, and even pass objects between hands. For SwiftUI views, you can apply the 'manipulable' modifier, allowing customization of supported operations and object inertia. In RealityKit, add the 'ManipulationComponent' to entities via the static 'configureEntity' function, which also adds collision, input target, and hover effect components. The function has several parameters for behavior customization. Subscribe to 'ManipulationEvents' triggered during interactions, such as starts, stops, updates, releases, and hand-offs, to update app state and customize the experience, including replacing default sounds with custom audio resources.
- 15:35 - SwiftUIのコンポーネント
The new SwiftUI RealityKit components enhance user interaction and connection within RealityKit scenes. These components enable you to seamlessly integrate SwiftUI views into 3D environments. Key features include the 'ViewAttachmentComponent', which allows you to add SwiftUI views directly to entities; the 'GestureComponent', making entities responsive to touch and gestures; and the 'PresentationComponent', which enables the presentation of SwiftUI views, like popovers, within the scene. The configuration parameter on 'PresentationComponent' is the type of presentation to show. These improvements simplify the development process, enabling you to create more dynamic and engaging experiences. In the example, a robot named Bolts can have a name sign that toggles on and off with a tap gesture, and people can choose Bolts' outfit from a popover menu, all within the immersive RealityKit environment.
- 19:08 - 情報のフロー
In visionOS 26, entities are now observable, which allows entities, such as the robot Bolts, to notify other code when their properties change. This is particularly useful for tracking Bolts' movements as it waters plants in the greenhouse. The example uses SwiftUI to implement a minimap that displays Bolts' position in real-time. By accessing Bolts' observable position property, SwiftUI automatically updates the minimap whenever Bolts moves. This two-way data flow between SwiftUI and RealityKit, enabled by observable entities, is a significant new tool. This new observable functionality also introduces the potential for infinite loops if not managed carefully. To avoid these loops, be mindful of where you modify observed state. Don't make changes to observed state within the 'update' closure of 'RealityView', because this can trigger a recursive update cycle. Instead, make modifications in specific places like custom systems, gesture closures, or the 'make' closure, which are outside the scope of SwiftUI's view body evaluation. By breaking down larger views into smaller, self-contained ones, and being cautious about where state is modified, you can ensure smooth and efficient data flow between SwiftUI and RealityKit, avoiding infinite loops and enhancing the overall performance of the application.
- 24:56 - 統合座標への変換
The new Unified Coordinate Conversion API bridges the gap between RealityKit and SwiftUI. By implementing the 'CoordinateSpace3D' protocol, you can seamlessly convert values between the two frameworks. This allows calculating the absolute distance between Bolts, an Entity in RealityKit, and Sparky, a Model3D in SwiftUI, as they move closer together, dynamically generating sparks based on their proximity.
- 27:01 - アニメーション
In visionOS 26, you can now leverage SwiftUI's animation APIs to implicitly animate changes to RealityKit components. This enables smooth and bouncy movements of entities by simply setting supported components within an animation block. There are two methods to achieve this: using 'content.animate()' within a RealityView or calling 'Entity.animate()' directly. This integration allows for custom release behaviors when manipulating entities, making interactions with 3D objects more engaging and fun. Various RealityKit components, including Transform, Audio, Model, and Light, support these implicit animations.
- 29:41 - 次のステップ
Use this new connection between SwiftUI and RealityKit to create innovative spatial apps by integrating SwiftUI components with RealityKit scenes, enabling dynamic interactions between the virtual and physical worlds, and inspiring new possibilities in app development.