I. はじめに
GameObjectはWwiseでオーディオデザインを行う際の基礎となる概念です。Wwiseの基本機能(特にProfiling機能)からWwise SDKを用いた開発に至るまで、GameObjectはパイプライン全体に深くかかわっています。私は日々の仕事で、オーディオGameObjectの不適切な管理が原因の問題にたびたび遭遇してきました。そこでオーディオGameObjectの登録や管理方法を整理したいと思いました。
まずはじめに、関連する基本的な概念を見てゆきます。
1. GameObjectsとは?
最初にこの概念を明確化します。
ゲームエンジンにおけるGameObjectや、Object-Based Mixingの概念との混同を避けるため、ここでは以下WwiseドキュメントのGameObjectの定義を使用します。
WwiseのUIおよびドキュメントと統一するために、この記事ではWwiseの用語GameObjectを使用します。
2. オーディオオブジェクトを管理する必要性とは?
上記の定義から分かる通り、GameObjectという考え方がWwiseのすべてのオーディオマネジメントの基盤にあります。
Wwiseを使用している場合、GameObjectなくしてゲームの音を再生することはできません(音の2D、3Dにかかわらず)。
オーディオGameObjectを適切に管理できない場合、Wwiseの例外や障害からゲームエンジン開発中の潜在的な問題に至るまで、さまざまな不具合が発生する可能性があります。
3. GameObject単位でオーディオを管理することはWwise独自のものですか?特別な点は何ですか?
実はGameObject単位(オブジェクトベース)のオーディオマネジメントはWwise特有のものではありません。このアプローチを採用しているオーディオエンジン(またはオーディオマネージャ実装)がほかにもあります。
- 完全にイベントだけをベースとするオーディオマネージャ実装があります。この場合オーディオデザイナーはイベントだけでオーディオを制御します。
- Wwiseではデザイナーがオーディオ制御のためにGameObjectとイベントの両方を使用することができます。(ただしイベントがGameObjectから完全に独立しているわけではありません。)これによりサウンドデザインの柔軟性や表現力が向上します。
Wwiseのイベント、GameObject、UEコンポーネント
GameObjectの重要性や動作のしくみを詳しく知るために、ブループリントの単純なインターフェイス実装を例にGameObjectの作成とリファレンスをコードレベルで確認してゆきます。
AudiokineticがUnrealエンジン用に機能豊富なインテグレーションセットを提供しており、UnrealエンジンインテグレーションにおけるWwise SDKの使用方法がかなり典型的であるため、ここではPostEventブループリントノードを例に紹介します。
次のスクリーンショットはUnrealのPostEventブループリントノードのC++コードです:
UnrealのPostEventブループリントノードの定義:
このコードは以下のブループリントインターフェースに該当します:
このブループリントノードのAkEventとActor入力ピンを変数または特定の値に接続する必要があり、接続されていないとコンパイル時にエラーが報告されます。
コードロジックを簡単に説明すると次の通りです:PostEventノードを実行した時にAkComponentがActorオブジェクトを指定したRootComponentにアタッチされます。
AkComponentをアタッチするプロセスとして、はじめに現在のActorの子に対して順にイテレーションを行います。
AkComponent がある場合は、アタッチ処理をすぐに開始します。
ない場合は以下のように新しいAkComponentを作成します:
次にAkComponentがアタッチメント変換ルールに基づいてWwiseでGameObjectにアタッチされます。
つまりActorがWwiseで作成したサウンドを再生するたびに、AkComponentが存在する必要があります。
AkComponentの役割:
AkComponentはUEにあるWwise専用のGameObject容器であり、以下の関数を呼び出すことができます:
プログラミングの知識がない方は、コードを見ただけではよく分からないかもしれません。
違う見方をします。ゲームエンジンは次のようにオーディオミドルウェアを使って音を再生します:
まとめ(第I部)
WwiseのGameObjectのデータソースとなるのがゲームエンジンのAkComponentであり、このソースは必要不可欠です。(このデータに向き、ステアリング、GameSyncなどの情報が含まれます。)
各サウンドの再生が、ターゲットオブジェクトであるAkComponentをポイントします。
ターゲットオブジェクトでそのサウンドを伝達するために、Wwiseは対応するGameObjectをWwiseで登録し、すべての基本プロパティをAkComponentから継承します。
ポストされたイベントでターゲットオブジェクトがAkComponentであるものは、すべてWwiseのそのGameObjectをターゲットとします。
言い換えると次の通りです:
Wwise内のGameObjectはサウンド側でそのAkComponentを担当し、AkComponentをポイントするすべてのイベントを伝達します。
さらにこのAkComponentに基づいてWwise内のGameObjectを登録・登録解除することができます。
これをすべて理解した上で、何ができるでしょうか?
II. オブジェクトベースとイベントベースのオーディオマネジメントにおける衝突の可能性
1. イベントベースのオーディオマネジメントとは?
イベントをベースとしたオーディオマネジメントは初期のゲームオーディオミドルウェアや小規模ゲームのオーディオマネージャ実装で広く用いられていました。しかし今日はゲーム中のほぼすべてのサウンドが独立した「オブジェクト」です。
時としてオーディオ開発担当者が以下のような極端に単純化した方法で、サウンドの再生を管理・制御しようとすることがあります:
- サウンド再生のたびに1つのGameObjectを登録し、再生が完了した時にこれを破棄。
- サウンド再生用のゲームオブジェクトを複数作成し、再生するすべてのサウンドをこれらオブジェクトにアサイン。
私はこの記事で、この方法を「イベントベースオーディオマネジメント」と呼びたいと思います。
サウンドをトリガーした後にそのサウンドを制御しつづける必要性や複雑性に考慮しないまま、サウンドがトリガーされてしまいます。
確かに上記のオブジェクト作成・管理のアプローチには一定の有効性がありますが、私たちのすべての開発要件を満たさないことは明らかです。
では最初のアプローチ(1つのイベントに対し、1つのオブジェクトをランダムに登録する方法)を考えます:
プログラミングができる人であればゲーム中のオブジェクトの作成・破棄が、システムリソースを消耗することを知っています。頻度の低いイベントであれば問題はないかもしれません。ところが頻発するイベントの場合、短期間に多数のオブジェクトが作成され破棄されることとになります。その結果システムに不必要な負荷がかかる可能性があります。パフォーマンス上のコストの観点から、これがイベントベースのアプローチの落とし穴となる可能性もあります。
当然オブジェクトプールの作成によりこれを最適化できることもありますが、ランダムなオブジェクトをアサインすることがすべてのサウンド再生に適しているわけではありません。
同じサウンドを同時にトリガーする複数のオブジェクトがゲームにある場合、これらのサウンドをイベントベース方式で正確に管理することは困難です。各イベントが互いに独立しており、どのイベントがどのゲーム内オブジェクトに対応するのかをオーディオエンジンが区別できないためです。その結果、同時に再生されるボイス数を制限するオーディオの管理は、グローバル(ゲーム全体を対象)で行うしかありません。つまりWwiseのGlobal モードを使用して管理するしかありません。
次に2番目のアプローチ(1つのオブジェクトに複数のイベントをアサインする方法)を考えます:
通常はゲームで表現豊かなサウンドが求められている場合、デザイナー自身がパフォーマンスの制御を考慮する必要があります。
すべてのサウンドを同じゲーム オブジェクトにアサインした場合、結果的にゲーム内の実際のオブジェクト経由で複数のサウンドを同時に細かく制御することができなくなります(1番目のアプローチにおいても同じ問題が発生します)。
同時ボイス数の観点で同じオーディオオブジェクトに最大数を設定(Global設定)した場合、一部のボイスがランダムかつ誤って消される可能性があり、無理のあるリスニング体験となります。
ボイスが誤ってキルされるという問題を解決するために、同時ボイス数の制限を解除する必要があります。しかしその結果、パフォーマンスに多大な悪影響が出てしまいます。
まとめ(第II部)
イベントベースのオーディオマネジメント方式ではGameObjectをイベントのアタッチメントとして扱い、各サウンドイベントに1つのGameObjectをアサインしています。
一方、オブジェクトベースのオーディオマネジメント方式では、イベントをGameobjectのアタッチメントとして扱います。ゲーム内のオブジェクトに対応するGameObjectをWwiseでアサインし、イベントはGameObjectの情報を継承する子となります。
III. GameObjectの不適切な管理時に予測される諸問題
読者が作業中に遭遇する可能性のあるいくつかのケースを、分析用に取り上げて説明したいと思います。
開発中のゲームでサウンドのトリガーや管理をオブジェクトベース方式ではなく、イベントベース方式で行った場合、Wwiseはゲーム内オブジェクトの該当GameObject情報を適切に受信することができず、オーディオイベントごとに1つのGameObjectをその都度ランダムに生成します。
その時はWwiseの使用中に以下のような問題が発生することがあります:
1. WwiseのGameObject関連の機能がすべて失敗または誤作動
- ランダムコンテナやシーケンスコンテナのScopeを"Game object"に設定した場合、再生がトリガーされた時にリストが更新されません。再生されるたびに新しいGameObjectが登録されるシーケンスコンテナでは、常に最初の子アイテムのみが再生されることに特にご注意ください。
このような状況ではデザイナーが各種サウンドのターゲットオブジェクトの割り当てをProfiler機能を通して入念に確認しない限り、デバッグに多くの時間を費やすことになります。
- Playback Limitを"Per game object"に設定すると失敗し、ゲーム中の実際のボイス数を制限することができません。同時制御を行うためのボイスマネジメント機能を、ゲームエンジンレベルで再実装する必要があります。ただしこれはプログラマー頼みとなります。喜ばれないかもしれません。
第II部で述べた通り、ゲームオブジェクト単位のサウンド同時制御が不可能になります。イベントごとに1つのランダムなオブジェクトを登録するにせよ、同一オブジェクトに複数のイベントをアサインするにせよ、オーサリングツールを使用してデザイナーがパフォーマンスを制御することができません。プログラマーからすでに引き渡された仕事を、彼らに戻すことになります。
- プラグインパラメータのさまざまなオートメーション(LFO)の各種スコープ設定の動作が混乱します。
以下のような混乱した状態となってしまいます:
- 1つのイベントに対し1つのランダムオブジェクトを登録する場合、実際にあるゲームオブジェクトに対してStopやBreakなどのオーディオイベントを利用できず(これらの動作はプログラムで管理する必要があります)、使用できるものは"Stop all"などのグローバル関数のみとなります。
前述のPlayback Limitの問題と同様です。プログラマーたちからすでに引き渡された仕事を、彼らに戻すこととなります。 - Wwiseの古いバージョンではゲームオブジェクトが順に生成され、観察したいサウンドが継続的にループしているサウンドでない限り、これから再生されるサウンドのゲームオブジェクトIDを把握することは難しいため、Game Object Profilerを使用することができません。
このためオーディオデザイナーにとってテストやデバッグ作業がさらに困難になります。
2. 不必要なシステム負荷
- ワンショット音が頻繁に再生される場合は、サウンドオブジェクトの作成と破棄が何度も繰り返されることがあります。前述の通りこの状況では不必要なシステム負荷が発生します。
- 各サウンドがそれぞれ別のGameObjectに登録され、その方向やステアリングが個別に計算され、個別にゲームシンクが読み込まれます。このため不要なシステム負荷がさらに発生します。
プレイヤーの足音を例に見てみましょう:
- 常時プレイヤーに付随するゲームオブジェクトがある場合、Wwiseはプレイヤーの足元の地面素材が変わった時だけSetSwitch APIの呼び出しを受信し、それから正しいオーディオコンテンツを再生します。
- そうでない場合はプレイヤーが1歩先にすすむたびに地面の素材を検知し、ランダムなIDでゲームオブジェクトを登録します。その後にゲームオブジェクトのSetSwitch APIを呼び出し、正しいコンテンツが再生されるようにしてからオーディオコンテンツを再生します。オーディオコンテンツ再生後はゲームオブジェクトの登録が解除されます。この手順を各足音に対して繰り返します。
別の例として車両を考えます:
車両のエンジン音、タイヤ音、風切り音をすべて独立したイベントでトリガーする場合、それぞれが車両スピードを読み込む必要があります。
- この車両に常駐するゲームオブジェクトがあるのであれば、このゲームオブジェクトに対応する3つのイベントのために、車両スピードパラメータを1つのRTPCで制御する(SetRTPCValueを1回呼び出す)ことができます。
- 逆に各イベントに対しランダムなオブジェクトを1つずつ登録するのであれば、ゲームオブジェクトが3つ生成されます。つまり3つのゲームオブジェクトに必要なRTPC値が同一であったとしても、各ゲームオブジェクトに車両スピードRTPCをそれぞれ送信する(SetRTPCValueを3回呼び出す)必要があります。
車両スピードRTPCの計算に必要なメモリ(CPU)負荷は、妥当な負荷より3倍もかかることになります。
これが多大なコストではないとしても、不合理なコストであることは確かです。
3. GameObjectに設定されたプラグインのインスタンス化の繰り返し
どのサウンドも前述の通り独立したオブジェクトであるため、サウンドごとにプラグインが新たにインスタンス化されます。各プラグインにその実行パラメータがあり、1つのGameObjectで複数のプラグインが同じパラメータを有するわけではありません。
4. 仮想インスタンステクノロジーの使用を制限
GameObjectを個別に登録・破棄することはできません。また同じサウンドをつくる複数のエミッターの1つだけをインスタンス化してシステム負荷を削減する方法はありません。このためすべてのエミッターが物理的にインスタンス化されます。
まとめ(第III部)
作業中に上記のような状況に遭遇した場合、ゲーム内のオーディオGameObjectの作成や登録解除のメカニズムが正常であるかどうかを確認してください。問題の原因を特定する手助けとなるかもしれません。
IV. 優れたGameObjectマネジメントの考え方
1. オーディオオブジェクトのプールをゲーム開発の初期段階に作成するべきです。頻繁に繰り返してトリガーされる可能性のあるサウンドイベントは、オブジェクトプール経由で管理するべきです。すべてのオーディオオブジェクトをオブジェクトプール経由で管理した方がよいかもしれません。
2. ゲーム開発の段階においてサウンドをトリガーし停止するメカニズム以外に、そのサウンドがアタッチされるGameObjectの作成や破棄のタイミング(リファレンス、デリファレンスのタイミング)にも注意を払う必要があります。
3. ゲームエンジンのオーディオインターフェースを開発する際、パラメータ設定をできる限りWwiseの公式SDKと統一するように注意します。必要に応じてWwise用に別のオーディオインターフェースを開発します。
4. オーディオミドルウェアを交換またはアップグレードする場合は、新旧のミドルウェアSDK の互換性に注意する必要があります。互換性が低い場合は新しいオーディオインターフェイスを開発し、アップグレード処理中にサウンドのフックアップロジックを変更する必要が出てくるかもしれません。
結論と謝辞
以上が私のオーディオGameObject管理に関する現時点での研究結果です。意図せず省略している部分もあるかもしれませんが、引き続き改善してゆきます。
最後にこの記事を執筆するにあたりご協力いただいた张正昱阳(Zhengyuyang Zhang)氏と温哲奇(Zheqi Wen)氏にお礼を申し上げたいと思います。
また記事の編集過程でAudiokineticグレーターチャイナ担当プロダクトエキスパートChenzhong Hou氏に豊富な情報と貴重なご意見を提供していただき、私も個人的に非常に勉強になり、心から感謝しています。
コメント