Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Can I make collapsible groups within the inspector?

Discussion in 'Scripting' started by Marscaleb, Feb 14, 2020.

  1. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    Is there a way I can have serialized / public variables be collected into a collapsible group when viewing in the inspector? Some of my classes are getting really big and it can be hard to find values I'm looking for, especially spread out across various child classes. I'd like to be able to group various variables and attributes so I can just open sections that have what I want to actually edit.

    EDIT: I guess I'm not being clear enough.

    Here's an example.

    This is how one of my player scripts looks in the inspector:

    There's a lot of properties and variables. It gets hard to navigate. This one is even longer than the size of my inspector.

    I would like to group these together so that they show up like this:


    Then I can just expand the sections that are relevant to the data I need to change.
     
    Last edited: Feb 15, 2020
  2. All_American

    All_American

    Joined:
    Oct 14, 2011
    Posts:
    1,528
    Lists or arrays.
     
  3. sbalanoff

    sbalanoff

    Joined:
    Nov 14, 2019
    Posts:
    36
    I would do something as the following:

    Code (CSharp):
    1. public class className : MonoBehaviour
    2. {
    3.         GameObejct[] objects;
    4.         void Update(){
    5.                 objects = GameObject.FindGameObjectsWithTag("object");
    6.         }
    7. }
     
    All_American likes this.
  4. All_American

    All_American

    Joined:
    Oct 14, 2011
    Posts:
    1,528
    But in the start method correct?
     
  5. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    I think I'm not being clear enough.

    EDIT: Moved example to original post.
     
    Last edited: Feb 15, 2020
  6. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    I did this exact thing yesterday. I used classes though.
    Here's how you could do it,
    Code (CSharp):
    1. public class PPMage : MonoBehaviour {
    2.              public MovementClass Movement;
    3.              public AttackClass Attacks;
    4.  
    5.  
    6.              void Start () {
    7.               }
    8.               void Update () {
    9.                       //You can access the variables like this
    10.                       transform.Translate(Vector3.Up, Movement.movementSpeed);
    11.                }
    12. }
    13.  
    14. public class MovementClass {
    15.             //Your movement variables here
    16.             //For instance,
    17.             public float movementSpeed;
    18. }
    19.  
    20. public class AttackClass {
    21.             //Attack Variables Here
    22.             public int attackDamage;
    23. }
    24.  
     
  7. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    I see; quite clever.
    It doesn't work quite the way I want, because I have to manage a separate script within the inspector, but that does still achieve the same end goal.
    I suppose this could be coupled with a "required component" and a simple search function within the main script so that I don't even need to have the variable for the second script show up within the inspector.

    I do somewhat wonder what kind of overhead that brings; I'm sure it's not too much but creating a whole class just to house variables I want to collapse within the inspector feels a bit wasteful/inefficient. I am working on some custom collision scripts, so some of these variables are going to be called several times each frame my several objects in the scene. I worry that casting the variables through a second class might slow things down a bit.
    Though as I say that, I suppose I could just copy those variables at the start if it was really a big deal. Still not as efficient as using conventional variables, but it would be a lot easier to work with.

    ...If there's no "true" collapsible field functionality, then I'm gonna make a feature request for it.
     
    Last edited: Feb 16, 2020
  8. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    I didn't get that. All the code I wrote above is in the same single script. There is no need for multiple scripts.
    Here's my script,
    Collapse1.PNG


    Collapse2.PNG

    The two references you see at the beginning are added by me. They don't have anything to do with the classes.
     
  9. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    Oh!
    Snap, I started making that class as a separate script. Well then, I guess I'll try to have multiple classes in the same script; I didn't really know that was possible. Sorry, I'm self-taught and I never actually went to programmer school.

    So now I'm left to ask, how efficient is this under the hood? If I reference a variable that is through this other class, does it take an extra cycle when it is class.myVariable instead of myVariable? Does it add any extra memory?
    This is becoming important to me because I'm writing some low-level stuff, some collision systems that will applied to nearly every object in my scene, each one referencing some of these variables several times every frame. Even taking one additional cycle cast a variable could nearly double the time it takes to calculate a single frame.

    EDIT: Also, I just tried to add that extra class at the bottom of my script, following that same example you posted, but I don't see it in the inspector. I have a public variable for it, but it doesn't show up in the inspector.
     
    Last edited: Feb 16, 2020
  10. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    Me niether, lol. Don't worry about that.

    I'm not sure about efficiency. But I think, It does cost a little extra memory to access variables through another class.
    A google search led me to this,
    https://forum.unity.com/threads/dec...lly-vs-globally-effect-on-performance.186915/

    It's not exactly what we're looking for, but it does help somewhat. (The second post)

    If performance is an issue, Then I suggest looking into Custom Editor (as Peter77 pointed out above). I haven't tried it personally, because It seems a little tedious to script a custom editor just to hide some variables. (Just look at the example script).
     
  11. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    So I tried what you suggested, but the class isn't showing up in the inspector.

    Code (CSharp):
    1. public class RCActor : MonoBehaviour
    2. {
    3.     public CollisionVars CollData;
    4.     //public LayerMask collisionMask;
    5.  
    6.     //public Vector2 myCollision = new Vector2(16f,16f);
    7.     //public Vector2 myCollisionOffset = new Vector2(0,8f);
    8.  
    9.     public float myGravity = 10f;
    10.  
    11.     //public float maxSlopeAngle = 65f;
    12.  
    13. ...
    14.  
    15. }
    16.  
    17. public class CollisionVars
    18. {
    19.  
    20.     public LayerMask collisionMask;
    21.  
    22.     public Vector2 myCollision = new Vector2(16f, 16f);
    23.     public Vector2 myCollisionOffset = new Vector2(0, 8f);
    24.  
    25.     public float stepHeight = 5f;
    26.     public float maxSlopeAngle = 65f;
    27.     public float maxCollisionPrecision = 8f;
    28.  
    29.     public float maxUphillSpeedMultiplier = 1f;
    30.     public float maxDownhillSpeedMultiplier = 1f;
    31. }
    But it doesn't show up in the inspector. I see "MyGravity" at the top of the script; that's it.
     
  12. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    You need to add [System.Serializable] above the class name where you declare it.
    Code (CSharp):
    1. [System.Serializable]
    2. public class MyClass {
    3.           public int myint;
    4. }
     
    MushyAvocado, BenGamer427 and Alaadel like this.
  13. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    It works! Oh ho ho, it's beautiful!
     
  14. Alaadel

    Alaadel

    Joined:
    Apr 6, 2013
    Posts:
    25
    For anyone interested, check this answer too. (You would probably need the parent's code only)
     
  15. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
  16. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    I'm trying to read about this, but I'm not sure exactly what I'm reading here; I'm not following how to add these labels into my script.
     
  17. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    You'd need to make a custom inspector for your script, which you can use to customize how the script's inspector is displayed.

    So if you had the following MonoBehaviour script:
    Code (CSharp):
    1. public class Example : MonoBehaviour {
    2.    public string fullName;
    3.    public int age;
    4.    public float height;
    5.    public Color favouriteColor;
    6.    public string favouriteAnimal;
    7.    public float favouriteNumber;
    8. }
    And you wanted to group "fullName", "age", & "height" into a "Basic Information" foldout, and "favouriteColor", "favouriteAnimal", & "favouriteNumber" into a "Favorites" foldout.
    You can write a custom inspector script that selects these fields and manually determines how they're displayed, while assigning each field under their appropriate foldout group:
    Code (CSharp):
    1. using UnityEditor;
    2.  
    3. [CustomEditor(typeof(Example))]
    4. public class ExampleEditor : Editor {
    5.    SerializedProperty fullName;
    6.    SerializedProperty age;
    7.    SerializedProperty height;
    8.    SerializedProperty favouriteColor;
    9.    SerializedProperty favouriteAnimal;
    10.    SerializedProperty favouriteNumber;
    11.  
    12.    bool showInfo, showFavorites = false;
    13.  
    14.    void OnEnable() {
    15.       fullName = serializedObject.FindProperty("fullName");
    16.       age = serializedObject.FindProperty("age");
    17.       height = serializedObject.FindProperty("height");
    18.       favouriteColor = serializedObject.FindProperty("favouriteColor");
    19.       favouriteAnimal = serializedObject.FindProperty("favouriteAnimal");
    20.       favouriteNumber = serializedObject.FindProperty("favouriteNumber");
    21.    }
    22.  
    23.    public override void OnInspectorGUI() {
    24.       serializedObject.Update();
    25.  
    26.       showInfo = EditorGUILayout.Foldout(showInfo , "Basic Information");
    27.       if(showInfo) {
    28.          EditorGUILayout.PropertyField(fullName);
    29.          EditorGUILayout.PropertyField(age);
    30.          EditorGUILayout.PropertyField(height);
    31.       }
    32.  
    33.       showFavorites = EditorGUILayout.Foldout(showFavorites, "Favorites");
    34.       if(showFavorites) {
    35.          EditorGUILayout.PropertyField(favouriteColor);
    36.          EditorGUILayout.PropertyField(favouriteAnimal);
    37.          EditorGUILayout.PropertyField(favouriteNumber);
    38.       }
    39.  
    40.       serializedObject.ApplyModifiedProperties();
    41.    }
    42. }
    The above will make the inspector for the Example script look like this:
    upload_2020-7-21_20-58-46.png

    upload_2020-7-21_20-59-3.png

    There is also EditorGUILayout.BeginFoldoutHeaderGroup and EditorGUILayout.EndFoldoutHeaderGroup, which works similarly, except the foldout labels are visually emphasized more. If we change the OnInspectorGUI code in the ExampleEditor script to this instead:
    Code (CSharp):
    1. public override void OnInspectorGUI() {
    2.    serializedObject.Update();
    3.  
    4.    showInfo = EditorGUILayout.BeginFoldoutHeaderGroup(showInfo, "Basic Information");
    5.    if(showInfo) {
    6.       EditorGUILayout.PropertyField(fullName);
    7.       EditorGUILayout.PropertyField(age);
    8.       EditorGUILayout.PropertyField(height);
    9.    }
    10.    EditorGUILayout.EndFoldoutHeaderGroup();
    11.  
    12.    showFavorites = EditorGUILayout.BeginFoldoutHeaderGroup(showFavorites, "Favorites");
    13.    if(showFavorites) {
    14.       EditorGUILayout.PropertyField(favouriteColor);
    15.       EditorGUILayout.PropertyField(favouriteAnimal);
    16.       EditorGUILayout.PropertyField(favouriteNumber);
    17.    }
    18.    EditorGUILayout.EndFoldoutHeaderGroup();
    19.  
    20.    serializedObject.ApplyModifiedProperties();
    21. }
    The Example script inspector now looks like this:
    upload_2020-7-21_21-4-55.png

    upload_2020-7-21_21-5-7.png

    I'm not exactly sure why Unity has two ways of doing the same thing, but hopefully this process will become more streamlined when they introduce their new way of writing editor scripts.
     
  18. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,120
    @Vryken 's approach is solid. But if you don't want to have to make custom inspectors for everything, I use a "poor man's" solution: Just put a [Header()] attribute on some of the public properties. For example:

    Code (CSharp):
    1. [Header("Warehouse Storage Rack")]
    2. public SkinnedMeshRenderer[] PlatformRenderers;
    It doesn't make collapsable regions, but it does visually separate things nicely enough for me to avoid "wall of text" syndrome:

    upload_2020-7-22_1-21-28.png

    Of course, [Spacer()] also does something similar, just without the bold header.
     
  19. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    The very quick and easiest way to go about it is to group whichever fields you want into a struct:
    Code (CSharp):
    1. public class Example : MonoBehaviour {
    2.    [SerializeField] private BasicInfo basicInfo;
    3.    [SerializeField] private Favorites favorites;
    4. }
    5.  
    6. [Serializable]
    7. public struct BasicInfo {
    8.    [SerializeField] private string fullName;
    9.    [SerializeField] private int age;
    10.    [SerializeField] private float height;
    11.  
    12.    public string FullName => fullName;
    13.    public int Age => age;
    14.    public float Height => height;
    15. }
    16.  
    17. [Serializable]
    18. public struct Favorites {
    19.    [SerializeField] private Color favouriteColor;
    20.    [SerializeField] private string favouriteAnimal;
    21.    [SerializeField] private float favouriteNumber;
    22.  
    23.    public Color FavouriteColor => favouriteColor;
    24.    public string FavouriteAnimal => favouriteAnimal;
    25.    public float FavouriteNumber => favouriteNumber;
    26. }
    27.  
    This would give the same result, and in some cases it would be preferred (it actually seems fine in this example), but it's not always ideal since you may sometimes only want to group fields in the inspector without grouping them in code.

    I just hope Unity's new method of writing custom inspectors makes this process much more streamlined. Creating custom layouts is only one of a few other issues I have with custom editors.
     
    hughperkins and elenzil like this.
  20. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    Ohhh, it's not adding elements to my script, it's an entirely new script!

    ...And it also extends from the base script. Hmm, that complicates things a bit, because the scripts I would want to add collapsible elements to are already extensions of other scripts.
    Like I've got a "pawn" script, and then a "PlayerPawn" and "EnemyAI" scripts that extend form that, plus more extensions for the different player classes and many various enemy types. So I'd have to create this script for each individual one of those scripts. I'd even have to create a new editor script for extension that don't need collapsable values, just to display the new public values, wouldn't I? Plus if I create new public variables I'd need open up that editor script and add them in there, too.
    With using a serialized class, I can at least keep those new variables in the same script file, and only ever change them in one place.

    This may be functional, but it sure is inconvenient to execute.

    This would be a good system if I was creating content for the Unity store, but since I'm just a solo developer, this doesn't seem like a reasonable amount of effort...
    Thank you for the information though. It's good to know there are options like this out there.
     
  21. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    Would it make any considerable different to use structs instead of classes?
    This is an example of something I am using:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PlayerPawn : Pawn {
    5.  
    6.     [Header("Player Pawn")]
    7.  
    8.     [Tooltip("A collection of sounds used by the player. ")]
    9.     public PlayerSoundList PlayerSounds;
    10.  
    11. protected virtual void PlayAttackEffortSound()
    12.     {
    13.             myAudio.clip = PlayerSounds.EffortAttack[Random.Range(0, PlayerSounds.EffortAttack.Length)];
    14.             myAudio.Play();
    15.     }
    16.  
    17.  
    18. }
    19.  
    20.  
    21. [System.Serializable]
    22. public class PlayerSoundList
    23. {
    24.     public AudioClip[] HurtMinor;
    25.     public AudioClip[] HurtMedium;
    26.     public AudioClip[] HurtMajor;
    27.     public AudioClip[] DeathCries;
    28.     public AudioClip[] EffortJump;
    29.     public AudioClip[] EffortAttack;
    30.     public AudioClip[] Breath;
    31.     public AudioClip Wind;
    32.     public AudioClip Dash;
    33. }
    It's all in one file. And if I decided I needed a new sound effect, I only need to add it to the class at the bottom of my script, and instantly I can use it in my script via PlayerSounds.NewSound.
     
  22. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Just a small clarification:
    Editor scripts don't extend the script they're editing, they extend the Editor class:
    Code (CSharp):
    1. using UnityEditor;
    2.  
    3. [CustomEditor(typeof(Example))]
    4. public class ExampleEditor : Editor {
    5.  
    6. }
     
  23. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    D'oh! I misread it! I saw
    ExampleEditor : Editor
    and thought it was
    ExampleEditor : Example
    !

    Even so, am I correct in that I would need a script for each level of script I extend? And am I correct in that I would need to edit the script to display a plain-old public variable I added even if it doesn't get put into a collapsible group?
     
  24. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Structs differ from classes in a few ways, but mainly in that they are value types, rather than reference types.
    This StackOverflow thread goes in to detail about them, but generally, they're used for when you want to group a set of data into one container that represents a single value, hence: "struct" -> "data structure".

    Unity's Vector3 and Vector2 are examples of structs being used to represent a set of numbers as a single vector.

    Unfortunately, yes.
    Here's hoping the new custom editor system changes that.
     
  25. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    A simple single-line function would be best. Like maybe:
    Code (CSharp):
    1. [group("myCustomGroup")]
    ...have that above every item I want in that given group.

    ...but I guess we'll see.
     
  26. chrische5

    chrische5

    Joined:
    Oct 12, 2015
    Posts:
    52
    Hello

    You can also use Odin Inspector.

    Christoph
     
  27. jack111one1

    jack111one1

    Joined:
    May 1, 2018
    Posts:
    2
    No, that's what I'M looking for. Nested class solves Marscaleb's problem easily, but I've been trying to figure out nested classes in a custom editor and that's exactly what I needed. So thanks!
     
  28. muzboz

    muzboz

    Joined:
    Aug 8, 2012
    Posts:
    93
    It WOULD be cool if Unity allowed another HEADER type which could be defined as COLLAPSIBLE, and it just visually closes everything inside that HEADER group. Would be handy. :)
     
    Satinel, hughperkins, Enlumis and 5 others like this.
  29. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    This is already a thing, actually.
    If you hold alt and click on any foldout header in the inspector or scene hierarchy, it will expand/collapse all child foldout headers as well.
     
  30. Thanitsak

    Thanitsak

    Joined:
    Jan 4, 2016
    Posts:
    110
    I'm not quite sure when since this changed, but in Camera component this collapsible ones look pretty awesome. Anyone else noticed this as well? Cuz maybe we can make something like that now? just curious

    Screen Shot 2565-05-30 at 4.10.26 PM.png
     
    Marscaleb likes this.
  31. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    992
    That looks pretty sweet. It's not in the version I have though. But I'd certainly love to put that kind of thing into my scripts, though.
     
    Thanitsak likes this.
  32. xxmark71xx

    xxmark71xx

    Joined:
    Dec 5, 2020
    Posts:
    4
    This is EXACTLY what I needed. Simple!

    As I start to scale my code, I will look into the more scalable solution, but for now, this is PERFECT, SIMPLE, QUICK TO MARKET!
     
  33. verny2verny

    verny2verny

    Joined:
    Nov 11, 2016
    Posts:
    5
    In my code I like using -

    [Header("**** Name_Of_Region ****")]
    #region Name_Of_Region
    //definitions and functions relevant to the region grouping
    #endregion
    [Spacer(25.0f)]

    This gives me both a collapsible region inside code that helps me organize large scripts, and labels with separation in the editor that correspond to the regions.

    I might have some region named "Bullets" that includes all the defines and functions that have anything to do with firing bullets inside the game. Then when I am working on the code, I can also easily find the relevant section in the editor in case I need to define or link values there.
     
  34. hughperkins

    hughperkins

    Joined:
    Dec 3, 2022
    Posts:
    191
    @Vryken 's solution worked nicely for me :) Simple, easy, effective.

    [Edit: i.e. using structs]
     
  35. MushyAvocado

    MushyAvocado

    Joined:
    Oct 16, 2021
    Posts:
    20