SerializeFieldを使用せずにPrefabをインスタンス化してみた

## UnityでSerializeFieldを使わずにPrefabをインスタンス化する方法

Unity開発でPrefabを参照する際、`[SerializeField]`を使うと`MonoBehaviour`の継承が必要となり、コードのみでPrefab参照を取得すること難しくなることがあります。今回はUIManagerを作成する際にコード上のみで参照できたら便利になるなと思い拡張機能を作成しました。

通常、`SerializeField`を利用すると以下のようなコードになります。
この方法では、Prefabの参照をインスペクターで設定する必要があります。

```csharp
public class ExampleUsage : MonoBehaviour
{
    [SerializeField] private GameObject _playerPrefab;

    private void Awake()
    {
        Instantiate(_playerPrefab);
    }
}
```

今回作成した拡張機能は以下のようにして使用します。
`PrefabReference`属性と`PrefabLoader`クラスを利用することで、**MonoBehaviourに依存せず**、コード上でPrefabの参照が可能になります。

#### 使用例
```csharp
using UnityEngine;

namespace HikanyanLaboratory.UI
{
    public class ExampleUsage
    {
        [PrefabReference(PrefabKeys.Player)]
        private GameObject _playerPrefab;

        public ExampleUsage()
        {
            _playerPrefab = PrefabLoader.GetPrefab(PrefabKeys.Player);
            Debug.Log(_playerPrefab.name);
            Object.Instantiate(_playerPrefab);
        }
    }
}
```

このように、`MonoBehaviour`を継承せずにPrefabを参照・インスタンス化できることで、**MVPパターン**や**DI**(**依存性注入**)との親和性が高まり、より柔軟な設計が可能になります。



`PrefabKeys`クラスはエディタが保存されたタイミングで生成されます。
`InitializeOnLoad`属性を使用することで、スクリプトコンパイル後呼び出されるようになり、`PrefabKeys`を手動で設定する必要が無くなる工夫もしています。

```csharp

using UnityEditor;

namespace HikanyanLaboratory.UI
{
    [InitializeOnLoad]
    public class PrefabBinderInitializer
    {
        static PrefabBinderInitializer()
        {
            PrefabLoader.Initialize();
        }
    }
}

```


`_prefabPathDictionary`はプレハブの名前とパスを保存しています。
` public const string Player = "Player";`の部分で`[PrefabReference(PrefabKeys.Player)]`のように使えるようにします。


#### 自動生成されるPrefabキー
```csharp
using System.Collections.Generic;
public static class PrefabKeys
{
    private static readonly Dictionary<string, string> PrefabPathDictionary = new Dictionary<string, string>()
    {
        { SampleList, "Assets/HikanyanLaboratory/UISystem/Resources/Player.prefab" },
    };

    public const string Player = "Player";
    public static IEnumerable<string> GetAllKeys()
    {
        return PrefabPathDictionary.Keys;
    }
}
```

### `Attribute`を使用してカスタム属性を定義

特定のフィールドに適用できるカスタム属性（アトリビュート）を定義しています。この属性は、Prefabに関連付けるためのキーを指定する目的で使用しました。

#### `AttributeUsage`の指定

`AttributeTargets.Field` : このカスタム属性はフィールドにだけ適用できる。
`AllowMultiple = false`  : 同じフィールドに複数回適用することはできない。

```csharp
using System;
using UnityEngine;

namespace HikanyanLaboratory.UI
{
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    public class PrefabReferenceAttribute : PropertyAttribute
    {
        public string PrefabKey { get; private set; }

        public PrefabReferenceAttribute(string prefabKey)
        {
            PrefabKey = prefabKey;
        }
    }
}

```


### PrefabLoaderの詳細
`PrefabLoader`はプロジェクト内のPrefabを自動で探索し、管理・取得できる仕組みです。
Assetの中のResourcesフォルダを探索し、Prefabのパスと名前を取得し、コードを自動生成します。それによって自分で登録しなくてもバインドできるようにしています。


```csharp
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

namespace HikanyanLaboratory.UI
{
    public static class PrefabLoader
    {
        private static readonly Dictionary<string, GameObject> PrefabDictionary = new Dictionary<string, GameObject>();
        private static readonly Dictionary<string, string> PrefabPathDictionary = new Dictionary<string, string>();

        /// <summary>
        /// 指定されたディレクトリからプレハブを読み込み、プレハブの変数名とパスをディクショナリに追加します。
        /// </summary>
        public static void Initialize(string searchPath = "Assets")
        {
            PrefabDictionary.Clear();
            PrefabPathDictionary.Clear();

            List<string> resourcePaths = new List<string>();
            FindResourceDirectories(searchPath, resourcePaths);

            foreach (string path in resourcePaths)
            {
                string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { path });

                foreach (string guid in prefabGuids)
                {
                    string prefabPath = AssetDatabase.GUIDToAssetPath(guid);
                    GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
                    string variableName = GenerateVariableName(prefab.name);

                    if (PrefabDictionary.TryAdd(variableName, prefab))
                    {
                        PrefabPathDictionary.Add(variableName, prefabPath);
                    }
                    else
                    {
                        Debug.LogWarning($"Duplicate prefab name detected: {variableName}");
                    }
                }
            }

            GeneratePrefabKeysClass();
        }

        /// <summary>
        /// 指定されたディレクトリからリソースディレクトリを見つけ、リソースディレクトリのパスをリストに追加します。
        /// </summary>
        private static void FindResourceDirectories(string path, List<string> resourcePaths)
        {
            foreach (string directory in Directory.GetDirectories(path))
            {
                if (Path.GetFileName(directory) == "Resources")
                {
                    resourcePaths.Add(directory);
                }

                FindResourceDirectories(directory, resourcePaths);
            }
        }

        /// <summary>
        /// プレハブの変数名とパスを使って、PrefabKeysクラスを生成します。
        /// </summary>
        private static void GeneratePrefabKeysClass()
        {
            string classContent = "using System.Collections.Generic;\n";
            classContent += "public static class PrefabKeys\n{\n";
            classContent +=
                "    private static readonly Dictionary<string, string> PrefabPathDictionary = new Dictionary<string, string>()\n    {\n";

            foreach (var entry in PrefabPathDictionary)
            {
                classContent += $"        {{ {entry.Key}, \"{entry.Value}\" }},\n";
            }

            classContent += "    };\n\n";

            foreach (var entry in PrefabDictionary)
            {
                classContent += $"    public const string {entry.Key} = \"{entry.Key}\";\n";
            }

            classContent +=
                "    public static IEnumerable<string> GetAllKeys()\n    {\n        return PrefabPathDictionary.Keys;\n    }\n";

            classContent += "}";

            File.WriteAllText("Assets/HikanyanLaboratory/UISystem/Script/PrefabProperty/PrefabKeys.cs", classContent);
            AssetDatabase.Refresh();
        }

        /// <summary>
        /// 指定されたキーに対応するプレハブを取得します。
        /// </summary>
        public static GameObject GetPrefab(string key)
        {
            if (PrefabDictionary.TryGetValue(key, out GameObject prefab))
            {
                return prefab;
            }

            Debug.LogError($"Prefab with key '{key}' not found.");
            return null;
        }

        /// <summary>
        /// プレハブ名から変数名を生成します。
        /// </summary>
        private static string GenerateVariableName(string prefabName)
        {
            return prefabName.Replace(" ", "_").Replace("-", "_");
        }
    }
}
```


### まとめ
この仕組みにより、`MonoBehaviour`に依存せず、柔軟にPrefabを管理できます。特に、**MVPパターン**や**依存性注入**(**DI**など)との相性が良く、プロジェクトの拡張性・保守性が向上します。