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

Resolved ReorderableList usage in editor not working

Discussion in 'Scripting' started by Trodgy, Jun 4, 2023.

  1. Trodgy

    Trodgy

    Joined:
    Sep 10, 2022
    Posts:
    8
    So, I was working with a tutorial on how to use ReorderableLists in the editor windows to make an animation system to not have to put up with Unity's nightmarish animation webs.
    The tutorial in question was this one from terresquall
    ( https://blog.terresquall.com/2020/03/creating-reorderable-lists-in-the-unity-inspector/ )

    I made some minor adjustments to the script but nothing that should have caused any particular issues.
    Things like reducing
    Code (CSharp):
    1. SerializedProperty wave;
    2. wave = serializedObject.FindProperty("wave");
    3. list = new ReorderableList(serializedObject, wave, true, true, true, true);
    to just the slightly less sightly
    Code (CSharp):
    1. list = new ReorderableList(serializedObject, serializedObject.FindProperty("animatorInstance"), true, true, true, true);
    Anyways, like the tutorial asks for I have a Struct, a Manager/Handler that uses the Structs and an Editor script to allow the Manager/Handler to have the ReorderableLists compatibility.

    I have an object with an Animator in it which the Manager has been assigned to.

    The code for each of these bits is as follows:
    The Struct:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public struct AnimatorInstance
    7. {
    8.     public string AnimationName;
    9.     public int Type;
    10.     public float StartPosition;
    11.     public float EndPosition;
    12.     public int AnimationToTransitionInto;
    13.     public float CurrentTime;
    14.     public int LoopExitCount;
    15.     public int LoopExitIncrement;
    16. }
    17.  
    The Manager:
    ( there is more code but I simplified it down to the bit that matters here )
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class AnimationHandler : MonoBehaviour
    6. {
    7.     public AnimatorInstance[] animatorInstance;
    8. }
    The Editor Script:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEditorInternal;
    4. using System.Linq;
    5.  
    6. [CustomEditor(typeof(AnimationHandler))]
    7. public class AnimationHandlerReorganizableAccess : Editor
    8. {
    9.     ReorderableList list;
    10.  
    11.     private void OnEnable()
    12.     {
    13.         list = new ReorderableList(serializedObject, serializedObject.FindProperty("animatorInstance"), true, true, true, true);
    14.         list.drawElementCallback = DrawListItems;
    15.         list.drawHeaderCallback = DrawHeader;
    16.     }
    17.  
    18.     void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
    19.     {
    20.         SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
    21.  
    22.         EditorGUI.LabelField(new Rect(rect.x + 120, rect.y, 100, EditorGUIUtility.singleLineHeight), "AnimationName");
    23.         EditorGUI.PropertyField(
    24.             new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight),
    25.             element.FindPropertyRelative("animationName"),
    26.             GUIContent.none
    27.         );
    28.  
    29.         EditorGUI.LabelField(new Rect(rect.x + 120, rect.y, 100, EditorGUIUtility.singleLineHeight), "Type");
    30.         EditorGUI.PropertyField(
    31.             new Rect(rect.x + 160, rect.y, 20, EditorGUIUtility.singleLineHeight),
    32.             element.FindPropertyRelative("type"),
    33.             GUIContent.none
    34.         );
    35.  
    36.         EditorGUI.LabelField(new Rect(rect.x + 200, rect.y, 100, EditorGUIUtility.singleLineHeight), "StartPosition");
    37.         EditorGUI.PropertyField(
    38.             new Rect(rect.x + 250, rect.y, 20, EditorGUIUtility.singleLineHeight),
    39.             element.FindPropertyRelative("startPosition"),
    40.             GUIContent.none
    41.         );
    42.  
    43.     }
    44.  
    45.     void DrawHeader(Rect rect)
    46.     {
    47.         string name = "Animations";
    48.         EditorGUI.LabelField(rect, name);
    49.     }
    50.  
    51.     public override void OnInspectorGUI()
    52.     {
    53.         serializedObject.Update();
    54.         list.DoLayoutList();
    55.         serializedObject.ApplyModifiedProperties();
    56.     }
    57. }


    Now, with all of that setup, the actual issue is that something in the Editor script results in this error:

    NullReferenceException: : SerializedProperty is null
    UnityEditor.EditorGUI.BeginPropertyInternal (UnityEngine.Rect totalPosition, UnityEngine.GUIContent label, UnityEditor.SerializedProperty property) (at <ee6fd03ff96245f993c3cba83ed9657b>:0)

    As far as I can tell the issue is the result of some sort of failure to obtain the properties of the Struct which thus results in them being null and therefore flagging many errors upon trying to create a new instance in the ReorderableList, but I can't say definitively.

    Any help would be appreciated.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,517
    Yeah, that's the problem with this approach:

    I always go exactly the opposite way personally: one step per line. Here's why:

    If you have more than one or two dots (.) in a single statement, you're just being mean to yourself.

    How to break down hairy lines of code:

    http://plbm.com/?p=248

    Break it up, practice social distancing in your code, one thing per line please.

    "Programming is hard enough without making it harder for ourselves." - angrypenguin on Unity3D forums

    "Combining a bunch of stuff into one line always feels satisfying, but it's always a PITA to debug." - StarManta on the Unity3D forums

    Beyond that, sounds to me like you have a nullref!

    If so, then the answer is always the same... ALWAYS!

    How to fix a NullReferenceException error

    https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

    Three steps to success:
    - Identify what is null <-- any other action taken before this step is WASTED TIME
    - Identify why it is null
    - Fix that
     
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    To eliminate or confirm your suspicions, why don't you try with a different struct? Or with the struct covered by the tutorial that you're following. If that works, you now know where to look. If that doesn't work, again, you now know where not to look.

    I doubt strongly that you will confirm anything by doing this, but you're still going to eliminate your own assumptions. That's always one half of the debugging job.
     
  4. Trodgy

    Trodgy

    Joined:
    Sep 10, 2022
    Posts:
    8
    Noted, although personally I prefer to optimize things like this since usually it doesn't hinder debugging for me.

    I found what was null, the variable element in the Editor script as a result of the list not being able to receive whatever array element it was looking for at index.

    Unsure how to since I don't fully understand how the ReorderableLists are declared nor how the void DrawListItems connects to it.
     
  5. Trodgy

    Trodgy

    Joined:
    Sep 10, 2022
    Posts:
    8
    That gave me the idea to just play around with how I was implementing it and I found out what was causing it.

    Turns out bits like this
    Code (CSharp):
    1. element.FindPropertyRelative("animationName"),
    were meant to be like this
    Code (CSharp):
    1. element.FindPropertyRelative("AnimationName"),
    I have been hit by the minor spelling mistakes yet again.

    Thanks for the help though.
     
    orionsyndrome likes this.
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Btw your change is less sightly on so many accounts, but I don't believe it has anything to do with this.

    ...

    Indeed, the problem is in how you're naming things
    Code (csharp):
    1. element.FindPropertyRelative("animationName")
    This cannot possibly be a camelCase, when you use PascalCase in the struct
     
    Nad_B likes this.
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Great, you've discovered it literally a minute before I posted.
     
    Nad_B likes this.
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,517
    Definitely a preferences choice... but only to an extent. Here's why:

    When you change stuff like that to be one long gnarly line, at least on MY monitor, the actual problem where the actual bug was actually ultimately found was ...

    ... off the righthand side of the screen. In other words, "I don't see no bug!"

    And as far as "optimize," you're not.

    Again, philosophies of code, potayto, potahto, but as a professional software engineer I regularly have to unwind gnarly code to find bugs and almost always, guess what... now that I can put a breakpoint on each line, I can trivially find the bug, as you did!

    It is my humble opinion that code should be optimized for HUMANS to read it, unless there truly is some way to change the code (not rearrange it, change it!) that makes the computer actually run faster. The above is not one of those ways.
     
    orionsyndrome and Nad_B like this.
  9. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    655
    That's the reason why C# team added the nameof keyword.

    Code (CSharp):
    1. element.FindPropertyRelative(nameof(AnimatorInstance.AnimationName))
     
    orionsyndrome and Kurt-Dekker like this.
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,517
    Nad_B likes this.
  11. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    @Kurt-Dekker I always thought that was interesting, and I believe that's because C/C++ programmers were notoriously clebber with how they were using macros, they've sunk innumerous projects in a horrible muddy mess of clebberness. It's simply too versatile, only 2% of programmers would show some humility, the rest of them would ride their mutated hip hop dinosaurs capable of polymorphing into sunset. Now before you ask "wait 'polymorphing into sunset', are you missing a comma there" yes, no I'm not, that was exactly the problem. Once they polymorphed into sunset, the next time someone would design a language had just one simple rule: we never talk about macros ever again.
     
    Nad_B and Kurt-Dekker like this.
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,517
    And yet we have code generators... why are code generators "okay" but macros bad?

    I have yet to hear a single coherent argument explaining the above.

    Now I need to write extra tooling and a special before-compile-time run state to do what used to just ... work!

    As Arnold Schwarzenegger said in Total Recall,

    "Geeve theze people macros!"
     
    orionsyndrome likes this.
  13. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Excellent question. But then one can also implement codegen macros as well? Sure they're no longer integrated with the compiler in any meaningful way.

    Here's the general sentiment against (proposed) SyntaxTrees since Roslyn, that I've come across
    So in some way codegen and code analysis is a solution? I really think they just want to distance themselves from the word "macro". It's somehow connotatively impure, detrimental, and contagious.
     
    Kurt-Dekker likes this.