Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Custom editor unable to access Serializable object

Discussion in 'Immediate Mode GUI (IMGUI)' started by Reahreic, Apr 7, 2020.

  1. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    I've been fighting with custom editors for a while now and am unable to gain access to one of my serialized properties regardless of if i use FindProperty, FindPropertyRelative, or new SerializedObject(elm.objectReferenceValue)

    The property resides within a monobehabiour, and is a custom class which is marked [Serializable] and does not extend from monobehaviour, which should lend for the new SerializedObject() workaround, but the objectReferenceValue is always null.

    I'm using Unity 2019.2.11f1, but find it super tedious to style the editor using the half implemented, quarter documented UIElements framework. (I've got a decade of HTML & CSS experience and UIE's custom css is... that's a rant for another time.)

    The whole thing is quite large so I've stripped as much as i can to try simplify debugging.

    Code (CSharp):
    1.  
    2. //The class with the custom Editor that's having issues
    3. [Serializable, DisallowMultipleComponent]
    4. public class PM : MonoBehaviour {
    5.     [CustomEditorName("T C C")]
    6.     public List<TC> lstTcc = new List<TC>();
    7. }
    8.  
    9. //The custom editor
    10. [CustomEditor(typeof(PM))]
    11. public class PMEditor : Editor {
    12.     SerializedProperty prop_tcc;
    13.     ReorderableList topLevel;
    14.  
    15.     private void OnEnable() {
    16.         topLevel = GenerateReorderableList(serializedObject);
    17.     }
    18.  
    19.     public override void OnInspectorGUI() {
    20.         serializedObject.Update();
    21.  
    22.         topLevel.DoLayoutList();
    23.  
    24.         serializedObject.ApplyModifiedProperties();
    25.  
    26.         //EditorUtility.SetDirty(target);
    27.     }
    28.  
    29.     private ReorderableList GenerateReorderableList(SerializedObject serializedObject) {
    30.         prop_content = serializedObject.FindProperty("ltsTcc");
    31.  
    32.         return new ReorderableList(serializedObject, prop_content) {
    33.             displayAdd = true,
    34.             displayRemove = true,
    35.             draggable = true,
    36.  
    37.             drawHeaderCallback = rect => {
    38.                 FieldInfo field_tcc = typeof(PM).GetField("lstTcc");
    39.                 CustomEditorNameAttribute attribute = Attribute.GetCustomAttribute(field_tcc, typeof(CustomEditorNameAttribute)) as CustomEditorNameAttribute;
    40.                 EditorGUI.LabelField(rect, attribute.NewName);
    41.             },
    42.  
    43.             drawElementCallback = (rect, index, a, h) => {
    44.                 SerializedProperty element = prop_content.GetArrayElementAtIndex(index);
    45.  
    46.                 //Always null
    47.                 SerializedProperty prop_entity = element.FindPropertyRelative("_entity");
    48.  
    49.                 //element.objectReferenceValue -> always null
    50.                 SerializedObject propObj = new SerializedObject(element.objectReferenceValue);
    51.                 SerializedProperty prop_entity = propObj.FindProperty("_entity");
    52.  
    53.                 EditorGUILayout.PropertyField(prop_entity, GUIContent.none);
    54.  
    55.                 //Configure more
    56.             },
    57.  
    58.             elementHeightCallback = index => {
    59.                 //Manually set untill null issues resolved
    60.                 return 20;
    61.             }
    62.         };
    63.     }
    64. }
    65.  
    66.  
    67.  
    68. //Supporting class objects
    69. [Serializable, ExecuteInEditMode, DisallowMultipleComponent, AddComponentMenu("")]
    70. public class TC : MonoBehaviour, ISerializationCallbackReceiver {
    71.     [SerializeField]
    72.     private TE _entity = null;
    73.     public TE entity {
    74.         get {
    75.             return _entity;
    76.         }
    77.         private set {
    78.             if (_entity == null) {
    79.                 _entity = value;
    80.             }
    81.         }
    82.     }
    83.  
    84.     //Other removed properties here
    85.  
    86.     /* Serialization Callbacks */
    87. #if UNITY_EDITOR
    88.     protected void Awake() {
    89.         if (entity == null) {
    90.             entity = new TC(gameObject);
    91.         }
    92.     }
    93.     protected void OnValidate() {
    94.         if (entity != null) {
    95.             entity.Validate(gameObject);
    96.         }
    97.     }
    98. #endif
    99.     public void OnDestroy() {
    100.         entity.Dispose();
    101.         entity = null;
    102.     }
    103.     public void OnBeforeSerialize() {
    104.         entity.BeforeSerialize(gameObject);
    105.     }
    106.     public void OnAfterDeserialize() {
    107.  
    108.         entity.AfterDeserialize();
    109.     }
    110. }
    111.  
    112. [Serializable]
    113. public class TE : IDisposable {
    114.     private bool disposed = false;
    115.  
    116.     [SerializeField]
    117.     protected byte[] serializedGuid = null;
    118.     protected Guid _cachedGuid = Guid.Empty;
    119.     public Guid guid {
    120.         get {
    121.             if (_cachedGuid == Guid.Empty && serializedGuid != null) {
    122.                 _cachedGuid = new Guid(serializedGuid);
    123.             }
    124.  
    125.             return _cachedGuid;
    126.         }
    127.     }
    128.     private void GenerateGuidCache(byte[] serializableGuid) {
    129.         if (guid == Guid.Empty && serializableGuid != null) {
    130.             _cachedGuid = new Guid(serializableGuid);
    131.         }
    132.     }
    133.  
    134. #if UNITY_EDITOR
    135.     [SerializeField]
    136.     private SceneAsset editorSceneRef;
    137. #endif
    138.  
    139.     //Other removed properties here
    140.  
    141.     public TE(GameObject owner) {
    142.         CreateGuid(owner);
    143. #if UNITY_EDITOR
    144.         if (editorSceneRef == null || sceneName != owner.scene.name) {
    145.             editorSceneRef = AssetDatabase.LoadAssetAtPath<SceneAsset>(owner.scene.path);
    146.         }
    147. #endif
    148.     }
    149.  
    150.     ~TE() {
    151.         Dispose(false);
    152.     }
    153.  
    154.     public void Dispose() {
    155.         Dispose(true);
    156.         GC.SuppressFinalize(this);
    157.     }
    158.  
    159.     protected virtual void Dispose(bool disposing) {
    160.         if (!disposed) {
    161.             //if (disposing) {} //Only needed if this class contains resources that need their Dispose methods called
    162.  
    163.             GuidManager.Remove(guid);
    164.             serializedGuid = null;
    165.             _cachedGuid = Guid.Empty;
    166.  
    167.  
    168.             editorSceneRef = null;
    169.  
    170.             disposed = true;
    171.         }
    172.     }
    173.  
    174.     public void Validate(GameObject owner) {
    175. #if UNITY_EDITOR
    176.         //Gets called on Copying a Component or Applying a Prefab at a time that lets us detect what we are
    177.         if (IsAssetOnDisk(owner)) {
    178.             serializedGuid = null;
    179.             _cachedGuid = Guid.Empty;
    180.         } else
    181. #endif
    182.         {
    183.             CreateGuid(owner);
    184. #if UNITY_EDITOR
    185.             if (editorSceneRef == null || sceneName != owner.scene.name) {
    186.                 editorSceneRef = AssetDatabase.LoadAssetAtPath<SceneAsset>(owner.scene.path);
    187.             }
    188. #endif
    189.         }
    190.     }
    191.  
    192.     protected void CreateGuid(GameObject owner) {
    193.         //Stuff that ties into global GuidManager
    194.     }
    195.  
    196.     public void BeforeSerialize(GameObject owner) {
    197. #if UNITY_EDITOR
    198.         // This lets us detect if we are a prefab instance or a prefab asset.
    199.         // A prefab asset cannot contain a GUID since it would then be duplicated when instanced.
    200.         if (IsAssetOnDisk(owner)) {
    201.             serializedGuid = null;
    202.             _cachedGuid = Guid.Empty;
    203.         } else
    204. #endif
    205.         {
    206.             if (!IsGuidSet() && guid != Guid.Empty) {
    207.                 serializedGuid = guid.ToByteArray();
    208.             }
    209.  
    210.         }
    211.  
    212.     }
    213.  
    214.     public void AfterDeserialize() {
    215.         if (IsGuidSet()) {
    216.             GenerateGuidCache(serializedGuid);
    217.         }
    218.     }
    219. }
    220.  
    221. //TE has it's own property drawer to aid in formatting
    222. [CustomPropertyDrawer(typeof(TE))]
    223. public class TEDrawer : PropertyDrawer {
    224.     SerializedProperty prop_guid, prop_name;
    225.  
    226.     Guid objGuid = Guid.Empty;
    227.  
    228.     public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) {
    229.         prop_guid = property.FindPropertyRelative("serializedGuid");
    230.  
    231.         //Using BeginProperty / EndProperty on the parent property means that prefab override logic works on the entire property.
    232.         EditorGUI.BeginProperty(rect, label, property);
    233.         rect.height = EditorGUIUtility.singleLineHeight;
    234.  
    235.         //Guid
    236.         objGuid = ByteArrayPropertyToGuidString(prop_guid);
    237.         EditorGUI.LabelField(rect, "Guid", objGuid.ToString());
    238.  
    239.         //Note: Remainder of TE fields are not shown as they are displayed/manipulated via TCC registration editor
    240.         EditorGUI.EndProperty();
    241.     }
    242.  
    243.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    244.         return (EditorGUIUtility.singleLineHeight * 2) + EditorGUIUtility.standardVerticalSpacing;
    245.     }
    246.  
    247.     private Guid ByteArrayPropertyToGuidString(SerializedProperty prop) {
    248.         byte[] byteArray = new byte[16];
    249.         int arraySize = prop.arraySize;
    250.         for (int i = 0; i < arraySize; ++i) {
    251.             var byteProp = prop.GetArrayElementAtIndex(i);
    252.             byteArray[i] = (byte)byteProp.intValue;
    253.         }
    254.  
    255.         return new Guid(byteArray);
    256.     }
    257. }
    258.