名古屋でアプリ・VR開発を行っています
ワクワクできるゲームをガンガン開発リリース中!

Inspectorのリスト型の複数情報の表示を色々と改造するエディター拡張

みなさん、こんにちは!SAT-BOXのツバメです。
最近は、新型コロナウイルスをよく耳にしますね。
SAT-BOXの拠点でもある愛知県でもついに感染者が…。
手洗いやうがい、マスクなどをして感染しないようにみなさんも気をつけてください。

さて、今回はUnityのエディター拡張のお話です。
みなさんは、リスト型の変数は使ったことがありますでしょうか?
個人的に使用頻度の高い変数だと思っています。
リストには、構造型を設定することで複数の情報を設定出来ます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    [System.Serializable]
    public struct EnemyData
    {
        [SerializeField, Header("敵の名前")]
        public string enemyName;

        [SerializeField, Header("HP")]
        public int hitPoint;

        [SerializeField, Header("攻撃力")]
        public int attackPoint;

        [SerializeField, Header("防御力")]
        public int deffencePoint;
    }

    [SerializeField, Header("敵のデータ")]
    private List<EnemyData> enemyData = new List<EnemyData>();
}

f:id:sat-box:20200127172152p:plain
このように複数の情報を扱うことが出来ます。
今回はこのInspectorの表示をエディター拡張を用いて色々と改造してみます。

エディター拡張するためには、拡張できるスクリプトが必要なので
早速作っていきます。今回は使い勝手を良くするため[ReorderableList]を使います。
[ReorderableList]が何なのかというと、
f:id:sat-box:20200127173438p:plain
こちらですね。ProjectSettingsのレイヤーの設定でこちらを確認できます。
配列要素の順番などを自由に変更できる便利な機能です。
では、実際に作成していきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;

[CanEditMultipleObjects]
[CustomEditor(typeof(Test))]
public class TestEditor : Editor
{
    /// <summary>
    /// テスト用リスト
    /// </summary>
    private ReorderableList testListRL;

    private void OnEnable()
    {
		#region テスト用リスト

		// Test.csにあるリストを取得
		testListRL = new ReorderableList(
			serializedObject,
			serializedObject.FindProperty("enemyData")
		);

		testListRL.elementHeight = 130;

		testListRL.drawHeaderCallback = (rect) =>
		{
			EditorGUI.LabelField(rect, "敵のデータ");
		};

		testListRL.drawElementCallback = (rect, index, isActive, isFocused) =>
		{
			// callbackが呼び出されるときに渡される index を使って配列の要素を取得
			var element = testListRL.serializedProperty.GetArrayElementAtIndex(index);

			// ラベルを描画
			EditorGUI.PropertyField(rect, element);
		};

		#endregion
	}

	public override void OnInspectorGUI()
    {
        serializedObject.Update();

		testListRL.DoLayoutList();

		serializedObject.ApplyModifiedProperties();
    }
}

f:id:sat-box:20200127174746p:plain
以上のコードで描画したものです。構造体のリストだと、複数の情報が存在しているため
このコードだけでは表示できません。
f:id:sat-box:20200127175033p:plain
情報が一つだけだとこのように描画されます。
では、構造体の情報を表示できるようにコードを書いていきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;

[CanEditMultipleObjects]
[CustomEditor(typeof(Test))]
public class TestEditor : Editor
{
    /// <summary>
	/// テスト用リスト
	/// </summary>
	private ReorderableList testListRL;

    private void OnEnable()
    {
		#region テスト用リスト

		// Test.csにあるリストを取得
		testListRL = new ReorderableList(
			serializedObject,
			serializedObject.FindProperty("enemyData")
		);

		testListRL.elementHeight = 110;

		testListRL.drawHeaderCallback = (rect) =>
		{
			EditorGUI.LabelField(rect, "敵のデータ");
		};

		testListRL.drawElementCallback = (rect, index, isActive, isFocused) =>
		{
			// callbackが呼び出されるときに渡される index を使って配列の要素を取得
			var element = testListRL.serializedProperty.GetArrayElementAtIndex(index);

			// ラベルを描画
			EditorGUI.PropertyField(rect, element);
		};

		#endregion
	}

	public override void OnInspectorGUI()
    {
        serializedObject.Update();

		testListRL.DoLayoutList();

		serializedObject.ApplyModifiedProperties();
    }

	[CustomPropertyDrawer(typeof(Test.EnemyData))]
	public class EnemyDataDrawer : PropertyDrawer
	{
		public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
		{
			using (new EditorGUI.PropertyScope(position, label, property))
			{
				// 要素番号ごとに区切るラベル
				EditorGUI.LabelField(position, label.text + " -----------------------------------------------------------------");

				position.y += EditorGUIUtility.singleLineHeight;
				position.height = EditorGUIUtility.singleLineHeight;
				position.width = 120;

				// propertyから各要素を取得(Serialized)
				var name = property.FindPropertyRelative("enemyName");
				var hp = property.FindPropertyRelative("hitPoint");
				var atk = property.FindPropertyRelative("attackPoint");
				var def = property.FindPropertyRelative("deffencePoint");

				#region 敵の名前のRect

				var nameLabelRect = new Rect(position)
				{
					y = position.y + EditorGUIUtility.singleLineHeight
				};

				var nameRect = new Rect(position)
				{
					x = position.x + 60,
					y = position.y + EditorGUIUtility.singleLineHeight
				};

				#endregion

				#region HPのRect

				var hpLabelRect = new Rect(position)
				{
					y = position.y + EditorGUIUtility.singleLineHeight * 2
				};

				var hpRect = new Rect(position)
				{
					x = position.x + 60,
					y = position.y + EditorGUIUtility.singleLineHeight * 2
				};

				#endregion

				#region 攻撃力のRect

				var atkLabelRect = new Rect(position)
				{
					y = position.y + EditorGUIUtility.singleLineHeight * 3
				};

				var atkRect = new Rect(position)
				{
					x = position.x + 60,
					y = position.y + EditorGUIUtility.singleLineHeight * 3
				};

				#endregion

				#region 防御力のRect

				var defLabelRect = new Rect(position)
				{
					y = position.y + EditorGUIUtility.singleLineHeight * 4
				};

				var defRect = new Rect(position)
				{
					x = position.x + 60,
					y = position.y + EditorGUIUtility.singleLineHeight * 4
				};

				#endregion

				GUI.Label(nameLabelRect, "名前");

				// 敵の名前のフィールドを表示
				name.stringValue = EditorGUI.TextField(nameRect, name.stringValue);

				GUI.Label(hpLabelRect, "HP");

				// HPのフィールドを表示
				hp.intValue = EditorGUI.IntField(hpRect, hp.intValue);

				GUI.Label(atkLabelRect, "攻撃力");

				// 攻撃力のフィールドを表示
				atk.intValue = EditorGUI.IntField(atkRect, atk.intValue);

				GUI.Label(defLabelRect, "防御力");

				// 防御力のフィールドを表示
				def.intValue = EditorGUI.IntField(defRect, def.intValue);
			}
		}
	}
}

f:id:sat-box:20200127180600p:plain
完成しました!上記のコードで重要なのが、

[CustomPropertyDrawer(typeof(Test.EnemyData))]
public class EnemyDataDrawer : PropertyDrawer

この部分です!この部分で指定の構造体の描画を変更出来るようにしています。
基本的にはRect型で座標などを調整して、フィールドを描画しています。
現状は少ない情報量ですが、これが増えてくるとリストから描画がはみ出てしまうので、

private void OnEnable()
{
#region テスト用リスト

// Test.csにあるリストを取得
testListRL = new ReorderableList(
	serializedObject,
	serializedObject.FindProperty("enemyData")
);

testListRL.elementHeight = 110; <- ここでリストの描画範囲を増やす

testListRL.elementHeightで逐一描画範囲を増やさないといけないので注意してください。

もっと改造すれば画像を表示したりと色々なことが出来ますので、
みなさんも試してみてください!以上、ツバメでした。