Отображение поля ScriptableObject в Custom Inspector’е некоторого компонента

Предположим, у нас есть некоторый класс унаследованный от MonoBehaviour, назовём его SODataBehaviour.

using UnityEngine;

namespace Triks.SO {
	public class SODataBehaviour : MonoBehaviour {

		/// <summary>
		/// Экземпляр класса SOData
		/// </summary>
		[SerializeField, Tooltip("Экземпляр класса SOData")]
		private SOData scriptableObjectData;

		/// <summary>
		/// Поле типа int
		/// </summary>
		[SerializeField, Tooltip("Поле типа int")]
		private int someInt = 5;
	}
}

В нём есть поле, тип которого — класс унаследованный от ScriptableObject, его мы назовём SOData.

using UnityEngine;

namespace Triks.SO {

	/// <summary>
    /// Класс характеризующий 
    /// </summary>
	[CreateAssetMenu(menuName = "Triks/SO Data")]
	public class SOData : ScriptableObject {
		
		/// <summary>
        /// Скорость передвижения
        /// </summary>
		[SerializeField, Range(1, 15), Tooltip("Скорость передвижения")]
		private float speed = 1;

		/// <summary>
        /// Возвращает скорость передвижения
        /// </summary>
		public float Speed { 
			get { return speed; }
		}

		/// <summary>
        /// Урон наносимый от контакта
        /// </summary>
		[SerializeField, Range(0.1f, 50), Tooltip("Урон наносимый от контакта")]
		private float contactDamage = 10;

		/// <summary>
        /// Возвращает урон наносимый от контакта
        /// </summary>
		public float ContactDamage {
			get { return contactDamage; }
		}
		
		/// <summary>
        /// Описание данных
        /// </summary>
		[SerializeField, Tooltip("Описание данных")]
		private string description;

		/// <summary>
        /// Возвращает описание данных
        /// </summary>
		public string Description {
			get { return description; }
		}
	}
}

По умолчанию в инспекторе это будет выглядеть так:

Неплохо было бы видеть поля SOData непосредственно в инспекторе SODataBehaviour? Например так:

Для нам придётся написать свой Custom Inspector с Блэкджеком и куртизанками. Вот как то так:

using UnityEngine;
using Triks.SO;
using UnityEditor;

namespace Tricks.SOEditor {
    /// <summary>
    /// Кастомный инспектор для SODataBehaviour с использованием DrawDefaultInspector()
    /// </summary>
    [CanEditMultipleObjects]
    [CustomEditor(typeof(SODataBehaviour))]
    public class SODataBehaviourEditor : Editor {

        private SerializedProperty scriptableObjectData;
        private Editor SODataEditor;

        void OnEnable() {
            scriptableObjectData = serializedObject.FindProperty("scriptableObjectData");
        
            ResetMonsterPropertyEditor();
        }

        /// <summary>
        /// Сброс Editor'а scriptableObjectData
        /// </summary>
        private void ResetMonsterPropertyEditor() {
            SODataEditor = Editor.CreateEditor(scriptableObjectData.objectReferenceValue); 
        }

        public override void OnInspectorGUI() {
                        
            serializedObject.Update();   
            EditorGUI.BeginChangeCheck();
            DrawDefaultInspector();
            if (EditorGUI.EndChangeCheck()) { 
                serializedObject.ApplyModifiedProperties(); // поскольку изменили значение PropertyField (scriptableObjectData) 
                                                            // нужно применить изменения и только после этого
                                                            // перезагрузить Editor scriptableObjectData
                ResetMonsterPropertyEditor();
            }
            
            // Рисуем SODataEditor
            if (SODataEditor != null) {
                GUILayout.BeginVertical(EditorStyles.helpBox);
                EditorGUI.BeginDisabledGroup(true);
                SODataEditor.OnInspectorGUI(); 
                EditorGUI.EndDisabledGroup(); 
                EditorGUILayout.EndVertical();
            } else {
                EditorGUILayout.Separator();
                EditorGUILayout.HelpBox("SOData не должен быть пустым!", MessageType.Error); 
            }

            serializedObject.ApplyModifiedProperties();

        }
    }
}

В этом варианте мы используем DrawDefaultInspector для отображения инспектора по умолчанию, после чего рисуем, либо ошибку-предупреждение о неприемлемости оставления поля SOData пустым, либо инспектор соответствующий этому полю.

Здесь используются EditorGUI.BeginChangeCheck для проверки изменения каких либо полей SODataBehaviour, нужно это для того, что бы при изменении поля SOData (например перетащив в него некоторый экземпляр) мы сразу же увидели инспектор экземпляра SOData с полями ему соответствующими.

А что если нужно не только добавить к стандартному инспектору инспектор SOData, но и переделать сам инспектор SODataBehaviour? В этом случае есть интересная загвоздка с тем как отобразить вот этот элемент

не используя DrawDefaultInspector. А вот сам способ которым можно этого добиться:

using UnityEngine;
using Triks.SO;
using UnityEditor;

namespace Tricks.SOEditor {
    /// <summary>
    /// Кастомный инспектор для SODataBehaviour без использования DrawDefaultInspector()
    /// </summary>
    [CanEditMultipleObjects]
    [CustomEditor(typeof(SODataBehaviour))]
    public class SODataBehaviourEditor : Editor {
        private SODataBehaviour componentWithSODataTarget;
        private SerializedProperty scriptableObjectData;
        private Editor SODataEditor;
        private MonoScript SODataScript;
        private SerializedProperty someInt;

        void OnEnable() {
            componentWithSODataTarget = (SODataBehaviour)target;

            someInt = serializedObject.FindProperty("someInt");

            SODataScript = MonoScript.FromMonoBehaviour(componentWithSODataTarget);

            scriptableObjectData = serializedObject.FindProperty("scriptableObjectData");
        
            ResetMonsterPropertyEditor();
        }

        /// <summary>
        /// Сброс Editor'а scriptableObjectData
        /// </summary>
        private void ResetMonsterPropertyEditor() {
            SODataEditor = Editor.CreateEditor(scriptableObjectData.objectReferenceValue); 
        }

        public override void OnInspectorGUI() {
                        
            serializedObject.Update();   
            
            EditorGUI.BeginDisabledGroup(true);
            EditorGUILayout.ObjectField("Script:", SODataScript, typeof(MonoScript), false);        
            EditorGUI.EndDisabledGroup();

            EditorGUI.BeginChangeCheck(); 
            EditorGUILayout.PropertyField(scriptableObjectData);
            if (EditorGUI.EndChangeCheck()) { 
                serializedObject.ApplyModifiedProperties(); // поскольку изменили значение PropertyField (scriptableObjectData) 
                                                            // нужно применить изменения и только после этого
                                                            // перезагрузить Editor scriptableObjectData
                ResetMonsterPropertyEditor();
            }
            
            // Рисуем SODataEditor
            if (SODataEditor != null) {
                GUILayout.BeginVertical(EditorStyles.helpBox);
                EditorGUI.BeginDisabledGroup(true);
                SODataEditor.OnInspectorGUI(); 
                EditorGUI.EndDisabledGroup(); 
                EditorGUILayout.EndVertical();
            } else {
                EditorGUILayout.Separator();
                EditorGUILayout.HelpBox("SOData не должен быть пустым!", MessageType.Error); 
            }

            EditorGUILayout.LabelField("-----------");
            EditorGUILayout.PropertyField(someInt); 
            EditorGUILayout.LabelField("-----------");

            serializedObject.ApplyModifiedProperties();
        }
    }
}

Вот что в итоге получилось:

На этом всё, приятного использования компонентов с полями ScriptableObject.

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Капча * Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.