こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • Resourcesフォルダに何を置けばいいのか分からない
  • Resourcesの外に出したらビルドに入らなくなりそうで怖い
  • Resourcesがいつの間にか肥大化している
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    Resourcesフォルダ、つい何でも入れたくなりますよね。

    Resources.Load で文字列から読めるので、とにかく便利です。

    僕も開発中の「勇者のどうぐ屋さん」で、バトル演出のアセット一式がResources配下に置かれているのに気付きました。

    しかもプロジェクトのルールでは、そこに置かない決まりの場所です。

    本記事では、ルール違反の場所からアセットを移した話と、その流れで見えてきた「Resourcesに何を置くべきか」の答えをまとめます。

    アセットを移動するときの落とし穴も2つ紹介します。

    Resourcesに置くのは「パスで読む入口」だけでいい

    先に結論です。

    Resourcesフォルダの中身は、使っていなくても全部ビルドに入ります。

    一方、Resourcesの外にあるアセットも、ビルド対象のシーンやResources内のアセットからGUIDで参照されていれば、依存として自動でビルドに含まれます。

    つまり、Resourcesに必要なのはパス文字列で読む「入口」のアセットだけ。

    入口から参照される中身は、Resourcesの外に置いて大丈夫です。

    この記事のポイント
    • Resources配下は使っていなくても全部ビルドに入る
    • GUIDで参照されているアセットはResources外でもビルドに含まれる
    • Resourcesに残すのはResources.Loadで読む入口だけ
    • 移動はAssetDatabase.MoveAssetでGUIDを保ったまま行う
    • SaveAssetsで無関係なdirtyアセットが混ざらないか差分を確認する

    Resourcesに紛れ込んだエフェクト

    バトル演出をNovaShaderへ移行した区切りで、プロジェクトの中を見直していました。

    すると、エフェクト一式が次の場所に置かれていました。

    Assets/Project/Resources/Battle/Timeline/  
    

    プロジェクトのルールでは、運用アセットは Assets/AssetResources 配下に置きます。

    その中を RawData と Resources に分ける構成です。

    「エフェクトのアセットがいつの間にかAssets/Project/Resources配下になっているけど、これはルール違反」

    見つけたときの僕の正直な感想がこちら。

    犯人は規約の短縮表記

    原因の1つは、規約の構成表にありました。

    構成表で「Resources/」と短縮して書いていたので、どのResourcesフォルダを指すのか曖昧だったのです。

    最近はAIと一緒に開発しています。

    AIは、規約が曖昧なら曖昧な方へ素直に置きます。

    指示どおりに動いた結果なので、AIを責めても仕方ありません。

    そこで構成表をフルパスで書き直し、「エフェクトはAssetResourcesに置く」と明記しました。

    AIに渡す規約ほど、省略せずに書く必要がありますね。

    MoveAssetならGUIDは切れない

    まずは、ルールどおりの場所に移しました。

    移動先は次のフォルダです。

    Assets/AssetResources/Resources/Battle/Timeline/  
    

    移動には AssetDatabase.MoveAsset を使っています。

    エディタのAPI経由で動かすので、GUIDが保たれたまま移動できます。

    シーンやPrefabからの参照も切れません。

    移動元も移動先もResourcesフォルダなので、Resources.Load に渡すパスは変わりません。

    コードの変更はほぼゼロで済みました。

    参照ゼロは機械判定で消す

    ついでに、NovaShader化前に作った試作VFXも整理しました。

    全アセットを対象に、GUIDの被参照数を数えて、参照ゼロだった7ファイルを削除しています。

    目で見て「たぶん使っていない」と判断すると、消してから困ることがあります。

    被参照数を数えて機械的に判定したので、安心して消せました。

    一方、旧スプライト演出の37個は本編でまだ使っています。

    未移行のIDが29個あるので、こちらは残しました。

    本当に必要なのは3つだけ

    移動が終わったあと、移した先のフォルダで Resources.Load されているアセットを調べました。

    結果は、次の3つだけです。

    残りのアセットは、パスで読まれていませんでした。

    これらは、カタログやStageからGUIDで参照されているだけです。

    だとすると、Resourcesに置いておく理由がありません。

    残りはRawDataへ追い出す

    GUID参照だけのアセットを、次の場所へ移しました。

    Assets/AssetResources/RawData/Battle/Timeline  
    

    今回もuLoop経由で AssetDatabase.MoveAsset を呼んでいます。

    gitでは全件が100%リネームとして記録されました。

    あわせて、周辺のツールも追従させています。

    エディタツールのルート定数を、Resources用とRawData用で分けたのがポイントです。

    ツールが新しく生成するアセットも、最初から正しい場所に置かれるようになります。

    外に出してもビルドに入る理由

    ここで「Resourcesの外に出したら、ビルドに入らなくなるのでは?」と不安になりますよね。

    僕も最初は少し身構えました。

    Unityのビルドに入るアセットは、大きく2種類あります。

    今回の構成に当てはめると、次のようになります。

    BattleSequenceCatalog などの入口はResourcesにあるので、ビルドに入ります。

    入口がGUIDで参照しているTimelineやVFXは、依存としてビルドに入ります。

    つまり、入口さえResourcesにあれば、中身はどこに置いても届くわけです。

    Resourcesに必要なのは、パス文字列で読む「入口」だけでした。

    「動くからいい」ではダメな理由

    「ビルドに入るなら、Resourcesに置いたままでも動くのでは?」

    と思うかもしれません。

    実際、動作としては何も困りません。

    それでも僕がRawDataへ出したのは、次の2つが理由です。

    「Resourcesフォルダに無駄にファイルがあると、今後Addressablesに移行したくなった時に困るし、そもそも起動時のルックアップテーブルを作る時間が増えちゃうので良くない」

    起動時間が延びる

    Resources配下のアセットは、パスで引けるようにするためのルックアップの対象になります。

    ファイルが多いほど、アプリ起動時にルックアップテーブルを作る時間が増えてしまい、ユーザーに不利益が生じます。

    パスで読まないアセットまでResourcesフォルダに入れておくことは全く意味がないのです。

    Addressables移行がつらくなる

    将来Addressablesに移行したくなったとき、Resources配下が移行対象になります。

    中身まで全部Resourcesにあると、移行の範囲がそのぶん広がります。

    入口だけに絞っておけば、移行するときに見るべきファイルが少なく済みます。

    Resourcesの注意点は、以前の記事でも触れました。

    👉 初心者が気をつけるべきUnity開発11のポイント

    ついでの落とし穴

    移動作業の中で、2つ引っかかったことがありました。

    アセットを大量に動かすときに起きやすいので、共有しておきます。

    SaveAssetsが無関係な変更を連れてくる

    移動後に AssetDatabase.SaveAssets() を呼んだところ、移動と無関係なMaterialが14件書き出されました。

    エディタ上でdirtyになっていたMaterialです。

    NovaShaderのキーワード補完で、いつの間にかdirtyになっていました。

    SaveAssets は、dirtyなアセットをまとめて書き出します。

    移動だけのつもりでも、別の変更がコミットに紛れ込むわけです。

    そこで git diff -M --summary で、全件が100%リネームになっているかを確認しました。

    混ざった変更は戻しています。

    アセットを移動したあとは、リネーム以外の差分が無いかを必ず見るようにしています。

    「移動で壊した?」は前後比較で切り分け

    移動後、プレビュー動画が全件「古い」と判定されていました。

    「移動で壊した?」と一瞬焦りますよね。

    切り分けのために、移動前のHEADを一時的なworktreeに展開しました。

    そこで同じテストを流したところ、移動前から全件「古い」判定でした。

    原因は、参照元のC#がすでに削除されていたことです。

    今回の変更が原因ではないと分かったので、ハッシュの更新は見送りました。

    更新しても意味が無いからです。

    変更の前後で同じテストを流すと、どちらが原因かを短時間で切り分けられます。

    worktreeを使えば、作業中のブランチを崩さずに済むのも助かるところ。

    まとめ

    Resourcesフォルダに置くのは、Resources.Load でパス指定して読む「入口」だけで十分でした。

    Resourcesは便利なぶん、気付くと中身が増えていきます。

    AIと一緒に開発していると、規約の書き方ひとつで置き場所がずれることもありました。

    一度、Resources配下で Resources.Load されているアセットを数えてみてください。

    入口だけに絞れないか、見直すきっかけになると思います。

    オススメ記事
    検証環境