ゲーム開発におけるサウンド実装の勘所

一條貴彰氏(以下、一條):本日は「ゲーム開発におけるサウンド実装の勘所」というタイトルで、株式会社ヘッドハイの一條がお話をさせていただきます。どうぞよろしくお願いいたします。

まずはじめに、自己紹介をさせてください。私は株式会社ヘッドハイという小さい会社で、ゲームに関わる事業をしています。ふだんはゲーム開発者をサポートするソフトウェアやサービスの販売をお手伝いしています。

具体的には、Play,Doujin!というレーベルによるゲームの家庭用機パブリッシングのお手伝いをしています。今日ご紹介する「CRI ADX2」というサウンドツールの紹介役や、Unity Japanの連載「Made With Unity」の企画、Googleのインディーゲーム開発者向けのコンテスト「Google Indie Games Festival」の企画などを手伝っています。

こうしたツールサポート事業のだいたい半分ぐらいで、もう半分はほぼ私1人で小さいゲームを作って販売しています。

3年ほど前に『Back in 1995』というタイトルを作り、現在ではNintendo Switch向けのゲームを作っています。あとで少し紹介します。

現在、事業の1つとして、Unityのサウンドに着目した『Unity サウンド実装の極意(仮)』という技術本を書いています。ボーンデジタル社さんから、春に発売されます。いま、一生懸命書いているところです(笑)。

一冊まるまる、オーディオの実装について語られている本はなかなかありません。この本はUnityの基礎機能を使ったもの、今日ご紹介する「ADX2」を使ったもの、VRサウンドの基礎的な部分のご紹介といったところで、Unityでゲームを作ろうを思った場合のサウンドの部分について、なるべく全部をカバーするような内容になっています。

これは先ほど少しお話しした私が開発中のSwitchのゲームです。4人対戦のゲームですから非常にたくさんの音が鳴ります。このゲームでも、今日お話をするADX2を使っています。

また、自社でゲームを作って展示などもしていますということで……台湾ゲームショウに出展してきました。ゲーム開発をやられている方がいたら、ぜひ台湾の熱を感じてみるといいかなと思います。

サウンドの実装は後回しになりがち

自己紹介はそんなところで、まずは本日のイントロダクションですが、みなさんはゲームサウンドの実装はどのようなタイミングでやられていますか?

「サウンドの実装なんて『鳴らして、止める』だけじゃん? 簡単じゃん?」「めんどいから最後に実装してるわ」というのはちょっとよろしくない。

先ほども少し話しましたが、サウンドは後回しになりがちでした。気持ちは非常にわかります。世の中に売ってるUnityの本は、サウンドの実装が4ページなど、非常に少ないんですね。以前に出版した『Unityゲーム プログラミング・バイブル』という本では、僕の担当章はサウンドで20ページぐらい書いています。それでもたぶん一番多いような状況です。

また、講演も少ないです。「Unite 2018」ではオーディオの講演を担当しました。「みなさん、サウンドの部分をもっともっとクオリティアップしましょう」という話をしています。

まず、今日会場にいらしているみなさんの属性をお聞きします。「個人や数人のチームなど小規模でゲームを作るのがメインです」という方は? あんまりいないかな?

(会場挙手)

意外と多い。では「業務でモバイル向けの運営型アプリを作っていて、それに携わっています」という方は?

(会場挙手)

多い……でも、半々ぐらいですかね。

サウンド処理実装で考慮すべきこと

さて、まず前半にお話しする部分は、Unityのサウンド標準機能を使った実装に関する基礎の振り返りになります。もうすでに開発会社さんなどで「実装やってるよ」という方にとっては、少し退屈なお話になるかなと思います。

まずサウンドを鳴らすときに大事な要素は、「CPU負荷」「メモリ使用量」「再生のレイテンシー」です。この3大要素のバランスをとることが大事です。

再生レイテンシーは、プログラムの中で「音を鳴らす」といってから実際に音が鳴るまでのタイムラグのことですね。ゲームを作る場合は、ゲーム機やスマートフォンなど、なにかしらのデバイスの上で動作をさせるわけで、リソースは限られている。ゆえに、サウンドで使える部分はますます限られています。

CPU負荷を抑えると、メモリや再生レイテンシー……例えば「タップしてからすぐ音が鳴る」みたいな部分が犠牲になってしまいます。逆に「じゃあ、それをすぐ鳴らすようにしよう」みたいなことをすると、CPUの負荷が上がったりメモリの使用量が増えてしまったりして、この三要素が三すくみみたいな構造になっているとお考えください。

こういった、リソースが限られている状況においては、ゲームの中で鳴らされる音の特性をよく把握して、その音の特性ごとに鳴らす仕組みの調整を行っていこうという考え方になります。

ゲームにおける6つの音

ゲーム内はたくさん音が鳴るんですけれども、ざっくりと分類ができます。ゲームのジャンルにもよりますが、例えば3Dのアクションゲームで考えてもらうと、ざっくり6種類ぐらいかなと思います。

まずはSE。英語だとSFXと言いますが、メニューの中で鳴るボタンの「ピコーン」といった音。それから、ゲームの中では足音やキャラクターの攻撃音がSEとして鳴りますね。

ジングルは、SEよりちょっと長い音で、例えば敵と遭遇したときに鳴る雰囲気を出すための音みたいなものですね。ダンジョンRPGとかであれば、ステージがあって、そのステージの環境音が鳴っている。そして、もちろんBGMがありますね。最後に、声優さんのボイスデータなど、こうした構造がありがちかなと思います。

これを全部読んでいただく必要はないんですけれども、ゲームの中に存在する音の種類を表にして考えると、それぞれ特徴があります。

例えば、最初にお話しした、ゲームの中の主人公の足跡や敵の叫び声は、音の長さは短い。ただ、再生する優先度は低い。でも、ゲームの中ではとてもたくさんの音が同時に流されるという特性があり、データの数ももちろん多い。

一方、キャラクターボイスなどは、音の長さはSEより長い。ただし、同時に再生される数はそんなに多くない。めちゃくちゃ(多くの人が同時に)しゃべるとぜんぜん聞こえないと思うので、かけ合いのシーンがあるとしても2〜3音ぐらい。ただし、例えばボイスを売りにしているゲームであれば、「ここの品質は捨てられないよね」みたいな話になってくるわけです。このようによく分けて考えるということですね。

それから、先ほどの表とこの分類のところに加えて、みなさんがこれから作るゲームの内容の特性によっても、大事にしたいことが変わります。

格闘ゲームであれば、パンチがバシッと当たった音をきちんと聞こえるように鳴らすのが大事になるわけです。恋愛アドベンチャーゲームだったら、ボイスの品質がとっても大事になる。「このすてきな声優さんの声が何フレーム以下の最速処理ですぐ再生できるようになりました」といっても、あまり意味がないわけです。

先ほどのように、ゲームに登場する音を分類して、どのタイプを最優先させるかを考えるのも、1つの考え方のポイントです。

ゲームエンジンのサウンド機能 VS モバイルタイトルの辛さ

話は変わって、Unity、Cocos2d-x、最近はHTML5のエンジンなどもありますが、みなさんも開発においてゲームエンジンをお使いでしょう。あるいは、自社のライブラリを使ってゲーム開発をされていると思います。ところが、汎用エンジンには「ゲームエンジンのサウンド機能 VS モバイルタイトルの辛さ」というテーマがあります。

開発に携わられた方はご存じだと思うんですけれども、モバイルオーディオというのは非常に大変です。

まず、端末の性能の制限が大きいというか、いろいろとあります。また、データ容量が大きいと……すみません、「ダウンロード率」と書いていましたが、離脱率ですね。データ容量が多いと、端末の中の容量をゲーム同士で奪い合うことになるので、小さければ小さいほど嬉しいです。

また、Androidの端末では、端末によって再生の遅延が起きやすいものもありまして、その対策をどうするかという話もあります。さらに、3Dバリバリのゲームだったりすると、そもそも音をたくさん鳴らしているので、音の再生負荷が高い。

加えて、運営型で「毎週イベントをやっていますよ」みたいなゲームだと、ボイスデータを順繰りに配信していくんですけれども、それを、UnityならUnityのエディタの中で、どうやって管理していくのかといった話も出てきます。このように、チャレンジングな部分がたくさんあるのが、モバイルのゲームオーディオです。

標準のサウンド機能は足りないことが多い

ところが、ゲームエンジンに載っている標準のサウンド機能は、こうしたモバイルの困難な状況に対しては物足りないです。

Unityはスクリプトをけっこう書かないと厳ししいです。また、Androidで音声遅延する場合がけっこうあったりします。「Unity 2019.1」で少し改善しましたが、Androidの一番下のAPIを叩いているわけではないので、まだまだつらい。

イントロ付きループができないといったこともあります。音声ファイルをイントロとループに分ければできるけれど、ファイルは1個でループポイントが埋め込まれているタイプのデータを再生できないといった制限があったりします。

UE4は、私が現在触っていないため、最新の情報は追えていませんが、やはりイントロ付きループ再生はできないそうです。「海外の方はイントロ付きループはしないんですかね?」といつも思ったりします(笑)。

話が前後するんですけれども、そういうプリミティブな再生システムの中で、「スクリプトを書いて作りましょう!」となったとき、やらなければならない、作らなければいけないサウンドの実装があるわけですね。

1つは、最初にお話しした端末の負荷軽減です。負荷を軽減するためには、「たくさん音を鳴らそうとするプログラムがあったときに、このゲームでは同時に鳴らせる数は〇〇まで」といった制限をつけてしまう。それから、CPUやメモリの管理の部分ももちろん気をつけながら、軽量の再生システムを使っていかないといけません。

2つ目の管理部分は、「ボイスデータがたくさんあります」といったゲームの場合、どのようなワークフローで管理していくかという話です。

そして3つ目は、演出の設計ですね。ゲームを作ってサウンドの機能を作りますというと、フェードイン・フェードアウト・クロスフェードみたいなものは、もう全員必ず作るものだと思うんですけれども、それもない場合はスクリプトを書いてやらないといけません。

あとは、ゲームで遊んでくれているユーザーに対して、「はっきりセリフを聞いてほしいな」といったときに、セリフが再生されたらBGMのボリュームを落とすような仕組みを考えます。このように、ゲームのサウンド実装には考えなければいけないことがけっこうあります。

Unityの標準機能

Unity標準機能を例にとって考えてみましょう。UnityのAudioの基礎は、みなさんご存じだと思うんですけど、一応ご紹介します。

サウンド素材をAudio ClipとしてUnityエンジンのプロジェクトの中に入れます。それをスクリプトの中で、Audio Sourceというコンポーネントを使って、シーンの中に適宜配置して再生処理を行います。そして、AudioListenerというコンポーネント……それはゲーム空間内の耳の役割をするもので、3次元処理を行います。そして、最終的にスピーカーから音が出るという流れです。この3つの要素についてはみなさんもご存じかなと思います。

加えて、Audio Mixerという存在があります。

これは自分のプロジェクトで作ってみたAudio Mixerの画像なんですけれども、例えば「BGM」「MenuのSE」「GameのSE」みたいに音の種類を分類して、適宜Audio Sourceから流れる音を、これらのミキサーに流してボリュームを調整するという手段が取れます。

Audio Sourceをグループ化して、グループごとにボリューム変更やエコー、リバーブ、重いエフェクトをかけたりという処理が、Audio Mixerでは可能です。

Unityでのオーディオ特性

Unityでのオーディオ特性なんですけれども、UnityのAudio Clipを触るときにロードタイプをきちんと気をつかって設定していますか? という話です。

LoadTypeというのは、「ストレージからメモリに読み込んで、実際に再生するまでに、どういうふうにデータを送るか」という部分です。例えば、LoadType「Decompress On Load」。ストレージに入っているデータを再生時にメモリに配置するのですが、そのときに圧縮データを展開して再生します。

Unityだとこれがなぜかデフォルトなので、なにも考えずにAudio Clipを入れて、BGMがいっぱいある場合に「5分間のBGMが3本あるので、とりあえず再生処理を組みましょうか」となると、なぜかめちゃくちゃメモリを使っていて「なんだこれは?」みたいなことが起きるんです。

ではどうするかというと、LoadTypeを「Compressed In Memory」に変える手段があります。

これは、ストレージの中に保存されているOggファイルなど、圧縮データをそのままメモリに配置して、再生するたびにデコードするというようなものです。

「これ、メモリの容量をダイエットできるからいいじゃん」と思うんですけれども、再生時に展開処理をするということは、それだけCPU負荷が発生して、再生が開始されるまでに若干のディレイが発生してしまうんです。だいたいはこの設定でいいんですけれども、「なるべく早く音を出したい」みたいなときには、これは使えません。

さらに「Streaming」というものもあります。

これは「圧縮データ自体をすべてメモリに置くのではなく、少しずつメモリに置いて再生しましょう」といった仕組みですね。「これもいいじゃん」と思うんですけれども、ストレージに常にアクセスして、ずっと読み込んでいるんです。例えば、ゲームの展開に合わせて、次のステージを裏側で読んでいますという状況が発生していたら、当然その読み込みできる量は、ストリーミング再生を使っている場合、邪魔されてしまいます。

このように、Unityの標準機能でいうと、読み込みタイプが3つありますというお話でした。

Unityのヒエラルキーの中で、AudioClipが格納されているフォルダに対して「このフォルダに入ったら、この音声設定をする」という仕組みを作ることも、Asset Post Processerを使うことで可能になります。それでどうにか管理できそうだというのが見えてきました。

サウンドのデータ管理

「データ管理、できるやん?」ということで、「じゃあ、音のタイプごとに再生方式を変えていこう」と。「フォルダごとにAudio Clipの設定を変更して、BGMが納品されたらこのフォルダで、ボイスが納品されたらこのフォルダで、とやっていけば、最適な設定ができるじゃん!」と思っているとですね……。

「フルボイスです! ボイスデータが2,000を超えます!」とか「BGMが50曲あります!」みたいなことがあると、ファイルリストが長大になって、管理が破綻してくるわけです。

これは想像なんですけれども、モバイルゲームの開発現場で、もしかしたら(スライドを指して)こういう図があるかもしれません。エクセルの「ボイス管理テーブル.xls」の登場です。

どういうものかというと、「ソーシャルゲームで、かつ、毎月・毎週なにかしらのイベントが配信されて、声優さんのボイスが配信されますよ」といった場合は、「音声データの中身がどういうキャラクターの何のボイスで、どんな場面に使われて、セリフの中身はこれで」といったものを手動で管理するエクセルの表みたいなものです。もしかしたらみなさんも現場でお使いかもしれません。

このエクセルの表で、どういうシチュエーションで鳴らされるかを管理して、Unityの中にデータを入れていくのですが、いざ再生システムに組み込むぞとなったときに、「ファイル名が変わります」「エクセルのバージョン違いがいくつもあるんだけど」「ボイスが入るAssetBundleが変わります」みたいな変化がたくさんあるんですね。そうすると、「いったいどれを鳴らせばいいの?」といったことが起きてしまう。

エクセルとUnityの標準の機能でなんとかしようみたいな話でいうと、実際のファイルと管理用のデータは別々です。すなわち、「データ」とそのパラメータである「メタデータ」が分離している状態です。すると、ファイルのズレが起きたり、さきほどみたいな状況になります。

Unityでフェードインを実装する

さて、そんなボイスの状況を踏まえつつ、サウンドの演出開発側の話もしておきましょう。たたとえばフェードインを実装してみます。Audio Sourceに便利な拡張スクリプトを作って、それを踏まえてコルーチンでボリュームをだんだん大きくすればフェードインできますし、DOTween、Tweenライブラリを使ってフェードイン・フェードアウトできますよね。

「フェードインは実装できる! クロスフェードは、フェードインさせながら、もう1本のAudio SourceでフェードアウトさせればOK!」(となりそうです)。

ところが、ゲームというのは状況がいろいろ重なるんですね。「クロスフェードを中断して、その直後にポーズをかけるんだけど、そのあと1秒以内にタイトルに戻ると、なぜかBGMが鳴らないんですけど……」みたいな(笑)。だんだん「この状況における、この条件」みたいな感じで非常に厳しくなってくる。やはり、オーディオの演出をスクリプトで作るのは大変です。

このように状況が重なってくると、管理がどんどん煩雑になっていくんですね。ほかにも、先ほどお話ししたようなAndroidの遅延の話などもありますし、あとは、(スライドを指して)これは繰り返しですね、エクセル地獄です。

「じゃあ、専用のツールを開発するか、Androidのドライバも端末機種ごとに全部1個1個書くか」という会社もたぶんあるでしょう。でも、それはサウンドとAndroidのネイティブに強い技術チームがいる前提という印象ですね。