インスペクターでインターフェースを割り当てる方法
~ 汎用エディターコードを活用したチュートリアル ~
1. はじめに
Unity の標準シリアライズでは、インターフェースや抽象クラスのフィールドは直接扱えません。
しかし、[SerializeReference] 属性を利用することで、これらのフィールドもシリアライズ可能となり、柔軟なポリモーフィズムが実現できます。
本チュートリアルでは、ハイスコア保存機能を例に、複数のクラスで共通に利用できる汎用エディターコードを作成し、インスペクター上でインターフェースの実装クラスを動的に割り当てる方法を解説します。
2. システム構成
本チュートリアルは、以下の 3 つのパートで構成されています。
- インターフェースと実装クラスの作成
IHighScoreSaver
インターフェース- 具体的な実装クラス(例:
FileHighScoreSaver
、PlayerPrefsHighScoreSaver
)
- HiScoreManager の作成
- [SerializeReference] 属性を用いてインターフェースフィールドをシリアライズ
- ハイスコア保存処理を実装
- 汎用エディターコードの作成と適用
- ジェネリックなカスタムエディター基底クラスを作成
- 対象クラス(例:HiScoreManager)でフィールド名とインターフェース型を指定するだけで利用可能に
3. ステップバイステップ実装
3.1 インターフェースと実装クラスの作成
まず、ハイスコア保存のためのインターフェースとその実装クラスを作成します。
各実装クラスには [Serializable]
属性を付与して、[SerializeReference] 対象にします。
// IHighScoreSaver.cs
using System;
public interface IHighScoreSaver
{
void SaveHighScore(int score);
}
// FileHighScoreSaver.cs
using System;
using UnityEngine;
[Serializable]
public class FileHighScoreSaver : IHighScoreSaver
{
public void SaveHighScore(int score)
{
Debug.Log("Score saved to file: " + score);
}
}
// PlayerPrefsHighScoreSaver.cs
using System;
using UnityEngine;
[Serializable]
public class PlayerPrefsHighScoreSaver : IHighScoreSaver
{
public void SaveHighScore(int score)
{
Debug.Log("Score saved to PlayerPrefs: " + score);
}
}
3.2 HiScoreManager の作成
次に、[SerializeReference] 属性を利用して、インターフェース型のフィールドをシリアライズする HiScoreManager クラスを作成します。
// HiScoreManager.cs
using UnityEngine;
public class HiScoreManager : MonoBehaviour
{
[SerializeReference]
public IHighScoreSaver highScoreSaver;
public int score = 100;
// ハイスコア保存を実行するメソッド
public void SaveScore()
{
if (highScoreSaver != null)
{
highScoreSaver.SaveHighScore(score);
}
else
{
Debug.LogWarning("Saver が設定されていません。");
}
}
}
3.3 汎用エディターコードの作成
次に、汎用的なカスタムエディター基底クラスを作成します。
このクラスを継承することで、対象クラスのフィールド名とインターフェース型を指定するだけで、インスペクター上に実装クラスの追加・削除 UI を自動生成できます。
using UnityEngine;
using UnityEditor;
using System;
using System.Linq;
using System.Reflection;
// 汎用エディター基底クラス
public abstract class GenericInterfaceEditor<T> : Editor where T : MonoBehaviour
{
// 派生クラスで対象のフィールド名を指定する
protected abstract string InterfaceFieldName { get; }
// 派生クラスで対象のインターフェース型を指定する
protected abstract Type InterfaceType { get; }
public override void OnInspectorGUI()
{
// デフォルトのインスペクター描画
DrawDefaultInspector();
T targetObject = (T)target;
// 対象のフィールド情報を取得(public/private 両方)
FieldInfo field = typeof(T).GetField(InterfaceFieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null)
{
EditorGUILayout.HelpBox($"フィールド '{InterfaceFieldName}' が見つかりません。", MessageType.Error);
return;
}
object fieldValue = field.GetValue(targetObject);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Interface Assignment", EditorStyles.boldLabel);
if (fieldValue == null)
{
EditorGUILayout.HelpBox("実装クラスが割り当てられていません。", MessageType.Info);
if (GUILayout.Button("Add Implementation"))
{
GenericMenu menu = CreateGenericMenu(InterfaceType, selectedInstance =>
{
field.SetValue(targetObject, selectedInstance);
EditorUtility.SetDirty(targetObject);
});
menu.ShowAsContext();
}
}
else
{
EditorGUILayout.LabelField("Current Implementation:", fieldValue.GetType().Name);
if (GUILayout.Button("Remove Implementation"))
{
field.SetValue(targetObject, null);
EditorUtility.SetDirty(targetObject);
}
}
}
// 指定したインターフェースを実装している具象クラスの一覧を GenericMenu として生成
private GenericMenu CreateGenericMenu(Type interfaceType, Action<object> onSelect)
{
GenericMenu menu = new GenericMenu();
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => interfaceType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
.OrderBy(type => type.Name);
foreach (var type in types)
{
menu.AddItem(new GUIContent(type.Name), false, () => onSelect(Activator.CreateInstance(type)));
}
return menu;
}
}
3.4 HiScoreManager 用のカスタムエディター作成
上記の汎用エディター基底クラスを継承して、HiScoreManager に対するカスタムエディターを作成します。
ここでは、対象フィールド名とインターフェース型を指定するだけです。
using UnityEngine;
using UnityEditor;
using System;
[CustomEditor(typeof(HighScoreManager))]
public class HiScoreManagerEditor : GenericInterfaceEditor<HighScoreManager>
{
// HiScoreManager の highScoreSaver フィールドを対象とする
protected override string InterfaceFieldName => "highScoreSaver";
// 対象のインターフェース型を指定する(例:IHighScoreSaver)
protected override Type InterfaceType => typeof(IHighScoreSaver);
}
4. 使用方法
4.1 スクリプトの配置
- Runtime 用スクリプト
以下のファイルをAssets/Scripts/
などのフォルダに配置します。IHighScoreSaver.cs
FileHighScoreSaver.cs
PlayerPrefsHighScoreSaver.cs
HiScoreManager.cs
- エディター用スクリプト
以下のファイルをAssets/Editor/
フォルダに配置します。GenericInterfaceEditor.cs
HiScoreManagerEditor.cs
4.2 シーンへの配置と操作
- シーンにオブジェクト配置
空の GameObject を作成し、HiScoreManager コンポーネントを追加します。 - インスペクターで実装の割り当て
- HiScoreManager のインスペクターに「Interface Assignment」セクションが表示されます。
- 「Add Implementation」ボタンをクリックすると、利用可能な Saver クラス(例:FileHighScoreSaver、PlayerPrefsHighScoreSaver)の一覧がポップアップ表示されます。
- 任意の Saver を選択すると、そのインスタンスが生成され、
highScoreSaver
フィールドに割り当てられます。 - 割り当て済みの場合は、現在の実装クラス名が表示され、「Remove Implementation」ボタンで解除できます。
- 動作確認
ゲーム実行中に HiScoreManager のSaveScore()
メソッドを呼び出すことで、設定された Saver によるハイスコア保存処理が実行され、 Console にログが表示されます。
5. まとめ
本チュートリアルでは、汎用エディターコードを活用して、[SerializeReference] 属性でシリアライズされたインターフェースフィールドに対し、
インスペクター上で実装クラスを動的に割り当てる方法を解説しました。
- 共通処理の再利用: 汎用エディター基底クラスにより、複数のクラスで同じ実装割り当て処理を利用可能
- リフレクションの活用: 利用可能な具象クラスを動的に取得し、ユーザーに選択させる仕組みを実現
- 実用例の確認: HiScoreManager を例に、シーンへの配置から実行時の保存処理までを詳細に解説
この手法を活用することで、柔軟なポリモーフィズムやプラグイン形式の拡張が容易となり、プロジェクト全体のコードの再利用性が向上します。
ディスカッション
コメント一覧
まだ、コメントがありません