フォルダー構成が重要な理由

Customer Success チームのコンサルタントとして、よく「アセットバンドルを正しく構築できているか」といった質問を受けます。私の答えは毎回同じで、「プロジェクトによりけり」です。その後、お客様に直接詳細をお聞きしています。この回答は正確ではありますが、今後のプロジェクトに役立つ知見は得られないでしょう。
よくあるジレンマではありますが、Addressables に関するガイドラインやベストプラクティスが存在するにもかかわらず、この質問に対するより一般的な答えを見つけるのには苦労しています。そもそもバンドルが正しいかどうかわからないユーザーもいますし、私の限られた時間では、プロジェクトのメモリを最適化できるか調べることしかできない場合も多いです。つまり、プロジェクトに含まれるすべてのアセットの使用目的を特定することができないのです。さらに、プロジェクトが大きくなるにつれ、1 人の担当者がすべてのアセットに対して同じ質問に答えることは不可能になります。
ある時、私はふと思いました。アセットバンドルを正しく構築できているかどうかという質問は、より一般的な答えを見つけにくくしているのではと。詳しく説明しましょう。
アセットバンドルはロードする際にメモリを占有します。そのため、アセットが同時にロードまたはアンロードされない他のアセットと一緒にバンドルされている場合、メモリの使用を最適化できていないことになります。したがって、特定のアセットが適切なバンドルに入っているか、あるいはバンドルに適切なアセットが入っているかという質問は、つまりは適切なアセットを適切なタイミングでロードできているかということです。特定のアセットをいつロードするべきかは、そのアセットの使用目的によって異なるため、「プロジェクトによりけり」ということになります。実際のところは、アセットの使用目的を知ることで、アセットがどのようにメモリにロードされ、どのようにバンドルされるべきかがわかります。
つまり、ここで鍵となるフレーズは「使用目的」になります。アセットの使用目的を知っている人は誰でしょうか。その使用目的を全員に共有するにはどうすれば良いでしょうか。そして最後に、それはいつ行われるべきでしょうか。
私が思う、明確にこれらが行われるべきタイミングのひとつは、アセットの作成または修正がされたときです。キャラクター用の特定のテクスチャであれ、グローバルライティングシェーダーであれ、複数のゲームレベルで使用するためのツリーメッシュであれ、使用目的を知っている作成者がその意図をチームを伝えるべきです。アーティストは、一貫したファイル命名規則を実施し、同じフォルダーに同じ用途のファイルをグループ化することによって、この意図を伝えることができます。
その後、プログラマーや他のチームメンバーはこの情報を用いて、特定のアセットを同時にロードされる他のアセットとバンドルすべきかどうかや、そのタイミングを決めることができます。このような理由から、プロジェクト期間中は、アセットの使用目的が一目でわかるよう明確にしておく必要があり、ファイルディレクトリはチームメンバー全員にとって信頼できる情報源である必要があります。
このブログでは、今後のプロジェクトをより良く構成できるよう、ベストプラクティスやよくあるエッジケースをご紹介します。まず、よくあるフォルダー構成とそれらの問題点についてお話しましょう。
私の経験上、フォルダー構成には 4 種類あります。ランダム、アセットの種類別、機能別、そして目的別です。これらの中で、一番望ましいの目的別です。目的別のフォルダー構成は、アセットの意図が伝わりやすく、最適なバンドル化戦略に最も役立ちます。
ランダムなフォルダー構成はあまり見かけません。このようなフォルダー構成は、ソフトウェア開発に不慣れな単独開発者が作成しがちですが、言うまでもなくプロジェクトの規模や複雑さが増すにつれて許されなくなります。構成の不備、つまりランダムな構成には多くの問題が伴います。アセットを見つけるのが難しくなり、使用目的を理解するのも事実上不可能になります。
アセットを種類別に構成することは、多くのアーティストがアセットをエンジンにインポートする前に行うため、これは、ごく一般的な方法なのです。アセットの種類が分かれば、その場所を見つけるのは簡単ですが、アセットに関するそれ以外の情報は分かりません。優れた命名規則があっても、キャラクター、環境、UI、あるいはこれら 3 つの組み合わせが、特定のシェーダー、テクスチャ、メッシュなどを必要とするかどうかを見分けるのは困難です。適切なファイルディレクトリは、情報を見えなくするのではなく、明らかにするものでなければなりません。
機能別のフォルダー構成はめったにありませんが、一見すると理にかなっているように見えます。多くの企業は機能ごとにチームを分けているので、データも同様にグループ化されていても不思議ではありません。しかし残念ながら、ゲームはデータをそのようには扱いません。過去に、シェーダーとオーディオのように、本来一緒にバンドルされるべきものが、異なるチームによって作成されていたために、この真実が曖昧になっていた例を見たことがあります。
アセットが目的別に分けられ、明確な命名規則があるファイルディレクトリを作成すれば、これらの問題を回避できます。これを説明するために、『Dinosaur Brawl』という架空のゲームを例に挙げてみましょう。
この例における『Dinosaur Brawl』は三人称視点の 3D アクションアドベンチャーゲームです。複数のバイオームが存在する広大なオープンワールドを舞台に、プレイヤーは 1 体の恐竜を選んで操作し、他の恐竜や太古の生物と戦いながら、来たる氷河期を生き抜くため、より強く成長して次の世代に遺伝子を受け継ごうとします。このゲームはモバイル向けに設計されていて、データの一部は最初にアプリケーションをダウンロードした際に提供されます。残りのデータは、必要に応じて CDN からダウンロードされます。
上記の概要から、プロジェクト全体の一般的なフォルダー構成を考案することができます。プレイヤーは恐竜を選択したり他の恐竜と戦ったりできるので、恐竜ごとにフォルダーを作成し、その恐竜固有のすべてのアセット(メッシュ、サウンドエフェクト、テクスチャ、アニメーション、パーティクルエフェクトなど)を保存することは理にかなっています。これらを一意のアセットとしてカテゴライズします。
アクションゲームなので、バイオーム別に分かれた環境が用意されます。したがって、平原、砂漠、ツンドラ、湿地帯、火山など、プロジェクト内のバイオーム(レベル)ごとに 1 つのフォルダーを作る必要があります。
もちろん、ゲームのすべてのセクションで必要になるアセットもあります。例えば、UI 要素などです。これらのアセットはグローバルアセットとして捉えることができます。一意のアセットごとに個別のフォルダーを作ったように、フォルダー階層の一番上に位置するグローバルフォルダーが必要です。例えば、グローバル UI フォルダー、グローバル恐竜フォルダー、グローバル環境フォルダーなどです。こうすることで、これらのゲームセクションで共有されるすべてのものが 1 つの場所に保存されます。
このアセットのカテゴリは、いくつかの一意のアセットと共有されていますが、すべてのアセットと共有されているわけではない、と定義されています。そのため、これらはグローバルアセットのカテゴリにも、一意のアセットのカテゴリにも当てはまりません。『Dinosaur Brawl』の場合、このような共有アセットの例としては、すべての翼竜に使用されるパーティクル、シェーダー、空中を舞う感覚を与えるために必要なサウンドエフェクトなどが考えられます。
これらのアセットは、それを最初に必要とする恐竜のフォルダーに入れられてしまうことがよくあります。残念なことに、これでは使い方を正確に伝えることができず、作られた意図がわかりづらくなってしまいます。考えうる最悪のシナリオは、このようなアセットがそれぞれの翼竜のバンドルに重複して入れられてしまい、メモリ、デバッグ、アプリケーションのサイズの効率が悪くなることです。
最良の解決策は、「翼竜」など、使用目的を示す名前を付けて新しいフォルダーを作成することです。具体的な保存場所を決めるのは、基準がないためもっと厄介です。個人的には「グローバル」や「一意の恐竜」のフォルダーと同じ階層のサブフォルダーに入れることが多いですが、他の一意のフォルダーと一緒に入れてもいいでしょう。
このような規則においてよく見られるエッジケースとして、プロジェクトの要件が変わり、元々一意のアセットとして作られたものが共有アセットに変更される例が挙げられます。『Dinosaur Brawl』の例では、開発時間を節約するために、他のすべてのラプトル(ユタラプトル、ダコタラプトルなど)のベースとして、ヴェロキラプトルのプレハブを使用することになりました。
しかし、ここで開発者たちが気づいていないことは、ヴェロキラプトルのプレハブがバンドルに追加されると、他のすべてのラプトルがロードされる際に、使用されるものがプレハブだけでも、すべてのヴェロキラプトルのアセットがダウンロードされるため、ダウンロード時間が長くなってしまうということです。
これが発生した原因は、アセットの意図が変更された際に、それがフォルダー構成に反映されていなかったことです。アセットの意図が変更された場合は、システム内の一貫性と正確性を維持するため、アセットの保存場所と名称も更新する必要があります。こうすることで、どのアセットを「ラプトル共有」バンドルに入れ、どのアセットをヴェロキラプトルの一意のモデルに残すべきかを、バンドル作成チームに伝えられます。
最も一般的で修正が困難なエッジケースのひとつは、アセットが意図しない形で使用された場合です。このようなケースは、たいていは事故です。例えば、誰かが期限に間に合わせるために、プロジェクト内の既存アセットを使って仕事を早く終わらせた場合などです。
例となるシナリオを挙げてみましょう。とあるアーティストが『Dinosaur Brawl』の次期拡張版のチュートリアルを追加し、プレイヤーのエリートエネミーへの反撃タイミングを強調する「黄金の輝き」シェーダーを見つけたとします。アーティストが知らないのは、このシェーダーが、多くのアセットがバンドルされている一意のボスである、巨大ティラノサウルスに対峙する際に表示されるエンドゲームコンテンツだということです。チュートリアルにこの「黄金の輝き」を転用すると、エンドゲームコンテンツのすべてのアセットが、まったく使用されないチュートリアルでダウンロードされることになります。このバンドルは巨大なため、通常はゲーム中にこのようなマイナーなアセットをダウンロードするシステムに負荷がかかります。結果として、パフォーマンスの急上昇や、接続環境が乏しいプレイヤーがアセットを時間内にダウンロードできず、ゲーム自体がクラッシュするといった状況が発生します。
上記は極端な例ですが、現実的な例であり、私が複数のプロジェクトで見てきた例でもあります。このため、フォルダー構成を整えるのに加えて、すべてのアセットにはその意図が伝わる名前を付ける必要があります。例えば、このシェーダーが gold_glow_trex_endgame という名前であれば、その使用目的は明らかになるでしょう。そして、デバッグを行えば、このアセットがチュートリアルでロードされるべきでないことも明確です。
Addressables に慣れている方は、Groups と Labels が、上記のゲームの例で提案したのと同じように、フォルダーやわかりやすい命名規則を使ってアセットをグループ化し、ラベルを付けるために使用されることをご存知かもしれません。「Groups や Labels を使えば済むのに、なぜこんな面倒なことをするのだろう」と思うかもしれません。
私の答えは、「両方ともやるべき」です。冒頭で説明したように、プロジェクトのアセットの数が増えれば増えるほど、1 人の人がすべてのアセットの使用目的を把握することは難しくなり、最終的には不可能になります。Addressables の Groups がフォルダー構成と一致するようにしておけば、正しくセットアップできていることが確認できるようになります。
Addressable なしでアセットバンドルを使用している多くのクライアントが、この問題の解決策として、複雑なシステムをコーディングしているのを見てきました。例えば、バンドルの作成に使用するマスターリストの作成や管理を行ったり、バンドル内の変更点を比較するためにバージョン管理のコミットをチェックしたりなどです。経験上、こうした解決策は、長期的な目で見ると費用対効果がよくありません。また別のシステムを開発し、維持しなければならなくなるので、失敗が起こる可能性がさらに増えてしまいます。プロジェクトの規模が大きくなるにつれ、長期プロジェクトで発生しやすい、無数の例外やエッジケースに対処できなくなります。最悪の場合、根本的なレベルで、ユーザーエラーに対する救済策がないために失敗が起こってしまいます。
適切に構成されたフォルダーシステムとファイル命名規則があれば、アセットバンドルと Addressables のグループを完全一致させることができるでしょう。ファイルを論理的なグループやサブフォルダーに分類することで、チームメンバー全員がファイルを一様に解釈し、場所を特定できるようになり、担当者が変わったり、プロジェクトの要件が変化したりしても、誤解や食い違いが生じる可能性が低くなります。アセット作成者は、アクセスやナビゲーションを簡単に行うことができ、他のチームメンバーは、特定のアセットを探し出すという骨の折れる作業から解放されます。システム的なアプローチによって、貴重な時間を節約し、エラーや見落としの可能性を最小化できるのです。永続的なソースポイント、つまり信頼できる情報源を設定することで、新しいチームメンバーの受け入れプロセスを容易にし、長期にわたるプロジェクトの実行可能性を確保できます。
フォルダー構成についてのサポートやアドバイスをお求めですか。フォーラムでぜひご投稿ください。また、現在連載中の Tech from the Trenches シリーズの他の Unity 開発者による新しい技術ブログもぜひご覧ください。
