2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-01-22

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

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

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

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

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

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

使用例

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を手動で設定する必要が無くなる工夫もしています。


using UnityEditor;

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

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

自動生成されるPrefabキー

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 : 同じフィールドに複数回適用することはできない。

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のパスと名前を取得し、コードを自動生成します。それによって自分で登録しなくてもバインドできるようにしています。

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など)との相性が良く、プロジェクトの拡張性・保守性が向上します。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?