はじめに
CEDEC2025 Unity向け軽量サウンドマネージャの開発と運用:小規模タイトルに最適な設計について
のスライド解説になります。
CEDECの時とは違って、
感想のような余談のような感覚で解説していきます。
DEMO:実際に作ったサウンドマネージャーの紹介です。
自己紹介(という昔話)
古くはアーケードの基盤向けのサウンドデータ作成ツールなどの開発・サポートもしています。
当時は開発環境のメモリが8MB、実機のメモリが8MBくらいの小さなデータを作る形で、
音源制御のチップも別、メモリも専用のものがあったりと、今とはだいぶ違う設計。
コンシューマー機の初期のころは内蔵音源で、アーケードより少しスペックが低いくらいのものでしたが、やがて、追いついて、途中からはメインCPUで処理するようになって、今の環境に近い形に変化していきました。
PC環境とかだと、メモリやCPUも贅沢に使える傾向がある一方、家庭用ゲーム機やスマホ、スタンドアローVRなどはまだまだ、制限が多い環境。
制限がある中でも、先端的技術の応用や、枯れた技術の応用で、なんとか表現を追求しようとするのがテクニカル系のデザイナーの仕事になります。
2足のわらじ
デザイナーとエンジニアは、職種名が分かれているように、最終目標が微妙に違います。
デザイナーは短期間で、大量のアセットを効率よく生成します。
エンジニアは、長期間で安定・最適化されたシステムを構築します。
デザイナーは一時しのぎであろうと人力の地獄のような繰り返し作業であっても、限られた期間の中で最大の品質やこだわりを保つことを求めます。
エンジニアは、プログラム技術で解決することを好みます。プログラムで効率よくできることに気持ちよさを感じます。
もちろん、どちらの視点も持っている上で、比較的どちらよりといった形で、グラデーションはあります。
自分の場合、両方を切り替えたり、視点を変えたりして業務にあたっている印象です。
デザイナー向きの仕事は、効率化が難しい面があります。
エンジニア向けの仕事は、効率化しやすい面があります。
もちろん、上に当てはまらない事象もよくあり、臨機応変さが求められることが多い印象です。
開発期間による制作スタイルの違い
- 開発期間が短い
- 更新頻度も高い
- 素材作成しか時間的に難しいけど、品質は保ちたい
品質というのは、
「どういう音が良いか」
を知っているうえで、
「良い音をゲームに乗せたい」
という欲求を持つものが
サウンドをデザインしていると思います。
そして、サウンドデザインツールとして、
DAWはとても強力なツールで、
ありとあらゆる音へのこだわりのニーズをかなえてくれるツール。
ワークフロー
ゲームの音を作るのは、一人ではない。様々な人の手が入って音が出ています。
音楽で例えるなら、
- ゲームのデザイナーが、コンポーザー(全体構成のデザイナー)
- サウンドデザイナーが、楽器制作(サウンド部分のデザイナー)
- サウンドエンジニア・プログラマが、楽器制作と演奏支援
- みんな(ユーザー)が、演奏者
それぞれの役割・責任をもって作業を進めていきます。
1つの音を作る
サウンドデザイナーが音を作る時には、なるべく手間のかからない仕様でありながら、素早く目的の音を提供すること。
求められた音を推測し、できる形でデータにする。
何が求められているかが重要で、デザインに傾倒するのであれば、個性は消えていきます。
(好きにやっていいですよ、みたいなタイトル開発でもないかぎり)
ただ、求められている範囲の中、一定のクオリティを保てるのであれば、好きにやってもよい。
いくらでもこだわれる部分でもあります。
作業見積
ざっくりとでも、試算しておかないと、スケジュール末期で徹夜対応するという、非現実的な(あるいみゲーム開発者的な)作業を強いられることになります。
最初は短時間でできる範囲で、材料を用意し、時間があったらこだわるように作業しています。
BGMの作成は、空き時間にやることが多いです。
仕組みとプログラム
エンジニアがツールを作って解決することも多いですが、
実際に運用をするとちょっとしたことが手間になり、改善を求めたくなります。
専任のエンジニアがいてくれると改善されていくのですが、大抵は、メインプログラマが片手間とかで対応していたりすることが多く、また、デザイナーは動けば(納期に間に合えば)文句は言わずにできる範囲で働いてしまう傾向があるので、改善されない仕組みで進めてしまいがちです。
なかなか、すでに動いていた仕組みがあったりすると、
仕組みを再考する余裕はなかったりします。
サウンド制作の場合、ゲームが固まるまでに時間がかかることが多く、前半は暇な期間が生まれます。
この時間を無駄にせずに仕組みの改善を狙っていくのが良いかと思います。
ゲームの音作り
要望通りの音を作る
- ゲームの音は、要望を叶えるところから始まる
- 音を一つ作るには、「どんな音か?」の検討から始まる
- 大量にあるので、リストにしておくと、後々管理で楽できる
ゲームで音が鳴っているのは、なぜ鳴らしているのか?必要性から検討し
意図などが明確であってほしいです。
他のゲームで、音が鳴っているから というのをもう一つ掘り下げて、意図まで検討
しないと ただの複製になってしまう。
この最終形がどこまで見えているかの解像度をうめられることが、最初の段階では結構重要です。
作業時間管理
音を作る作業時間も重要で、一日に15個作るとしたら、1個にあてられる時間は30分くらいだなと
検討して、リストのどの音が大事かなどから、作りこみの幅をコントロールする。
そうやって、大量の音の品質を保ちつつ、良いものを目指す作業をする。
いくらでも手を抜けるし、いくらでもこだわれるチャンスがあります。
サウンドマネージャとは
- サウンドマネージャーはゲームで音を鳴らすための仕組み
- Unityオーディオはメジャーな環境なので安心な部分がある
- 複数人で扱う時は、データ競合しないように気を付ける
余談:更新しやすさ・更新可能タイミング
ゲームで、データを更新するというのは、不安定にする要因でもあります。
サウンドを少し調整して、すぐにゲームに反映されるかどうかは、
データ更新の複雑さ・テストの信頼性などが絡んでくる。
ゲームは、負荷やメモリ使用量などの変化は、非常に繊細な話となり、
余裕のあるプラットフォーム(PCなど)ではあまり問題にはならないのですが
スタンドアローンVRなどでは顕著だったりします。
そのため、リリースが頻繁にある場合、リリース前の更新はバグ修正にとどめたいことが多く、
サウンドの問題による手間の発生は避けるべきです。
そのために
更新頻度は高くしたうえで、
タイミングとしては、リリース前にサウンドの修正は終わっておくのが良いです。
バージョン管理
バージョン管理の話は、あまり表にでてこない話題かもしれません。
地味な話題でありつつも、依存している部分です。
とくに、大きな開発に関わることがないと、どう扱うのかもわからないものです。
バージョン管理しなくてよいもの
- ローカルにある作業途中のデータ
DAWのプロジェクトだったりは、あまり途中経過を残していなくても問題はないと思います。
たいていは、wav出力したら再度開きなおすことはないから。
バージョン管理が必要なもの
- ずっと継続して編集しつづけるデータ
Unity Editorのファイルは、ゲーム開発中ずっと作業し続けるデータで、
壊れたらもとに戻せるようにバージョン管理が必要です。
同じようにオーサリングツールでも、同じファイルを触り続けなければならないような場合も同様にバージョン管理が必要です。
(壊れたらもとに戻せるようにしておく必要があります)
サウンドデザイナーがUnityを触らなくなると良い点
バージョン管理が必要なくなります。
人にもよりますが、作ったデータを管理するのが得意な人種ではないように思えます。
(比較的、エンジニアなどとくらべて)
これは、たいていは一回限りのものを作ることが多く、再利用できるような作り方
あるいは、何かを大きく構築していくような作り方とは違うような気がします。
一方、バージョン管理ツールは、エンジニア向けに作られていて、
あらゆるトラブルを避けられるように機能が豊富であるため、敬遠されがちです。
ほとんどの場合使わなくてよい機能や、過剰な保護機能によって、バージョン管理自体に時間がかかってしまうことが多い印象です。
これはクリエイティブの邪魔になります。
サウンドデザイナーは音を作ります
名前を決めるのは、経歴が長いと問題ないですが、もともとは苦手と思われます。
ゲームのデータ管理になれた人に、命名規則などは決めてもらうのが良いです。
下手な例:play_001.wav, scene_0.wav, bgm0.wav ← 名前が意味を持っていない
たいていは、数字を禁止することで、抑制することが可能です。
数字は、同じ音のバリエーションにとっておくのが良いです。
例えば、
se_maracas_shake_00,
se_maracas_shake_01,
se_maracas_shake_02,
3種類のマラカスを振った時の音 (鳴らす時にランダムにリクエストしてください)
といった感じ
サウンドデザイナーはGoogleドライブに音をあげるだけ
実際に、ゲームに組み込む前に別途整理される可能性もありますが
リストに対応するファイルを用意しておくことで、
後段はどうにでもなります。(プログラムでリネームするとか、プロジェクト固有のルールに変換するとか)
更新手数を一手でも減らす
昔、ゲーム開発のツールを作っていたときに、
開発の要望として、同じことができるのであれば、
マウス移動、ボタンクリックなど、不要な操作は一つでも削ってくれ
という要望がありました。
これは、ツール開発者が見落としがちなのですが、
もし、100個のデータを作る時に、
操作が2手かかるとすると、 200回の操作が必要になります。
これが、一つ操作を削れば、100回で済みます。
さらに、まとめてできる機能があれば、1回にまで減らせる可能性があります。
波形をドロップで追加するとき、
特定のフォルダにエクスプローラーからまとめて入れるだけで、
100個の波形であっても1回の操作で済みます。
もしかするとGoogleドライブにあげて何かアクションするだけでgit上のUnityを更新するとかまでできてしまう可能性もあります。
実際には、仕組みをもっと厳格にしないと運用が難しいかもですが・・・厳格にしすぎるのも余白がなく辛い場合があるので、ワンアクションくらいにとどめておく感じでしょうか。
Demoの時間
フェードは重要です。
フェード機能が実装できるようなサウンドマネージャーが設計できていれば、
サウンドのたいていのことはプログラムで解決できます。
フェードがプログラム上やっかいな点として、
リソースの解放が遅延することです。
また、ゲームと並列して変化し続けるため、多少のコストになります。
そのため、Unity標準機能ではサポートされていないのですが、
サウンド演出としては、必須の機能です。
これを実現するには、オブジェクト指向の中でもプール機能を理解する必要があります。
エンジニアが気にする実行時コスト
長い経験の中でも、文字列を扱うだけでガベコレがかかるなんて事実を知るのはかなりの年月が経った後でした。
それまでは、
知らずにデバッグ表示などで文字列生成したりと、パフォーマンスに影響がでて問題です。
(C言語では問題なくてもC#では問題といったところ)
文字列を str1 + str2 とかしただけでも 見えないnewが発生したり。
ゲーム開発では致命的です。
Unityには文字列をガベコレなしで扱えるものが欲しいところです。
人間はミスをする生き物
文字などというあいまいな、思い込みしやすいものを扱うのはかなり危険です。
最新の注意をして作業をしていても、見落とすことはあります。忙しくなると発生率が上がります。
人間同士のやりとりであれば、いい感じに解釈されますが、
プログラムはちょっとでも違うとエラーになり、開発が止まります。
開発中に文字を入力させないように仕組みでカバーしましょう。
Unity Editorの拡張性に感謝
おそらくUnityというゲームエンジンがこれほどまでに広まった一つに
エディターの拡張性があげられます。
それまでは、ツールの機能を拡張したいと思っても、開発元が許してくれません。
開発元が良かれと思ったデザイン・開発スタイルに従って作業することになります。
ゲーム開発は、開発元の想定できないものを作ることが多いため、
Unityはなんでも作れるように、拡張性をもたせてくれています。
blenderとかも似たような印象ですね。 reaperとかも。
ゲーム開発用のオーサリングツールは、拡張性が求めらます。
実行時の設定
発音リミットは、実は不完全です。
何故なら、UnityはAudioSourceで音を鳴らすときに、優先度はみるけれど、仮想ボイスがどこまであるかが把握できないし、制御もブラックボックスです。
そのため、プールしているAudioSourceが必ず再生できる保証はありません。
実際には、鳴らす側も適度に鳴らしすぎない工夫が必要です。
ここでできることは、BGMを大量に鳴らさない くらいの制御になります。
ランタイム時の音の再生設定の初期値
UnityではAudioSouceという音を鳴らす器が、音を実行時(ランタイム時)にどうならすかをすべて持っているのです。
この設定は、ランタイム時に書き換えられて、変化してしまう情報です。
AudioDataは、音ごとにその設定の複製を持たせてデータとして管理し、初期値として扱います。
実際には、音を鳴らす時に、プールから音を拾ってきたタイミングで、
鳴らす音に従ったAudioDataの設定を反映させています。
イントロ付きループはサンプルレベルではない
よく、波形でループをとるとかありますが、
イントロ付きのループの波形を用意するのは至難の業です。
できなくはないけど、かなり手間がかかります。
ここでは、
イントロ
ループ
アウトロ
の3つの波形として用意することで、ループ波形を作る時の手間を一つ減らしています。
イントロのテール(余韻部分)が
ループの開始にかかっていてほしい。
アウトロのテールがループの開始にかかっていてほしい。
3つ音を連続で鳴らす時
内部では、
先頭の1ショット音の終わり0.3秒前からフェードアウト
先頭の1ショット音の終わり0.3秒前からループの波形を0.3秒かけてフェードイン
ということをしています。
これで、なんとなくつながって聞こえます。
/// <summary>
/// 連続再生
/// </summary>
private async TaskType ChainPlayCoroutine(AudioHandle[] handles)
{
for (int i = 0; i < handles.Length; i++)
{
AudioHandle currentHandle = handles[i];
if (currentHandle == null) continue;
bool loop = currentHandle.m_AudioSource.loop;
if (loop)
{
// Loop波形時はFadeIn
currentHandle.PlayWithFade();
// 自動開放
currentHandle.UseAutoDispose();
}
else
{
currentHandle.Play();
// 自動開放
currentHandle.UseAutoDispose();
}
// 再生が終了するまで待機
while (currentHandle.IsPlaying)
{
// FadeOutに入ったら抜ける
if (currentHandle.IsInFadeOut)
break;
if (!loop)
{
// 1shotの場合再生時刻から逆算して0.3sec前なら
if (currentHandle.m_AudioSource.timeSamples >
currentHandle.m_AudioSource.clip.samples - (48000.0f * 0.3))
{
break;
}
}
await TaskType.Yield();
}
}
// 解放忘れ防止のためDispose呼び
for (int i = 0; i < handles.Length; i++)
{
AudioHandle currentHandle = handles[i];
if (currentHandle == null) continue;
// 自動開放
currentHandle.UseAutoDispose();
}
}
距離減衰は誰がデザインするのか?
UnityEditorのようなものが無い時は、
ミドルウェアのオーサリングツールでデザインできるのはかなり重宝します。
それでしかできない波形の組み合わせとか凝ったこともでき、プログラマからは隠蔽した形で実装できます。
ただし、どういうワールドのスケールで、カメラの画角で、どれくらい距離が変化するものなのかを想像しながらデザインすることになります。
また、どこでどう使うのかを把握していないとデザインできないのはかなりつらいです。
ですが、UnityEditorのようなゲームを実行しながら、カーブを変更できるのが理想です。
実行時にAudioHandleのリストから、距離減衰のカーブのデータへ ワンボタンで表示をし、
実行時に変更したカーブが即座に反映され、スクリプタブルオブジェクトのおかげで、シリアライズも行われます。(保存忘れが無い)
これは、UntyのAudioSourceのカーブ編集(実行時にしても保存されない)という、悲しみから解放してくれます。
Unityのミキサーとかは、実行時Editの設定も反映してくれるけど、AudioSourceは実行時のインスタンス設定で、プールするみたいな使い方だと、編集対象が変わってしまうので、そこができてもうれしくない。 Unity上で、AudioData型のような波形に依存するデータとして紐づけておくのが肝なようです。
デバッグ機能
サウンドのトラブルはつきものです。
音は、すべてを聞き取るには情報が多すぎます。
視覚的にも確認できるようにしておくことで、
様々な問題を解決できる手助けになります。
オーディオの情報を一覧するビュー
昔の音源の表示とか見ていると結構楽しいのです。
デバッグという要素もありますが、何かすごいことが見えないところでいろいろあって、
演奏者の手元をズームしてみているみたいな感じで、楽しさと学びが得られます。
パッケージ
必要な機能をパッケージという形で配布できるのは pythonとかのように環境を明確にできるところが良いです。
またほかのモジュールと同じように扱える点も、理解しやすいです。
もちろん、バージョンの更新なども簡単なので、一部の先進的な処理のテストだったり、古いターゲットは、安定バージョンでおさえておくなど臨機応変な対応が可能です。
ミドルウェアとかはこの実装みたいに機能別に必要なもモジュールを組み合わせるみたいになっていてほしい。(最新だけとか、すべてのバージョンを合わせてとかだと結構つらい)
アセット管理がシンプルだった時代の話
古くは、Romに焼く
みたいな形で、実際にサウンドのリードオンリーメモリーに書きこむみたいな時代もありました。
(ツール側で、書き込むアドレスを指定したり、ロードするスロットの幅(連続メモリサイズ)の指定などできたりした)
CDのようなメディアに焼くという時代もありました。
(焼く位置による高速化とか)
それくらいまでは、シンプルでした。
サウンドクリエーターでも把握できたかもしれません。
アセット管理が複雑になった最近の話
昨今のゲームでは、サウンドデータが、クラウド上のどこかに置かれます。
Metaのプラットフォームであれば、メタのストアのどこかに置かれ、
それがゲーム実行時はダウンロードされて、ゲーム機のストレージのどこかにおかれ、
さらに実行時はラム上などに展開されて、やっと音が鳴ります。
プラットフォームやストアの形式によってさまざまな制限があって、サイズ制限のために分割したり。
また、アップデートが頻繁な場合、どの単位で、どこまでのデータを更新としてまとめるかなどは、
ゲームプロジェクト側でそれぞれ個別に管理されています。
(ゲームの最適化都合で分割されたり)
これらを把握するのは難しい時代になったと思います。(個人的感想)
専門家にまかせましょう。
また、まかせられるような汎用的なフォーマットの方が都合が良いかもしれません。(ミドルウェアのデータが稀に都合悪くて、両方の折衷を設計しないといけない時などあったりするのは、なんとかなってほしい希望)
まとめて呼ぶ関数ほしい
オブジェクト指向とは真逆ですが、
メモリに順番に並んで処理するほうがプログラムは効率が良いはずです。
UnityのAudioSourceであれ、ミドルウェアのハンドルであれ、過度なオブジェクト指向気味に感じます。
昨今、音は、40音くらい72fpsですべての位置を更新したり、発音・停止制御・再生パラメータを変更したりし続けます。
まとめてできたら、高速化できるのではないかなと思っていたりします。
最後に
CEDECのセッションでは2倍速くらいの叩きこむような口調でやってしまって、反省。
次回は時間のゆとりのあるレギュラーセッションを目指します。
もし、何か不明な点などありましたら、お知らせください。