
神奈川パシフィコ横浜にて9月4日から9月6日まで開催されたCEDEC 2019。「『大乱闘スマッシュブラザーズ SPECIAL』~膨大なアセットのビルド時間&容量との戦い」セッションレポをお届けします。

このセッションにはバンダイナムコスタジオCS技術2部 CS技術6課ゲームプログラマの中村彰宏氏と、コアテクノロジ部コアテクノロジ1課 南相培氏が登壇しました。
■如何にビルド時間短縮を図るか―PCの高スペック化と環境の統一

初めに登壇した南氏はアセットパイプライン周りを担当する人物で、『大乱闘スマッシュブラザーズ for Nintendo 3DS / Wii U』の開発にも関わった経験をもっています。本作で求められた質と量は想像以上だったため「ユーザーに最高のタイトルを贈らなければ」と自覚したようです。

さらに本作では、登場ファイターが70体以上、ステージも100以上、そして60fps厳守というシビアな条件が求められていました。これら要求の質と量を一定水準まで満たすためには工夫が常に必要で、限られた時間とリソースで最大限の効果を発揮した施策を紹介するのが今回のセッションです。

次に中村氏が登壇しました。当初からの懸念されていたこととして「アセット規模の増大」が挙げられます。本作のアセット規模は単純計算で『for 3DS』と『for Wii U』、そして過去作分『スマブラX』とそれ以前のコンテンツも含む膨大なもの。

さらに対応言語数も増加すると共に、全リージョンを1つのROMに統合している規模です。アセットビルドパイプラインの肥大化と、最終的なロムの容量を抑制する必要があります。そのため基本方針として、前作の資産を活用することになりました。

第1章はアセットビルド編です。全ゲームリソースファイル規模は中間フィイルを別にしても約23万/42GB(v1.0.0の時点)と莫大な量です。そのため、一つ一つビルドしていくと13時間ほどかかってしまうため処理時間を短くする必要があります。

方針として「インフラの見直し」と「アセットビルドの高速化」、そして「アセットビルドフローの見直し」を行いました。前回はSubversion+Alienbrainで作業してしましたが、安定性の向上とレスポンス改善を図るため今回はPerforce+Helix Coreを活用しています。一部をAlienbrainで管理しつつ、ワークはSSDで構築。アセットビルド構築時間では、HDDが4時間30分で、SSDが4時間とアセットビルド時間に30分ほど差があります。そのためM.2 SSDへの切り替えも視野にいれているとのこと。


またスタッフ全員にハイスペックPCを完備させるのは難しいですが、一部の自動ビルドマシンで構築すれば提出ロムの作成など、クリティカルな所に高性能なものを導入することでも効果を発揮出来ます。従来のPC(3.6GHz、4コア8スレッド)では4時間で、高性能PC(3.8GHz、10コア20スレッド)では2時間半と1時間30分も短縮出来ています。


重いビルド自体の高速化では、ボトルネックを調査すると共に重いものを洗い出し高速化を図ります。また高速化が難しい場合ビルドステップを分割し、並列処理を強化します。


またパラメーター調整用ファイルの解析処理をエクセルで管理していましたが、ビルドが重く、1ファイルあたりのビルド時間が長くボトルネックになっていました。重かった原因は標準機能のCOMでしたが、エクセルの編集でなく解析が目的ということからエクセルファイル解析処理を高速化します。エクセルの中身はOpenXMLフォーマットであるため、C++でXMLを解析することによりComと比べて90倍程度高速化に成功します。


ビルド済みデータのキャッシュでは、他の人が作ったバイナリを流用し、全ヒットの場合40分へと1時間を切る大幅な短縮を図れました。異なる環境によってビルド結果が異なることもあるためIntelとAMD、GPUのGeForceとRadeonの違いに加え、GPUドライババージョンの差異も影響するため開発環境の統一が大切と加えます。最終的にアセットビルド時間は、13時間掛かっていたものが作業PCで1時間半、高性能PCで40分と早さにまで短縮することに成功しています。


アセットビルドでは「Ninja Buld System」というApache 2.0ライセンスのオープンソースソフトウェアを「忍者は速いというイメージ」から採用しています。設定(.ninja)ファイルは、基本1ファイルで全コマンド記述されています。この「Ninja」の強みとしては、高速なビルドシステムやコア並列ビルド、インクリメンタルビルドに加え、アセットビルドやWindowsでも使いやすく、レスポンスがそもそも早いことも挙げられます。


Ninja_syntax.pyを利用してジェネレーターを作成し、リポジトリのアセットファイルをみて生成します。また番人となる明確な担当者を立てると共に、編集者の多いファイルのぐだぐだ化を回避しました。


またリポジトリ構造を初期段階から設計すると共にプログラマ主導で管理することで自由にさせすぎないようにしています。アセット置き場は特に厳格な取り決めを行い、ninjaファイルを生成しやすくするためビルドルールがシンプルになるようにしています。

Ninjaはexe単体で動かすには問題ありませんが、ライブラリを独自ツールで実装するのに改造が必要になりました。リセット処理やメモリリークの修正、ターゲットごとのビルド開始時イベントと出力ログをもらうためのコールバックを追加しています。またビルドシステムのルーチン本体はそのままですが、修正内容のフィードバックは検討中のようです。


前作での問題では、リソースバイナリも直接リポジトリ管理したことでリポジトリのさらなる巨大化やバイナリゆえの激しい衝突をバージョン不一致がありました。不一致問題は運用でカバーしたものの、根深いものだったため解決優先度が高いものだったとも加えます。

他にもネットワークドライブにリソースバイナリを設置する方法がありますが、最新のデータ以外は保持しづらくデータも嵩張ってしまいます。元データのみをリポジトリに登録する方法を採用し、バイナリはローカルでリビルドして生成する方法に決定。これによって作業ローカルにおける元データとリソースバイナリのバージョンが必ず一致するようになりました。例外的に直接バイナリ管理したものもあり、フォントやムービー、そしてビルドに時間がかかるものがそれに当たります。


ローカルでビルドを行うことにしたもののビルド時間の問題が浮上してしまいます。これらの問題を解説するための仕組みとしてリソースサービスを開発。ホストPC上にあるワークとゲーム開発機を繋ぐツールで、タスクトレイに常駐します。


ここで「マリオを調整する人にルイージのデータは必要?」という例を表示。マリオとマップのデータだけで良いため、必要なデータだけを必要な時にビルドするシステムをオンデマンドビルドと呼び構築しました。


このシステムの利点としては不要なデータまでビルドする必要がないことと、ディスク容量にも優しいことです。一方で、ビルド時間分ロード時間が長く、一度に幅広いチェックをする人には恩恵が少ないという問題もあります。


そのためバックグラウンドビルドを実装。空き時間に裏で自動的にビルドするというシステムですが、問題点として順にビルドすると長く、利用頻度の高いデータがビルドされるタイミングがわからないことです。よく使うリソースを有線してビルドしたいためリソース利用状況を学習する必要性があることを述べると共に、まとめを映して1章を終えました。


■ROM容量を下げる手法―圧縮コーデック・重複削除・サイズの可視化
第2章はROM容量を下げる施策に関する内容です。各アセットサイズの目標値を『for Wii U』でのサイズ統計から今作の指標を算出したところ、ファイターが5GB、ステージが3GB、UIが2GBなどです。

そのため1ファイターの容量配分はモデルが33.5MB、モーションが14.5MB、エフェクトが11.5MB、サウンドが3MB、その他が1MBとなっています。1ファイターのサイズがトータルで収まっていれば、モデルやモーションのサイズを融通しあうことが出来たとも説明します。この表はメモリ配分を表す表となるため、メモリ使用量を意識してアセットを作れば結果的にROM容量も守られるとも述べました。

特に大変だったのはBGMです。前作分だけでも膨大で、今作だけでジングルも含めると900曲以上となるため「マジすか」と声がでるほど驚いたそうです。前作のWii U版では500曲ADPCMで圧縮し2.6GBの容量でしたが、今作の900曲となるとADPCMで5GBに到達してしまいます。ここで新コーデックOpusを採用し1.2GBまで抑えることができました。


このコーデックは比較的新しいもので高品質・高圧縮・低レイテンシの標準化されたオープンフォーマットです。今作の開発においてはまさに救世主と呼べるものと表現し、AndroidやiOSにもネイティブにサポートしているため知名度が上がっているとも説明します。

インゲームのサイズ削減では、オンメモリデータ向けのデータ圧縮を行いました。『スマブラfor』ではzlibを活用していたものの、より高圧縮・高速なものを求めLZ4から最終的にZstandardになりました。LZ4は高圧縮率・高速圧縮・解凍な機能を有しており、解凍速度が早くロード時間に直結するためかなり魅力的でした。

しかしながら容量が収まるのか怪しくなってきたため途中でLZ4からZstandardに変更。Zstandard は、LZ4同じ開発者によるもで、元々Facebook内部で開発使用されていたとも解説します。特徴としては、LZ4より高圧縮率ですが、圧縮・解凍時間が伸びることが懸念点とも語ります。

圧縮レベルはLZ4で1から12、Zstandardで1から22にまで指定できますが、使用した圧縮レベルはどらも9です。最大レベルまで圧縮しないのは微々たる量しか小さく出来ないために加え、レベルを最大するとさらに時間がかかるからです。一方でZstandardは最大レベルでもそれなりに圧縮できることが表から読み取れます。


またソース本体は一切触らずプラットフォーム用にlibのビルド対応したのみで、OSSなのでゲーム用のビルドは標準でされていませんが、組み込み自体はそこまで難しくないと振り返ります。Zlibを扱えているのならそこまで苦にはならならず、シンプルなため理解に苦しむこともないですが、LZ4とZstdで少し違いがあるようです。

LZ4からZstdに変えた場合のロード時間はほぼ変わらないケースが大半でした。要因としては圧縮率が上がれば処理するサイズが減っているためで、ファイル読み込み時間にある程度埋もれていたことも挙げられます。またロードと解凍をストリーミング処理したそうです。


Zstdの辞書機能については、軽く検証したが採用しませんでした。理由として情報が少ないことと辞書データの運用が難しそう(辞書データそのものの取り扱い)なこと、そして必ずしも圧縮率が上がるわけでなかったからです。

さらなる対策として重複削除を行いました。1ファイターあたり8種類のカラーバリエーションがあります。見かけ上のサイズ削減手法として、同一バイナリは1ファイルにまとめてしまうこと、ファイターとステージデータに適応することです。タイプは2種類で、積集合と和集合を使用しています。


マリオでは積集合では指定された組全てで一致すれば1ファイルにまとめ、目のテクスチャはまとめ、帽子のモデルはまとめていません。和集合ではステージに適応し、ステージはファイターと異なり1ステージ部分にまとめてロードすることとオブジェクト数が多いため足場など同じテクスチャを使用しています。


サイズ以外の利点では、アセット制作者が各カラバリ間で同じファイルを気にせずとも、わざわざ共通データ管理をしなくて良いという部分。アセットパイプラインをシンプルに出来るため、アプリプログラマーは基本的にまとめられていることを意識する必要がありません。


リソースの実体は1つであるためメモリ使用量も減ります。これら重複排除の施策を行ったことで14.6GBから11.2GBへ3GBも削減することが出来ました。


リソースサイズの静的解析では、システム的には目標に収める目処がたったものの運用上の問題が残りました。作業ローカルでサイズが求められないリソースバイナリが確定した状態ではないと確定出来ないというもので、全てのリソースバイナリが確定した状態、つまりROMを作らないとわかりません(実際に出来たものも含めて)。

アセットサイズビューワーをElectronで実装し、ファイルサイズとメモリ上のサイズ、そしてROM上で確認可能にしました。またファイルサイズからテクスチャ出力設定のミスなどを予測出来るとのこと。

これらの施策から全ゲームリソースファイルは約23万/42GBだったものが、開発中に圧縮し20GBへ、そして製品同等圧縮で13GB程度まで圧縮できました。最後にまとめとして、「全て規定値内に納めきったので大勝利」や「圧縮技術を見極める」、「早期に見積もりを出すのは効果的」、そして「可視化は求められる物に合わせて」を挙げセッションを終了しました。

