ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
RealityKit 2の詳細
RealityKit 2を使えば、魅力的なAR体験の作成がこれまで以上に簡単になります。RealityKitフレームワークの最新の強化点を探り、水中サンプルプロジェクトを通じて詳細を確認しましょう。機能が強化されたエンティティコンポーネントシステム、効率化したアニメーションパイプライン、フェイスメッシュとオーディオを強化したプラグアンドプレイのキャラクターコントローラーを紹介します。
リソース
- Applying realistic material and lighting effects to entities
- Building an Immersive Experience with RealityKit
- Creating a Fog Effect Using Scene Depth
- Creating an App for Face-Painting in AR
- Explore the RealityKit Developer Forums
- PhysicallyBasedMaterial
- RealityKit
関連ビデオ
WWDC23
WWDC22
WWDC21
WWDC20
WWDC19
-
ダウンロード
♪♪ こんにちは アマンダです 同僚のオリビエも 後ほど参加します このセッションでは 2021年にRealityKitに 追加する機能について 説明します RealityKitはリアルな レンダリングに焦点を当てた 2019年に導入された フレームワークです AR Appが簡単に 作成できます ARKitでデバイスの センサーデータを読取り RealityKitを使用すると 3Dコンテンツを 現実世界に配置し できるだけリアルな表現で 表示することができます RealityKitによる体験から いくつか例を 次に示します 現実世界でScavengar ハントに出かけたり 友達とバーチャルに ボーリング対戦したり 博物館の彫刻やなったり カラフルな虫を探すこともできます 過去2年の間にRealityKitで 作成された― すばらしいAppを見を見てきました このフレームワークを さらに改善するための フィードバックも いただきました
そのフィードバックに 耳を傾け RealityKit 2として 多数の新機能を導入し 没入感の高いAR Appや ゲームを提供できる ようになりました
このセッションではそれらを いくつか見ていきます 最も要望の高かった 機能には カスタムシェーダーや マテリアル カスタムシステムに 新しいキャラクター コントローラーがあります シュノーケルを付けて 飛び込みましょう 私は中東育ちですが スキューバダイビングを 学びました こんなにかわいい ヘルメットは 被れませんでしたが 色とりどりの 魚の群れを見るのが 大好きでした これを再現したいと 思ったのです 水中の気分をリビングに 持ち込むわけです このセッションで紹介する 一連の機能を使用して このデモを作成しました 週の後半の2回目の RealityKitセッションでも紹介します 深霧効果と水のコースティクスを 作成するポストプロセッシングや カスタムジオメトリ モディファイアで 海藻を波の中で 踊らせるることなどが可能です RealityKit 2では多くを カスタマイズできる ようになりました このサンプルコードは 開発者サイトから お試しいただけます
今日取り上げる主な トピックは5点です ECSとは何かを確認し カスタムシステム機能で Appでの魚の群れを どのように 実装したかをご紹介 します アニメーションだけでなく マテリアルでできることの進化や 新しいキャラクター コントローラーで ダイバーのシームレスな リビングのARメッシュとの インタラクションや ランタイムで リソースを生成する方法 について説明します
それではECSから 始めましょう ECSはエンティティ コンポーネントシステムの略で データと動作を構造化 する方法です ゲームやシミュレーションで 広く使われています オブジェクト指向 プログラミングでは アイテムをカプセル化された バンドルとして その機能とステートを アイテムに関連付けることが 通常です ECSには3つのプロングスが あります エンティティとコンポーネント それにシステム 機能がシステムに組込まれ ステートはコンポーネントに エンティティはコンポーネントの グループの識別子となります 今年はRealityKit 2を 使用して より純粋なECS実装に 移行しています 新しいカスタムシステムを 使用してシステムレイヤで より多くの機能を 維持するようにガイドします
エンティティとは何を 意味するのでしょうか? エンティティはシーン内の 1つのものを表します これがシーンの海の生物を 表すエンティティです エンティティは子エンティティを 持つことができ 操作するグラフ構造を 提供します たとえばトランスフォーム コンポーネントは 親エンティティのトランスフォームを 利用して自身の位置を追加します エンティティ自体は画面上に 何もレンダリングしません モデルコンポーネントを指定するか モデルエンティティを作成することで 自動的に処理されます
属性とプロパティおよび動作を 追加するには エンティティに コンポーネントを追加します ではコンポーネントについて お話しします
コンポーネントはフレーム間の ステートを保持し エンティティのシステムへの参加を マーキングするためのものです ただしここでそのステートを 処理するためのロジックを 含める必要はありません ロジックと動作はカスタム システムに組み込まれます
作成するエンティティに すでに存在する コンポーネントが いくつかあります 非表示の組み込みコンポーネント であるトランスフォームや 同期コンポーネントです 3つのエンティティ すべてにあります メッシュやマテリアルなどを含む エンティティを画面に表示するための モデルコンポーネントなど 多くの場合に 追加したいものがあります 実行時にエンティティに コンポーネントを追加したり エンティティからコンポーネントを 削除することも 動作を動的に変更したい場合に 行うことができます
最初の魚をまず フロックシステムで 藻を食べるのが 好きな魚と設定 2番目の魚は 最初の魚と 群がりますが エサはプランクトンを 好みます 3番目はプランクトン 二匹目の餌になります 背後に気をつけなくちゃ Appの中に 空腹の生き物がいるので 藻食いなのか プランクトン食いなのか コンポーネントで空腹を判断します フレームごとにEatingシステムの 更新関数を呼び出し すべてのシーン内の エンティティが いずれかのコンポーネントを 持ち エサのエンティティがあるため エサの下に空腹の 魚を導きます どのエンティティが 空腹であるかを Eatingシステム把握するために 最適な方法とは何でしょうか? どれがエサで どれがそのどちらでもないのか エンティティグラフを トラバースして コンポーネントを 確認しないで済ませたいです 代わりにエンティティ クエリを実行します RealityKitに管理を 任せます フロックシステムでは フロックコンポーネントを 持つ全エンティティを検索 Eatingシステムは 両方のタイプの空腹エンティティに加え エサのエンティティが必要です システムがエンティティ クエリを使用するときの 処理を詳しく見て みましょう システムには全フレームで 呼ばれる更新関数があります キイロハギの フロックシステムを 見てみましょう このフレームで一時停止して 内容を確認します フロックシステムの 更新関数では フロックとモーションの コンポーネントを両方もつ シーン内すべての エンティティをクエリします 多くのものにモーション コンポーネントがありますが 全てが必要ではなく ここではフロックのものだけが必要です クエリは群がっている 魚を返すので 群れの各魚に対する ボイドシミュレーションを適用した カスタムゲーム物理演算を 動作させます 各々の魚の モーションコンポーネントに フレーム間のステートを状態を維持し 群れるためのフォースを追加します これは一定の距離を 保つことと 鼻先を同じ方向に 向けさせるための 処理です
モーションシステムが実行 されているとき 同じフレーム内でフロック システムが実行された後 すべてのフォースをまとめて 魚の加速度や 速力に位置などを 決定します 他のどのシステムが追加 したかは関係ありません 他にもEatingシステムや モーションコンポーネントで動作する 魚にとっての恐怖 システムにより 魚をさまざまな方向に 動かします コードを見てみましょう
これがフロックシステムの 概要です これはRealityKit.System プロトコルに準拠する クラスです Appの起動時にカスタム システムを登録すると Appのシーンごとにこの タイプの1つを インスタンス化するように エンジンに指示します initは必須でdeinitを 指定することもできます 依存関係も指定 できます このシステムは常に モーションシステムの前に 実行する必要があるため 列挙値value.beforeを ここでは使用します 更新関数ではモーションコンポーネントに 格納されているステートを変更し モーションシステムは提供するステートに 基づいて処理されるので フロックがモーションの前に 実行されていることを ちょうど生産者と消費者の ような関係で 設定しておく必要が あります .afterオプションを使用 することもできます 依存関係を指定 しない場合は システムの更新関数は 登録した順序で 実行されます
EntityQueryは両方の コンポーネントを内包する すべてのエンティティが 必要であることを意味します シミュレーション中は 変更されないため staticなletとなります マルチプレイヤーAR体験では codableに準拠する コンポーネントがネットワーク 経由で自動的に同期されます ただしシステム内のデータは ネットワークを介しては 自動的には同期 されません データは一般的にコンポーネントに 保存される必要があります 次にフロックシステムの 更新関数について 詳しく見ていきましょう フレームのdeltaTimeと シーン自体への参照を含む SceneUpdateContextが 必要です まずシーンで エンティティクエリを実行 フロックコンポーネントを持つ エンティティに対し 反復処理できる クエリ結果が返されます
それぞれのモーションコンポーネントを 取得し これに変更を加えます なぜフロックコンポーネント自体を 取得しないのでしょうか? データが関連付けられて いないからです そこでタグのように 使用することで フロックのメンバーを 示します 次に標準のボイド シミュレーションを実行し モーションコンポーネントの フォースに変更を加え フロックのガイドとします 最後に各々の魚を設定 した方向に動かすため― またコンポーネントがSwiftの 値型である構造体のため モーションコンポーネントを 元のエンティティに 格納する必要があります システムへのカスタム更新 機能の実装は不要です シーンイベントのハンドラを 登録するなどinitのみを 提供するシステムを 作成することも有用です これまでエンティティや コンポーネントに カスタムシステム間の関係に ついて見てきました 少しズームアウトして ざっくりした部分として RealityKit 2で導入した アーキテクチャの変更を見ます 以前はフレームごとに呼ばれる クロージャで SceneEvents.updateイベントを サブスクライブしていました この種のハンドラは ゲームマネジャーの ようなクラスに登録 されていました そうしたクロージャの 代わりに更新ロジックを 使用できるようになり それぞれ分離され 別々のシステム更新関数で 正式に処理されるように なりました
ゲームマネジャーの 役割が減ります イベント更新のための 登録を行ったり ゲーム内の全ての事柄に対する 更新の順番を 管理したりする代わりに ゲームマネジャーは クエリに含める必要がある エンティティをシステムに示すべく コンポーネントを エンティティに追加するだけで 済むようになりました
以前はプロトコルへの 準拠を エンティティのサブクラスに 特定のコンポーネントを持つことの 表現として宣言していました ですがエンティティの サブクラス化は不要になりました あまり有用でなく なったからです
単にオブジェクト識別子 である可能性があり 属性はコンポーネント としてモデル化できます エンティティを サブクラス化しない場合は コンポーネントを永久に保持するよう オブジェクトを紐付けないので コンポーネントを 自由に追加および 削除できます RealityKit 2を使用すると カスタムコンポーネントが カスタムシステムが あるのではるかに便利になります どちらの方法でも実行は 可能です それがゲーム開発の 醍醐味です まさに自由自在 水中デモでは両方 利用しました
新しいタイプの コンポーネントは Transientコンポーネントです タコを見たことがない 場合に限り 魚がタコを恐れるものと します 新しい魚のエンティティを 複製するとき その魚のタコに対する恐怖を 継承させたくない場合 Fearコンポーネントを Transientコンポーネントに 紐付けられます そうすれば新しいエンティティには 適用されません
Transientコンポーネントは 他のコンポーネント同様 コード化可に準拠 している場合は ネットワーク同期に 含まれます
もう1つの追加項目が Cancellableの拡張です エンティティのイベントへの 登録解除を手動で 管理する必要が なくなりました storeWhileEntityActiveを 使用すると自動的に実行します
ここでは魚のエンティティの 衝突イベントを処理します 魚よりもサブスクリプションが 続く必要はないため storeWhileEntityActiveを 使用します ゲームを構築するときは 再コンパイルせずに その場で微調整したい 必要な設定が たくさんあると思います ここではSwiftUIで 設定ビューを作成し そのバッキングモデルを さまざまなカスタムシステムに CustomComponentsでラップして 渡します
設定インスタンスを @StateObjectとして作成し それをARViewContainerと SwiftUIビューの両方に environmentObject として渡します
設定オブジェクトを CustomComponentで SettingsComponent としてラップします 次に、魚エンティティを 作成するときに SettingsComponentを 指定します これで設定に必要な カスタムシステムの “最高速度”の値を適用し 魚の速力を制限します それではここで同僚の― オリヴィエからマテリアルに ついて説明してもらいます
ありがとうアマンダ 今年はマテリアルの 新しいAPIを追加しました ベースカラーや粗さ メタリックなどの プロパティのある SimpleMaterial またUnlitMaterialという 色のみでライトなし のものがあります 仮想オブジェクトを 非表示にするマスクとして 使用できるOcclusionMaterials そして昨年ビデオをカラーとして 使用できる UnlitMaterialsである VideoMaterialsも導入しました そして今年は透明度も サポートします ビデオファイルに透明度が 含まれている場合は オブジェクトのレンダリングに 使用されます 今年はマテリアルをより 高度に制御できる 新しいAPIを追加しました PhysicallyBasedMaterialは USDマテリアルの スキーマと似ています SimpleMaterialの スーパーセットであり 他のレンダラーで使用 可能なPBRプロパティの ほとんどを備えています これはUSDから ロードされた エンティティにある マテリアルです たとえばカクレクマノミの USDを読み込んでから マテリアルの プロパティを変更し 金色や紫に変更できます
マテリアルのプロパティで たとえば メッシュの一部ではない詳細を ノーマルマップを変更することで 追加することも 可能です モデルの透明度を定義する テクスチャを 割り当てることもできます デフォルトで透明度は アルファブレンディング を使いますが ただし opacityThresholdの設定により すべてのフラグメントは しきい値を下回ると 破棄されます
モデルで影を定義して アンビエントオクルージョンの テクスチャを設定できます より高度なプロパティの例は 追加のレイヤーにより マテリアル反射塗料を シミュレートするクリアコートです。 PhysicalBasedMaterial タイプで利用できる 他のプロパティも多数 あります
カスタムマテリアルという 新しいタイプも追加しました 独自のMetalコードを 使用して作成します これはタコのモデルの 色遷移効果に 使用したものです シェーダーとカスタム マテリアルについては 2つ目のレンダリングの トークにて説明します
マテリアルに加えて RealityKitのアニメーションに より多くの制御を 追加しました
アニメーションの既存APIを まず見ていきましょう これはUSDからロード されたアニメーションの 再生に関するものです USDからアニメーションを ロードする場合はすぐ再生でき 無限ループさせることも できます ダイバーが何もしない時の アニメーションに 必要なものです アニメーションを一時停止 再開また停止することも可能
新しいアニメーションを 再生するとき 遷移時間を 指定できます 指定しない場合 キャラクターは すぐに新しいアニメーションに 切り替わります 遷移時間を指定すると RealityKitは 新旧アニメーションの間に それらをブレンドします これはたとえばダイバーの ウォークとアイドルの サイクルへの 遷移に有効です 足のアニメーションを 改善することも可能
ブレンドレイヤーに 新しいAPIを使用して アニメーションを よりリアルにします 別々のブレンドレイヤー上で ウォーキングとアイドルの アニメーションを それぞれ再生します 最上層でウォーキング アニメーションを再生したので 現在表示されている アニメーションはこれだけです ただしウォーキングアニメーションの ブレンド係数を変更し 下層のアイドルアニメーションを 表示することもできます ブレンドファクターが 小さくなるにつれて 歩幅も小さくなります
また再生速度を 変更することもできます ダイバーの歩行を 速くしたり遅くしたり ダイバーを半分の 速度で歩かせたり
最後にキャラクターの 地面に対する速度を 各々の値に合わせて 制御します このようにしてアニメーションを よりスムーズにし 地面と比較して 足の滑りを抑えます
アイドルサイクルや ウォークサイクルなど これまで複数のアニメーション クリップを使用してきました これらはアニメーション リソースとして RealityKitに 保存されます USDファイルからロード するには複数の方法があります 最初の方法はクリップごとに USDファイルを用意 各USDをエンティティ としてロードし アニメーションをリソース として取得します その後リソースは スケルトンのジョイント名が アニメーションと一致していれば どのエンティティでも 再生できます
複数のアニメーションクリップを ロードする別の方法は 同じタイムラインで単一の USDにそれらを配置し AnimationViewを使用して 複数のクリップに スライスすることです
これには各クリップ間の タイムコードが重要です
各ビューが アニメーションリソースに変換され 前の方法と まったく同じ方法で 使用されます それではAppのタコの アニメーションを見てみましょう タコは隠れていますが プレイヤーが近づくと恐れて 新しい隠れ場所に 移動します それをアニメーション化する 方法を見てみましょう タコのスケルタルアニメーション であるジャンプ― 泳ぎ 着地などをロードする ところから始めます これらのアニメーションは ダイバーと同じように USDから読み込まれます タコのトランスフォームをアニメートして ある場所から次の場所に― 移動させることも必要です トランスフォームをアニメーション化 するために新しいAPIで― FromToByAnimation タイプを作成します
このようにして位置を アニメーション化します タコがどのように 見えるか見てみましょう
さらに面白くするために 回転させてみましょう
タコが動くと回転する ようになりましたが 横に泳いでいるので あまり現実的ではありません アニメーションシーケンス 作成することでこれを改善できます まずタコを新しい場所に 向けて回転させます 次にそれを新しい 場所に変換します そして最後にタコを 回転させ カメラに向かって 戻らせます これが完成形です
アニメーション用の新しい APIに加えてキャラクターの 物理演算を管理する方法も 追加されました キャラクター コントローラーです これによりシーン内のコライダーと 物理的に相互作用する キャラクターを 作成できます ここではダイバーが床から ソファにジャンプして その上を歩いているのが 見えます これはダイバーに― キャラクターコントローラーを 追加することで実現しています これによりダイバーは LiDARセンサーにより 生成された環境の メッシュと自動的に 相互作用します キャラクターコントローラーの 作成は簡単です キャラクターの形に マッチするカプセルを 定義するだけでそれが 可能になります 作成時にカプセルの 高さとその半径を 指定します コントローラーがエンティティに 割り当てられた後 フレームごとにmove(to:)関数を 呼び出します 障害物を通過することなく キャラクターを― 目的の場所に 移動させます 一方障害物を無視したい 場合は テレポート関数を 使用できます アマンダにマイクを 戻します RealityKitのリリースに 追加されたいくつかの― 楽しい機能について 紹介してもらいましょう ありがとうオリヴィエ ディスクからロードする ことなくその場で リソースの作成に利用 できる2つの新しい― APIについてご紹介 したいと思います
SceneUnderstandingから 人の顔のメッシュを 取得する方法について 次にオーディオを生成する 方法について説明します 手続き的に生成された アートに無限の― 可能性が広がります まずフェイスメッシュ デモAppにある紫と オレンジのタコの見た目に とても刺激を受けたので 自分のの顔に フェイスメッシュ機能を使用 して手描きしてみました SceneUnderstandingは 顔を表すエンティティを提供し これらのエンティティには モデルコンポーネントがあり 顔のエンティティメッシュ上の マテリアルのプロパティを 換えることができる ことを意味します ライブドローイングにより その場でフェイスメッシュに― 適用するテクスチャを生成 するのは楽しかったです コードを見てみましょう SceneUnderstanding Componentに SceneUnderstaningSystem により設定される entityTypeという列挙型があり 2つの値のいずれかを受けます faceは現実世界の人の顔の表現 meshChunkは再構築された ワールドメッシュの別の部分を 意味します タイプが不明の場合 nilのときもあります EntityQueryを再度見ます SceneUnderstanding Componentを持つ― エンティティをクエリして 顔を見つけるための― entityTypeを チェックします 次にそれらのエンティティから ModelComponentを取得し お好きな操作を 実行できます フェイスペインティングの サンプルでは PencilKitを使用して キャンバスに描画できる ようにしてあり CGImageからリソースを作成 してfaceEntityにラップします このフェイスペイントの 外観を作成できるように PhysicalBasedMaterialを 使用して 可能な限りリアリスティックに 見えるよう 処理するために プロパティを設定します キラキラペイント効果を 作成するために 物理ベースのレンダラに それを伝えるノーマルマップの テクスチャを使用し マテリアルの表面上で 光をどのように反射させるか なども設定します 次に鉛筆画したものを マテリアルに追加し エンティティに設定します これが新しく生成された リソースを操作する 方法の1つです 現在生成できるもう1つの タイプのリソースは AudioBufferResourceです AVAudioBufferは好きな ように操作できます マイク入力で録音し 自身で手続き的に生成したり AVSpeechSynthesizerを 使用することもできます 次にAVAudioBufferで AudioBufferResourceを作成し Appでサウンドを 再生します
AVSpeechSynthesizerに AVSpeechUtteranceを 書き込むことによりテキストを 音声に変換する方法は コールバックで AVAudioBufferを受けとり ここでAudioBufferResourceを作成し そのinputModeを 3Dポジショナル オーディオの .spatialに設定して 使用します その他の使用可能なinputModeは nonSpatialとambientです そしてそのオーディオを再生する ようエンティティに指示 オーディオバッファを 駆使して 魚がブクブク言いながら 話しているようにしたり 他の方法で声が 出ているようにしたり 好きなように 処理できます
これが今年のRealityKitの 新機能の 概要です シーンの外観と動作を より細かく制御できるように することに重点を 置いて改善しました ECSを修正して カスタムシステムを提供 Appの動作をより柔軟に 構成できるようにしました マテリアルとアニメーション APIにも 多くの機能を追加しました エンティティが現実の環境と 相互作用するために キャラクターコントローラーが 導入されました 最後にリソースを その場で生成する方法も 紹介しました しかしRealityKit 2の 新機能はこの リストに収まるものでは ありません 今週後半の2回目の RealityKitセッションでは 新しいレンダリング機能に ついて詳しく解説し 水中デモで使われているものについて ご確認いただけます ジオメトリモディファイアを 使用して 海藻をアニメートします タコをサーフェスシェーダーを 使用して遷移させ カラフルに動作させます ブルーデプスフォグエフェクトと 水のコースティクスは ポストプロセッシングにより 作成します またリソース生成を テーマに 動的メッシュの 使用方法についても確認できます 復習をご希望の場合は 2019年の“RealityKitで Appを構築する”をどうぞ ご清聴ありがとう ございました これらのAPIを使用して 創造性を駆使してください [アップビートな音楽]
-
-
7:10 - FlockingSystem
class FlockingSystem: RealityKit.System { required init(scene: RealityKit.Scene) { } static var dependencies: [SystemDependency] { [.before(MotionSystem.self)] } private static let query = EntityQuery(where: .has(FlockingComponent.self) && .has(MotionComponent.self) && .has(SettingsComponent.self))
-
8:34 - FlockingSystem.update
func update(context: SceneUpdateContext) { context.scene.performQuery(Self.query).forEach { entity in guard var motion: MotionComponent = entity.components[MotionComponent.self] else { continue } // ... Using a Boids simulation, add forces to the MotionComponent motion.forces.append(/* separation, cohesion, alignment forces */) entity.components[MotionComponent.self] = motion } }
-
11:58 - Store Subscription While Entity Active
arView.scene.subscribe(to: CollisionEvents.Began.self, on: fish) { [weak self] event in // ... handle collisions with this particular fish }.storeWhileEntityActive(fish)
-
12:36 - SwiftUI + RealityKit Settings Instance
class Settings: ObservableObject { @Published var separationWeight: Float = 1.6 // ... } struct ContentView : View { @StateObject var settings = Settings() var body: some View { ZStack { ARViewContainer(settings: settings) MovementSettingsView() .environmentObject(settings) } } } struct SettingsComponent: RealityKit.Component { var settings: Settings } class UnderwaterView: ARView { let settings: Settings private func addEntity(_ entity: Entity) { entity.components[SettingsComponent.self] = SettingsComponent(settings: self.settings) } }
-
21:26 - FaceMesh
static let sceneUnderstandingQuery = EntityQuery(where: .has(SceneUnderstandingComponent.self) && .has(ModelComponent.self)) func findFaceEntity(scene: RealityKit.Scene) -> HasModel? { let faceEntity = scene.performQuery(sceneUnderstandingQuery).first { $0.components[SceneUnderstandingComponent.self]?.entityType == .face } return faceEntity as? HasModel }
-
22:03 - FaceMesh - Painting material
func updateFaceEntityTextureUsing(cgImage: CGImage) { guard let faceEntity = self.faceEntity else { return } guard let faceTexture = try? TextureResource.generate(from: cgImage, options: .init(semantic: .color)) else { return } var faceMaterial = PhysicallyBasedMaterial() faceMaterial.roughness = 0.1 faceMaterial.metallic = 1.0 faceMaterial.blending = .transparent(opacity: .init(scale: 1.0)) let sparklyNormalMap = try! TextureResource.load(named: "sparkly") faceMaterial.normal.texture = PhysicallyBasedMaterial.Texture.init(sparklyNormalMap) faceMaterial.baseColor.texture = PhysicallyBasedMaterial.Texture.init(faceTexture) faceEntity.model!.materials = [faceMaterial] }
-
23:09 - AudioBufferResource
let synthesizer = AVSpeechSynthesizer() func speakText(_ text: String, forEntity entity: Entity) { let utterance = AVSpeechUtterance(string: text) utterance.voice = AVSpeechSynthesisVoice(language: "en-IE") synthesizer.write(utterance) { audioBuffer in guard let audioResource = try? AudioBufferResource(buffer: audioBuffer, inputMode: .spatial, shouldLoop: true) else { return } entity.playAudio(audioResource) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。