AssetBundleとアプリ本体のアセット重複を防ぎたい

こんにちは。技術部平山です。

今日は、「アセットバンドルに入れる素材をアプリ本体のプレハブから参照しっぱなしじゃないかー」 という事故を削減するためのツールをご紹介します。

コードはGithubに置いてあります(AssetReferenceFinder.cs)。 そのままでも使えるとは思いますが、カスタマイズする方がなお良いでしょう。 そこも後で詳しく述べます。

使い方

まず使い方を示してしまいましょう。

インストール

上の.csファイル をプロジェクトのどこかのEditorフォルダの中につっこみます。 すると、メニューにKayac/AssetReferenceFinderが現れます。

使う

プロジェクトビューで、アプリ本体から参照してほしくないアセットが入ったフォルダを選択状態にします。 AssetBundle素材を置くフォルダ、を指定するのが想定する使い方です。

そしてメニューからKayac/AssetReferenceFinderを選択します。

すると、 プロジェクトのフォルタに"AssetReferenceFinder.txt"というテキストができ、 指定したフォルダのにあるアセットから 指定したフォルダのにあるアセットへの参照があれば、 ここに順次書き込まれていきます。

プロジェクトの規模にもよりますが、書き込みが終わって、 UnityEditorが操作可能になるまでには数秒から数十秒ほどかかるかと思います。 心を広くしてお待ちください。

さて、実製品での出力は問題がありすぎてお見せできませんが、だいたいこんな感じです。

Asset/Prefab/hoge.prefab
    Asset/AssetBundleResources/Character/hero.png
    Asset/AssetBundleResources/Animation/hero.anim

hogeというプレハブが、AssetBundleResources以下にある、 いかにもアプリ本体に入れちゃいけなさそうなものを二つ参照しているよ、 ということがわかります。直しましょう。

なお、選択したフォルダだけでなく、ResourcesやStreamingAssetsにあるフォルダ も自動で検索します。これらはパスを指定して実行時にロードするものを置く場所であって、 静的に参照を差しておくなら別の場所に置く方が良いでしょう。 Resoucesの中身が大きくなると起動が遅くなる、 という話も聞きますし、良いことがありません。

動機

なんでこんなものを作ったか?

と言っても明らかですよね。アプリ本体の容量を削りたいからこそAssetBundle化している素材が、 アプリ本体に入ってしまう事故を防ぐためです。 アプリの容量が無駄に増えてしまいます。おそらくAssetBundleも別途配信されるので、 同じものが複数入った状態になってしまうのです。

もう一つ、この事故が困るのは、 「後でAssetBundleとして配信しようと思っていたものが、思いがけず先にお客さんの所に届いてしまう」 ということです。1ヶ月後のイベントで配信しようと思っていた新キャラの絵が、 実はアプリに入っていた、なんてことになると、解析されれば一発でバレてしまいます。

実のところ、AssetBundleにするデータはアプリ本体とは別のプロジェクトにしておいた方が無難です。 そうすれば絶対に間違って参照してしまうことはありませんし、 AssetBundleにするデータに用がない開発メンバーにとっては、importやgitから持ってくるための 待ち時間が短くなります。

ただ、スクリプトやシェーダまで入れたシーンやプレハブを丸ごとAssetBundleにしている 所もおありでしょうし、そうなるとプロジェクトを分けるわけには行かなくなります。 やむをえず一つのプロジェクトでやるとなれば、この手の検査ツールは必要になるのではないでしょうか。

実装

メニューのエントリポイント

public class AssetReferenceFinder
{
    [MenuItem("Kayac/AssetRefereneFinder")]
    public static void EntryPoint()
    {
        var instance = new AssetReferenceFinder();
        instance.Process();
    }

メニューに出すためのMenuItem属性 をつけたstatic関数を用意して、 そこで処理するだけです。状態を持つのが楽なように、 インスタンスをnewして関数を呼ぶ形式にしています。

依存関係の取得

実際に依存関係を調べるには、 AssetDatabase.GetDependencies()を使います。 今回は全アセットを調べますから、 直接参照しているアセットだけが見つかれば良く、 再帰検索は不要です。第二引数はfalseにします。

カスタマイズ

今回ご紹介したものは、製品固有のカスタマイズが何も入っていないので、 実際には使いにくいかと思います。

  • 参照されたくないフォルダをコードで指定しておいて選択の必要をなくす
  • Resourcesへの参照は許すからその機能はいらない
  • ガッツリ止まるのは困るので、コルーチンでほどよくyieldして操作不可能にならないようにしたい
  • 外のテキストファイルに吐かれても見辛いので、ウィンドウを作ってそこにリストを出し、項目クリックでそのアセットに飛びたい

等々、いろいろな改造ができるかと思います。

おまけ: 類似の処理

「アセットの依存関係を調べる」という処理は、結構いろんな目的で必要になります。

「どこからも参照がないアセットのリストを作る。さらには自動で消す」 というのは一つで、今回と似たような手続きで実現可能です。 用意しておいて損はないと思います。

また、「あるアセットを参照しているアセットを探したい」 という要求も結構ありますね。 今回は「あるアセット参照しているアセットを列挙したい」 であればGetDependencies一発で終わりですが、向きが逆なので、 今回と同様に全アセットに対してAssetDatabase.GetDependencies()を 呼ぶことになります。 この場合は、先にアセットの参照関係を全部テキストに吐き出してしまう方が良いでしょう。 そうすれば、複数のアセットについて調べたい時に複数回実行する必要がなくなり、高速です。

さらに、「あるアセットを参照しているGameObjectを特定したい」 という欲求もあります。こうなると、GetDependencies()では足りなくて、 自力でprefabやsceneのyaml を解釈することになります。 これも弊社ではやっており、いずれ別の人が文書化してくれるものと思います。