⛳
【Unity】Resourcesの使い方と落とし穴【非推奨】
使い方と落とし穴シリーズ一覧
非推奨な理由
This chapter discusses the Resources system.
(中略)
3.1. Best Practices for the Resources System
Don't use it.
Unity - Assets, Resources and AssetBundles
- このように公式から、Resources は使わないのが一番よいと明言されている
- 理由は
- 細かいメモリ管理が難しい
- アプリの起動時間, ビルド時間が増加する
- プラットフォームごとのリソース選択ができない
非推奨でもこの記事を書く理由
- 個人的に使う機会がなんやかんやありそうなため
-
例:unityroom は直接 Addressables に対応していない (2025/06/04時点)
- 外部サーバーを介したリソース読込など、工夫次第では使用可能だが…
- unity1week の場合、そこでリソースを割く(
Resourcesだけに)のは避けたい
Resourcesを使用するケース
-
簡単にリソース読込機能を実装したい場合
- AssetBundle, Addressables よりも手軽
Resourcesの使い方
全体の流れ
- 以降、各工程に対応した使い方を解説する
Resources フォルダ格納
-
Assets/Resources
以下にリソースを配置する-
Resources
フォルダが無い場合、同名フォルダを自分で作成すればok
-
- サブフォルダは特に制限ナシ
リソース読込、利用
- ファイルパスを指定して読込を行う
- パスに
Assets/Resources
を含める必要はない
- パスに
🔧 Load
string assetPath = "Prefabs/Enemy/Slime";
var slimeObj = Resources.Load<GameObject>(assetPath);
🔧 LoadAll
- 指定フォルダ下のアセットを全て読み込む
- アセットファイルを指定した場合、アセット単体を配列で返す
string folderPath = "Prefabs";
GameObject[] prefabs = Resources.LoadAll<GameObject>(folderPath);
🔧 LoadAsync
- 非同期で読み込む
- サイズが大きく、処理落ちが起きてしまうようなリソースに有効
string assetPath = "Sounds/TitleBGM";
//UniTask 版 (async, await)
var request = Resources.LoadAsync<AudioClip>(assetPath);
await request; //完了を待機
AudioClip bgm = request.asset as AudioClip; //取り出し
AudioSource.PlayClipAtPoint(bgm, Vector3.zero);
//Coroutine 版
IEnumerator LoadBgmAsync()
{
var req = Resources.LoadAsync<AudioClip>(assetPath);
yield return req;
var clip = req.asset as AudioClip;
audioSource.clip = clip;
audioSource.Play();
}
- 進捗は
request.progress
(0〜1) で取得できるのでロードバーに利用可
不要リソースの解放
🔧 UnloadAsset
- 解放できるのはアセット本体のみ
-
Instantiate
で生成したインスタンスを渡しても何も起きない - 同アセットを複数回読み込んでいた場合、最後の参照が切れたタイミングで解放される
//読み込んだ Prefab を明示的に解放
var slimePrefab = Resources.Load<GameObject>("Prefabs/Enemy/Slime");
Resources.UnloadAsset(slimePrefab); //Prefab 自体のメモリを解放
slimePrefab = null;
🔧 UnloadUnusedAssets
- 参照が0になったアセットをまとめて解放する
- シーン遷移などで有効
await Resources.UnloadUnusedAssets(); //UniTask 版
yield return Resources.UnloadUnusedAssets(); //Coroutine 版
その他
🔧 FindObjectsOfTypeAll
- シーン内だけでなく、プロジェクトにロード済みの全オブジェクトを取得
- 非アクティブ, DontDestroyOnLoad など
- 読込や解放の状況を確認するのに有効
#if UNITY_EDITOR
//例1:現在メモリに乗っている Texture2D を全部列挙
Texture2D[] loadedTex = Resources.FindObjectsOfTypeAll<Texture2D>();
Debug.Log($"Loaded Texture2D : {loadedTex.Length} 枚");
//例2:シーンに存在しない不要 GameObject を洗い出す
var allObjs = Resources.FindObjectsOfTypeAll<GameObject>();
foreach (var go in allObjs)
{
// Inspector で選択できるよう ping して確認
if (!EditorUtility.IsPersistent(go) &&
go.hideFlags == HideFlags.None)
{
Debug.Log($"シーン外でロード中の GO → {go.name}", go);
}
}
#endif
オイラはこんな落とし穴に出会った
ビルドサイズ肥大化
-
Assets/Resources
フォルダ下のアセットは使用の有無に関わらずビルドに含まれる
✅ 回避策
- Resources を使わない
- 未使用アセットを自動消去してくれるツールの導入もアリ
パスのタイプミス
- 手入力ではミスが多い & 修正が面倒
- 同様にパス(アドレス)を指定する Addressable も同様
string path = "Prefab/Enemy/Slime"; //実際は Prefabs/Enemy/Slime
var slimeObj = Resources.Load<GameObject>(path); //失敗
✅ 回避策
- 定数管理、列挙型 + 拡張メソッド の活用
定数管理
public static class ResourcePath
{
public const string EnemySlime = "Prefabs/Enemy/Slime";
public const string EnemyDragon = "Prefabs/Enemy/Dragon";
public const string TitleBgm = "Sounds/TitleBGM";
}
var slime = Resources.Load<GameObject>(ResourcePath.EnemySlime);
列挙型 + 拡張メソッド
public enum PrefabPath
{
EnemySlime,
EnemyDragon
}
public static class PrefabPathExt
{
//Enum → 文字列パス変換
private static readonly Dictionary<PrefabPath, string> map = new()
{
{ PrefabPath.EnemySlime, "Prefabs/Enemy/Slime" },
{ PrefabPath.EnemyDragon, "Prefabs/Enemy/Dragon" }
};
public static string Path(this PrefabPath p) => map[p];
}
var dragon = Resources.Load<GameObject>(PrefabPath.EnemyDragon.Path());
Discussion