無重力空間で宇宙船を引き揚げるファーストパーソンゲーム『Hardspace: Shipbreaker』はPlaystation 5、Xbox Series S/X、PC向けに2022年にリリースされました。仕事、借金、そして資本主義に対する皮肉のゲームです。没入型シミュレーションゲームとして、プレイヤーは未来の巨大企業で働く日々の苦労を味わえます。地球の軌道に乗り、誤操作で爆発するかもしれない燃料システムや原子炉をよけながら、大小さまざまな宇宙船のリサイクル業務を遂行します。ハードで危険なお仕事です。同時になぜか妙に楽しい作業です。
『Hardspace: Shipbreaker』 引き揚げベイからの眺め
2017年に私がこのプロジェクトにかかわりはじめた頃、ゲームはいまよりもかなり小さくシンプルなものでしたが、次の主要な柱はすでに確立されていました:
- シミュレーションが第一。未来の架空の仕事のシミュレーションを提供し、プレイヤーを宇宙船の引き揚げ業者の気分にさせます。
- 物理的なゲームプレイ。遊び心のあるやり取りや試行錯誤が続く、充足感のある物理的シミュレーションを中心としたゲームプレイを生み出します。
- 切断のゲームプレイ。複雑で満足感のあるカット機能を取り入れ、ゲームプレイに奥行きを出します。カッティングツールで物を細かく切り刻むことができます。
- 労働者の雰囲気。労働者ならではの美しさを重視します。工具は中古であり、故障することもあります。昔のようにうまく機能しません。すべてが使い古され、工業的です。
これら4本の柱をゲームオーディオに全面的に反映させ、できるだけ意義のあるかたちで浸透させたいと私もチームメンバーたちも考えました。ゲームのシミュレーション内容、物理、芸術性にオーディオを深く結びつけ、音がプレイヤーの行動と連動し、環境と調和しながら変化する必要がありました。ゲームの中核にシミュレーションがあり、シミュレーションの原理にオーディオも従わせたいと考えました。例えばプレイヤーが宇宙の真空空間にいる時、空気を伝わる音は聞こえないはずです。空気伝播音が聞こえるのは、空気圧の保たれた宇宙船内にいる時だけです。
ところがシミュレーションに従うと、すぐに次のような面白い疑問がわいてきました:
- ほとんどの時間を宇宙の真空空間で過ごす中、すばらしいサウンド体験をどのようにつくり出するのか。
- エリアごとに空気圧の有無をプレイヤーが変えることができる中、その変化を単に「音が大きい」から「音がない」状態に切り替えて表現する以外に方法はあるのか。
- オブジェクトがいつでも細々に切り刻まれてしまう可能性がある中、物理的な反応音をどう達成するのか。
- どうすればくたびれて、いまにも壊れそうな道具を、使って面白く満足感のあるものにできるのか。
議論とプロトタイプづくりを重ねた結果、これらの課題を乗り越えるために役立ち、ゲームの心臓部であるシミュレーションを支え、きめ細やかでユニークな気分を盛り上げてくれる複数のシステムを開発しました。そのうちのいくつかを選び、それらを実現させるためにWwiseの機能をどのように活用したのかを紹介します。
おっと...。
Resynthesisシステム
まずはじめにResynthesis(再合成)システムを説明します。プレイヤーが火花の散る電線や燃え盛る燃料パイプに近づき過ぎると決してよいことはないため、周囲の危険の音が聞こえるようにしたいと考えました。ところがすぐに前述の1番目の課題に突きあたり、真空の中の音をシミュレーションした時に危険を示す音をどう届けるべきか、私たちは悩みました。なにしろ音は届きません。
私たちはゲームデザイナーたちと協力し、Resynthesizerというゲーム内テクノロジーをつくりました。これはプレイヤーの引き揚げ作業用の宇宙服に装着する、架空の装備品です。これが周囲の危険を察知し、当該危険のシミュレーション音を再生します。この技術はアップグレードオプションとしてゲーム内通貨で購入することができます。さらにアップグレードすることもでき、プレイヤーがアップグレードするたびにResynthesizerの対象範囲は広がります。
Resynthesizerの実装
Resynthesizerの有効範囲のアップグレードは、WwiseのハザードエフェクトアクターミキサーでWwise Gain Effectsを有効にしたり、バイパスしたりすることにより実現しました。Distance(距離)に紐づいているRTPCを使い、プレイヤーが一定の距離以上にいる時は、Wwise Gain Effectを利用してサウンドを遮断します。こうしてハザード音に標準減衰を設定し、加圧空間では通常通り減衰させ、加圧されていない空間ではプレイヤーのResynthesizerのいまの範囲内だけで聞こえるようにしました。
ハザードアクターミキサーにWwise Gain Effectを設定
Effectをバイパスしたり有効化したりするためにStateを使いましたが、このStateの切り替えはWwiseのミュージックシステムの型破りな使い方で行いました。WwiseのミュージックシステムでStateやSwitchなどの変数をクエリし、その変数値に応じてEventをトリガーすることができます。つまり、あるState Groupの値をクエリしてクエリ結果を使い、別のState Group値を設定することができます。
プレイヤーのResynthesizerの現在のアップグレードレベルは、StateではなくRTPCとしてWwiseに送られていました。このRTPCはグローバル設定であり、ハザードオブジェクトはローカルRTPC値を使用するため、グローバルRTPCでWwiseGain Effectを直接有効にすることはできませんでした。グローバルRTPC値を受け取り、すべてのハザードオブジェクトから参照できるState値へ変換し、これを使いWwise Gain Effectをバイパスまたは有効化させる必要があります。ありがたいことに、Wwiseのミュージックシステムがまさにこれを可能にしてくれます。
最初にグローバルRTPCからターゲット値を選択する以下のSwitchを作成しました:
次にミュージックシステムに1秒ごとにループするコンテナを置き、Upgrade_Resynthesizer Switchの値をクエリしました。このSwitch値に基づいて無音のMusic SegmentのMusic Event Cueを使い、Stateの値を設定しました。
さらにこのステートを用いて、ハザードサウンドのWwiseGain Effectを有効化やバイパスを行いました。
それなりに難解な実装であり、必ずしも推奨しません。ただしすぐにでも実装したい機能があり、どうしてもプログラマーに頼ることができない場合など、どこまでWwiseのシステムだけで可能性を広げることができるのかを考える上では興味深い例だと思います。
プレイヤーが見るアップグレードツリーのResynthesis画面
Resynthesisの音
Resynthesis機能をつくる上で私たちが次に直面した課題は、美しくてもブルーカラーの雰囲気に反しない音を出すことでした。真空状態の中でも危険の音を聞こえるようにしたい一方、Resynthesizerで出される音が完璧すぎても、空気伝達した音と似すぎていてもいけません。すでに私たちは架空の道具であるResynthesizerをゲームの装備として受け入れており、プレイヤーの装備は古くていまにも壊れそうな状態であるという状況も決めてありました。Resynthesizerに信憑性を持たせるためには、グリッチ音、ビーッ音、音の途切れなどが伴う必要がありました。
これを達成するために最初はGlitchmachines社のFractureというプラグインを中心としたオフライン処理に目を向けました。このプラグインをPlogog社のBiduleの中でホストし、擬似乱数のサンプル&ホールド値を生成してFractureに渡し、パラメータを部分的に修正しました。
この流れでハザード音をオフラインで処理し、グリッチ音の混ざった結果に満足しました。
ところがうまくいくのは1つの音が再生される時だけでした。2つ以上の音が同時に再生された場合、それぞれの音のサンプル&ホールドのステップのタイミングがずれてしまい、結果的に大変なことになりました。これは音源が増えるほど悪化しました。オフラインのプロセスが効率的でないことは明らかでした。この種のDSPを音に適用するのであれば、すべてのResynthesizer音を一緒にしたサブミックスに対し、リアルタイムエフェクトを適応する必要がありました。
そこで私たちはWwiseカスタムプラグインの作成をPure Dataを使って試み、PdパッチをHeavyコンパイラでプラグインに変換しました。Fractureで構成したプロセスに類似したものをPdで作成し、サンプルデグレーダ、ステップフランジャー、マイクロスタッター、ビープジェネレーターから成るマルチエフェクトを作りました。
Resynthesizer Pdパッチのトップレベル
必要なパラメータを"@hv_param "ノードとして公開し、Wwiseでパラメータを設定できるようにしました。これらのパラメータにRTPCを割り当て、プレイヤーがゲーム中にResynthesizerをアップグレードした時に、グリッチやデグレードのエフェクトが徐々に軽度になるように設定しました。
次に処理をサブパッチ単位で行い、各チャンネルで同一の処理としました。上図でadcノードとdacノードの間にあるのがサブパッチです。
チャンネル処理サブパッチ
Heavy経由で変換した後、できあがったプラグインをすべてのResynthesizer音を受領するミキサーバスでインスタンス化することにより、すべてのサンプル&ホールドのステップが同期しました。Resynthesizerサブミックス全体を1つのDSPで実行するため、CPUの使用は最小限です。まさに私たちが求めていたものです。
こちらはWwiseでプラグインを実行し、火柱ループを処理する様子です:
こちらはゲーム内のエフェクトです:
Heavyな気づき
ここでHeavyで生成したプラグインを扱う際に気づいたことを、いくつかあげたいと思います。
第1にHeavyライブラリについてですが、既製品のままで対応できるのは最大2チャンネルです。私たちはパンニング後にエフェクトをサラウンドバスでインスタンス化するため、6チャンネルに対応できるようコンパイラを修正しました。hvccのc2wwwise.pyファイルで行い、変更は簡単でした。ところが6チャンネル以上の出力デバイスを使用した時にクラッシュに繋がるような重大な安定性の問題があり、これを回避するためにWwiseのChannel Configurationドロップダウンにおいて、エフェクトをホストするバスを5.1に制限する必要があることが分かりました。
第2にプラグインDLLについてですが、オーサリングツールとゲームのPC版用は簡単に生成できましたが、PlayStationやXbox用のプラグイン生成は簡単ではありませんでした。幸いにもBlackbirdのコーダーたちが協力してくれ、ターゲットをVisual Studioで構成してコンソールプラットフォームに必要なファイルを生成してもらうことができました。コードのサポートが得られない場合、コンソールでエフェクトを使えるようにするためにはかなりの作業が発生することを覚悟してください。
Touch Transfer音
次は私たちがTouch Transferと呼ぶシステムです。プレイヤーが音源に触れているとその音が聞こえてくるシステムで、空気圧のない環境においても聞こえます。『Shipbreaker』の開発をはじめる数年前に公開された映画『ゼロ・グラビティ』にインスピレーションを受けたエフェクトで、映画の影響が大きかったと言えます。
Touch Transferシステムはゲーム開発中によくある「こんなことができればいいな…」という会話から生まれたものです。空気圧のある宇宙船内では2Dのアンビエントオーディオや3Dの環境エミッターが豊かなサウンドスケープを演出してくれる一方、その場所の空気圧を抜いた途端に音が抑えられてしまいます。この演出自体はよかったのですが「これらの音が音を発している物体に触れた時に、宇宙服越しに聞こえたらかっこいいのに」と思わずにはいられませんでした。
この想いを以下3つの相互に補完し合うデザイン目標として集約しました:
1. 船内の各種物体が単なるポリゴンの空箱ではなく、内部に制御システムや機械部品が詰まった、振動する本物の物体のように感じてもらうことで、没入感を高めること。。
2. プレイヤーに船内を歩き回って手で触ってもらい、触覚的な刺激のある体験をしてもらうこと。
3. 環境内に潜む危険を伝えること。例えばガタガタうるさい燃料パイプや、低い振動音のする電気的な危険物など。
Touch Transfer処理を施す対象として、私たちは以下の音を候補にあげました:
- 2Dアンビエンス。宇宙船の構造そのものに触れた場合、手を通して2Dアンビエンスが聞こえるようにします。
- 3D環境エミッター。コンピュータや燃料パイプといった物体に触れた時、触っている間はそこから発信されるオーディオが聞こえるようにします。
- 物理的な衝突。衝突する物体や衝突面に触れた時、物理的な衝突の音が触覚を通して聞こえます。
Touch Transferを念頭に設定した特殊なケースもいくつかありますが、大半は上記カテゴリーのいずれかに属します。
手を通して聞こえるゲーム内の音
Touch Transfer音の設定
Touch Transfer音をプレイヤーが生成できる方法は3通りあり、それぞれのRTPCをWwiseで3種類設定しました。何かに手で触れた時のRTPCが1つ、体の一部が触れた時(つまり物体に寄りかかった時)のRTPCが1つ、そして物理的な衝突に関与する物体に触れた時のRTPCが1つです。Wwiseでそれぞれの名前を「Grab」「Prox」「Physics」としました。
Touch TransferのRTPC3種
2Dアンビエンスに関しては、すべての2Dアンビエンスの入った親アクターミキサーノードに対してこれらのRTPCを実装しました。プレイヤーが宇宙船内の壁や船体の一部に触れた時、該当するRTPCを100に上げ、その音をTouch Transferバスに送ります。
2Dアンビエンスノードに設定したTouch Transfer RTPC
3Dエミッターの場合は少し複雑で、空気を経由する時は減衰付きの3Dサウンドとし、同じ音をTouch Transferバスに送った時は減衰なしの2Dサウンドとしたかったのです。そこで空気圧エリア用の3D減衰バージョンと、Touch Transfer RTPCが上がった時にはじめて聞こえる2Dバージョンという2つのインスタンスを再生させました。
Touch Transfer用に設定した3Dエミッター
物理サウンドは2Dアンビエンスと同じように、すべての物理サウンドが入っている親ノードにRTPCを設定しました。
Touch Transferサウンドを「Grab」「Prox」「Physics」にカテゴリ分けしたことにより、音に優先順位をつけるミキサー構造をつくることができました。物体に寄り掛かった時に伝わってくる音は、プレイヤーが手を触れた時に伝わってくる音にダッキングされます。これは手の動きが能動的で意図的な行為であるのに対し、物体に寄り掛かるという行為が受動的であるからです。手に触れたことにより伝わるワンショットのTouch Transferサウンドが、ほかの種類のTouch Transferサウンドをダッキングする設定です。Touch Transferサウンドがどのように変化した場合においても、プレイヤーが必ず感じ取ることができる構成となります。
私たちはシステムの成果に満足しており、特にゲームプレイのフィードバック手段として優れていると思っております。ゲーム内はプレイヤーが誤って切り込んでしまうような危険な物体が多数あり、致命的な火の玉や放電を引き起こす恐れがあります。これらの物体の燃料や電気供給を停止することで危険を回避できるため、そのオーディオをシステムに紐づけ、対象のエネルギー源が切断された時にエミッターのオーディオが停止されるようにしました。プレイヤーは物体に触れるだけで、それが安全かどうかを確認できます。
以下の動画ではゲームプレイのフィードバックとしてTouch Transferサウンドを使用しています:
- 宇宙船の両側に2本の燃料管が通っています。
- 燃料管を燃料が勢いよく流れ、切断すると危険です。燃料の奔流が聞こえます。
- 宇宙船内の燃料フラッシュバルブは、切断しようとしている管の反対側にあります。
- 船内の減圧後、空気がないため燃料管を流れる燃料の音が聞こえなくなります。
- ただしプレイヤーは燃料管に触れ、どちらに燃料が流れていて、どちらが止まっているのかを耳で判断することができます。
- 両方の燃料管を空にしたプレイヤーは、2本の燃料管に手を触れて切断しても安全であることを確認します。
Touch Transferサウンドを利用し、燃料システムの安全性を確認
Touch Transferのしくみ
どの物体に触れているのかを追跡するシステムは、かなり入り組んでいます。テクニカルディレクターのRichard Harrisonは、物体の追跡システムのコードについて次のように説明します:
宇宙船内の物体は3段階に分かれています。はじめに物体の部品の「Parts」があります。次に「Parts」の小さな集合体が入った「Group」があり、1つの物体を表します。(例えば取り付けプレートとアンテナは「Parts」であり、これらは衛星アンテナを構成する「Group」に入ります。)「Parts」も「Group」も3D SFXエミッターとすることができます。物理的に繋がっている「Parts」や「Group」は最後に1つの「Hierarchy」の中に入ります。宇宙船の一部を切り取った場合、元が断たれても互いにまだ接続している「Parts」や「Group」は、新しい「Hierarchy」が割り当てられます。
タッチ行為は左手または右手の「Grabs」や「Proximity」で行い、関係するRTPC値が異なるため、別々に追跡します。「Grabs」は文字通りつかむ行為であり、近接していることを意味する「Proximity」は、物体がプレイヤーを中心とした球体に重なり一定の距離に近づいたことを検知します。球体内にある物体は、近接によりプレイヤーが「触れている」とみなされます。
Touch Transferシステムは根本的に大げさな参照カウント機能であり、「Part」「Group」「Hierarchy」をタッチするたびにアイテムのカウントに1を足し、タッチをやめるたびに1を引きます。「Part」「Group」または「Hierarchy」のタッチ数がゼロより大きい場合、それは確実にタッチされていることを意味します。「Part」や「Group」のカウントがゼロを超えた時、そのアイテムに対応する「Grab」または「Proximity」のRTPCが増加します。「Part」や「Group」が3D SFXを発している場合はRTPCの設定によりに、何らかのかたちでそれが聞こえるようになります。タッチ数がゼロに戻された時、該当するRTPCもゼロにリセットされます。
「Part」や「Group」と同じように「Hierarchy」も追跡されますが、目的が異なります。Hierarchy RTPCが設定されるのは「Hierarchy」に宇宙船の「Reactor」(原子炉)が含まれている場合のみで、「Reactor」は宇宙船の2Dアンビエンスの音源となります。それ以外にも「Hierarchy」がTransferされた物理SFXの影響を受けているかどうかを、コリジョンシステムに伝えるためにも、この追跡システムを利用します。
最後の難関が物体の分裂と破壊です。参照カウンタは「Part」「Group」または「Hierarchy」が分割されたことを把握する必要があり、タッチを元のアイテムから取り除き、代わりにまだ触り続けている、新しくできたアイテムに移す必要があります。このしくみの詳細は『Shipbreaker』の切断ロジックに大きく依存していますが、今回の記事では省略します。
物理的な難題
最後に『Shipbreaker』の物理オーディオシステムを紹介します。
これまでのプロジェクトでは、物体に何らかの物理オーディオタグを添付するという物理システムが、この物体がサーフェスに衝突した時にどの音を再生するのかを決めていました。タグ付けされた2つのオブジェクトが互いに衝突した時に再生する音のルックアップテーブルや、元の物体が壊れて崩壊した時に発生する破片の子オブジェクトの物理タグなどがありました。衝突した時に衝突の強さがRTPCとして送信され、物理タグと衝突力の値を組み合わせてサウンドが決まりました。ところが『Shipbreaker』では従来のアプローチでは解決できない特殊な問題がありました。
『Shipbreaker』ではコンピュータ端末のような大きな物体を持ち上げ、標準的な剛体で物理的なオブジェクトの要領で投げることができます。それと同時にプレイヤーは切断ツールで、コンピュータを半分に分割することもできます。コンピュータ端末を半分に切り、その断片を投げることができるのです。ここまでは対応不可能ではありません。オブジェクトの質量の変化をRTPCで表し、それと連動させてコンピュータ端末の音の大きさをWwiseで変更することができます。ただしプレイヤーは切断ツールでコンピュータ端末から「薄片」をそぎ落とし、一片のシートメタルをつくり出すかもしれません。放り投げた時はすでにコンピュータ端末ではなく、一片のシートメタルの音を出し、あちこちにあたるはずです。言い換えるとコンピュータ端末のために再生される音が、ある物理音からまったく別の物理音に切り替わる必要があります。
宇宙船内にあるコンピュータ端末
これには私たちもしばらく悩みました。コンピュータ端末が薄くスライスされてシートメタルとなってしまったことを、コンピュータ端末自体にどう伝えるのか?結局のところ極めてシンプルな解決策にたどり着きました。オブジェクトに2つの物理タグを設定し、一定の質量値以下となった時に1つ目のタグから2つ目のタグに切り替えるようにしました。
ここからは物理オーディオの設計仕様書からの引用です:
コンピュータ端末などのオブジェクトには、AudioMaterialDataコンポーネントがあります。このコンポーネントには以下3つのフィールドがあります:
- Tag:オブジェクトの物理的オーディオ識別子です。例えばコンピュータ端末の値として「object_large_metal_electronic_heavy」などを設定します。
- Family:オブジェクトが所属する汎用マテリアルグループです。例えばコンピュータ端末の値として「metal_light」などを設定します。
- Threshold:オブジェクトの本来の質量に対するパーセンテージ値であり、これ以下になるとオブジェクトはTag値ではなくFamily値を使用します。
TagはFamilyのオーバーライドとみなすことができ、オブジェクトがThresholdで指定する質量率を超えている間はTagがFamilyをオーバーライドします。
コリジョン(衝突)が発生するとオブジェクトの質量の絶対値がRTPCとしてWwiseに送信され、サウンドデザイナーはこれに基づき、音で感じる大きさを変化させます。オブジェクトがThreshold値より大きいか小さいかにかかわらず、RTPCは更新され送信されます。
FamilyとTagフィールドは以下の通り任意です:
- オブジェクトが衝突した時にTagがない場合、システムはThreshold値に関係なくFamily値を使用します。
- Family値がない場合、オブジェクトはThreshold値を下回るまでTag値を使用し、下回った後は衝突したとしても音を発しません。(場合により、これが望まれます。)
TagとFamilyの値にはそれぞれ異なるWwiseEventが割り当てられます。物理オーディオシステムはTagとFamilyのどちらが使われているのかに基づき、適切なイベントを起動します。
Unityの物理オーディオ設定画面
ここまできてしまえば、あとは順調でした。Wwiseでいつも通りに物理音を設定し、Unity側ではオブジェクトのTag値またはFamily値とThreshold値に対する質量に基づき、どちらの音を再生すべきかを判断します。
こちらがゲームで稼働するシステムです:
私たちのシステムについて興味を持っていただけたでしょうか。『Shipbreaker』は多くのシステムが集約されたゲームです。このブログ記事に書ききれないほど、ほかにも多くのシステムがあります。また後日、別の記事で紹介できるかもしれません。お読みいただきありがとうございます。
コメント