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