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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Need help with some custom inspector

Discussion in 'Scripting' started by Hapciupalit, Aug 3, 2017.

  1. Hapciupalit

    Hapciupalit

    Joined:
    Apr 24, 2015
    Posts:
    103
    Hey guys,
    I'm trying to create a custom inspector element for a class. What I want to achieve is something like in this photo.



    On the same Object if I choose Peasant to have some variables while if I choose warrior to get other variables.
    If someone could point me to the right tutorial or give me a short script of how this could be possible it would be great. I searched the internet for a few hours, but to be honest I'm not sure what to search for.

    Thank you
     
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    First, depending on your needs, would it be better to use subclasses or composition?

    Subclasses
    For example, you could use these subclasses to distinguish between Peasants and Warriors without having to write any custom editors:
    Code (csharp):
    1. public class ConversationFirstNode : MonoBehaviour {
    2.     public string name;
    3.     public int age;
    4. }
    5.  
    6. public class PeasantNode : ConversationFirstNode {
    7.     [Header("Peasant-Specific")]
    8.     public int numberOfSheep;
    9. }
    10.  
    11. public class WarriorNode : ConversationFirstNode {
    12.     [Header("Warrior-Specific")]
    13.     public int level;
    14.     public string profession;
    15. }

    Composition
    Or you could add another class that indicates the character's station:
    Code (csharp):
    1. public class ConversationFirstNode : MonoBehaviour {
    2.     public string name;
    3.     public int age;
    4.     public Station station;
    5. }
    6.  
    7. public class Station : MonoBehaviour {}
    8.  
    9. public class Peasant : Station {
    10.     public int numberOfSheep;
    11. }
    12.  
    13. public class Warrior : Station {
    14.     public int level;
    15.     public string profession;
    16. }

    Custom Editors
    But if you want to use a custom editor, these two links will help:
    Briefly, you'll create a new class inside an Editor folder. Its OnInspectorGUI method will:
    1. Copy the component's current data into a serialized object representation.
    2. Show editor GUI elements for each field of serialized data.
    3. Copy the serialized object representation back to the component.
    For example:
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. [CustomEditor(typeof(ConversationFirstNode))]
    4. [CanEditMultipleObjects]
    5. public class ConversationFirstNodeEditor : Editor {
    6.  
    7.     public override void OnInspectorGUI() {
    8.         serializedObject.Update();
    9.         EditorGUILayout.PropertyField(serializedObject.FindProperty("name"));
    10.         EditorGUILayout.PropertyField(serializedObject.FindProperty("age"));
    11.         var classProperty = serializedObject.FindProperty("characterClass");
    12.         if (classProperty.enumValueIndex == (int)CharacterClass.Warrior) {
    13.             // Show warrior-specific info:
    14.             EditorGUILayout.PropertyField(serializedObject.FindProperty("level"));
    15.             EditorGUILayout.PropertyField(serializedObject.FindProperty("profession"));
    16.         }
    17.         serializedObject.ApplyModifiedProperties();
    18.     }
    19. }
     
    roojerry likes this.
  3. Hapciupalit

    Hapciupalit

    Joined:
    Apr 24, 2015
    Posts:
    103
    One more question... Is there any way for me to create a List of this kind of types. For example to create a GameObject which have a public list. And when I set it to 10 to have 10 objects of the ConversationFirstNode?

    Also I'm trying now to use the subclasses way but I'm not sure how can I set the class? I got age, name, but I don't understand how can I choose what kind of person it is so that I have those attributes only for a certain class
     
    Last edited: Aug 3, 2017
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    Sure. It's easiest if FirstConversationNode is plain serialized class and not a MonoBehaviour. For example:
    Code (csharp):
    1. [System.Serializable]
    2. public class ConversationFirstNode { // Note: not derived from MonoBehaviour
    3.     public string name;
    4.     public int age;
    5.     public CharacterClass characterClass;
    6.     public int level;
    7.     public string profession;
    8. }
    9.  
    10. public class ConversationList : MonoBehaviour {
    11.     public ConversationFirstNode[] firstNodes;
    12. }
    When you add ConversationList to a GameObject and set the size of its firstNodes array to 10, it will create 10 ConversationFirstNode objects.

    MonoBehaviours, however, are serialized by reference. If ConversationFirstNode is a MonoBehaviour (public class ConversationFirstNode : MonoBehaviour), then if you set the size of firstNodes to 10, it will create 10 empty slots to which you'll need to manually assign references to ConversationFirstNode components. You can write an editor script to do that, something like:
    Code (csharp):
    1. [CustomEditor(typeof(ConversationList))]
    2. public class ConversationListEditor : Editor {
    3.     public override void OnInspectorGUI() {
    4.         DrawDefaultInspector();
    5.         if (GUILayout.Button("Create missing components")) {
    6.             serializedObject.Update();
    7.             var firstNodesProperty = serializedObject.FindProperty("firstNodes");
    8.             for (int i = 0; i < firstNodesProperty.arraySize; i++) {
    9.                 var element = firstNodesProperty.GetArrayElementAtIndex(i);
    10.                 if (element.objectReferenceValue == null) {
    11.                     // Array element is null. Create & assign a component:
    12.                     var gameObject = (target as ConversationList).gameObject;
    13.                     var component = gaeObject.AddComponent<ConversationFirstNode>();
    14.                     element.objectReferenceValue = component;
    15.                 }
    16.             }
    17.             serializedObject.ApplyModifiedProperties();
    18.         }
    19.     }
    20. }
     
  5. Hapciupalit

    Hapciupalit

    Joined:
    Apr 24, 2015
    Posts:
    103
    Thank you very much. I appreciate your fast response. I'm going to try it now.
    Best regards
     
  6. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    Happy to help! A lot of info is packed into these replies. If you get stuck, those two documentation links in my first reply should help clarify things.
     
  7. Hapciupalit

    Hapciupalit

    Joined:
    Apr 24, 2015
    Posts:
    103
    Sorry to bother you, but I'm a bit stucked while creating the list and you seem like a guy who knows this things.
    I created my objects and made the Editor as I wished, but now I'm not sure how can I create a list of those objects.

    This is the type of objects I want to see in my list
    Code (CSharp):
    1. public class ConversationOption{
    2.  
    3.     public OptionTypes optionType;
    4.  
    5.     public StatBool boolVariable;
    6.     public bool boolNeedsToBe;
    7.  
    8.     public StatsList statVariable;
    9.     public Comparison statNeedsToBe;
    10.     public int value;
    11.  
    12.     public GameObject target;
    13. }
    And this is the Editor I made for the ConversationOption
    Code (CSharp):
    1.  
    2. [CustomEditor(typeof(ConversationOption))]
    3. [CanEditMultipleObjects]
    4. public class ConversationOptionEditor : Editor{
    5.  
    6.     public override void OnInspectorGUI(){
    7.  
    8.         serializedObject.Update();
    9.  
    10.         EditorGUILayout.PropertyField(serializedObject.FindProperty("optionType"));
    11.  
    12.         OptionTypes optionType = (OptionTypes)serializedObject.FindProperty("optionType").enumValueIndex;
    13.  
    14.         switch(optionType){
    15.  
    16.         case OptionTypes.booleanType:
    17.             EditorGUILayout.Space();
    18.             EditorGUILayout.PropertyField(serializedObject.FindProperty("boolVariable"));
    19.             EditorGUILayout.PropertyField(serializedObject.FindProperty("boolNeedsToBe"));
    20.  
    21.             break;
    22.        
    23.         case OptionTypes.integerType:
    24.             EditorGUILayout.Space();
    25.             EditorGUILayout.PropertyField(serializedObject.FindProperty("statVariable"));
    26.             EditorGUILayout.PropertyField(serializedObject.FindProperty("statNeedsToBe"));
    27.             EditorGUILayout.PropertyField(serializedObject.FindProperty("value"));
    28.             break;
    29.         }
    30.  
    31.         EditorGUILayout.Space();
    32.         EditorGUILayout.PropertyField(serializedObject.FindProperty("target"));
    33.  
    34.         serializedObject.ApplyModifiedProperties();
    35.  
    36.     }
    37.  
    38. }
    Thank you
     
  8. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    Where do you want the list? What's the overall purpose and structure that you want?
     
  9. Hapciupalit

    Hapciupalit

    Joined:
    Apr 24, 2015
    Posts:
    103
    For a game that I'm working, I made a poorly conversation system with different nodes based. And one of them should be the list GameObject.
    So what I'm trying to achieve here is a new node, is having a GameObject with the ConversationOptionList Script where I can create a list of ConversationOptions elements.
    I already made a node like this which is working, but it's a bit annoying, because I have to create other GameObjects and then drag/drop into my ConversationOptionList Object.

    This is how my ConversationOptionScript looks like


    And this is one ConversationOption Object


    And the other one


    So the point of this thing is having different conversations based on some stats of the character, like money, fighting skill, progress in quest. The player could always talk to the NPC but only some of the options will be available based on some stats.
    Here is an example of what I get out of this.


    So the thing that annoys me is the fact that I have to create another objects with the ConversationOption script instead of having those options on the ConversationOptionList.
     
  10. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    You could add a "New Option" button in your custom editor that creates a child GameObject with a new ConversationOption:

    Code (csharp):
    1. public override OnInspectorGUI() {
    2.     serializedObject.Update();
    3.     ...
    4.     serializedObject.ApplyModifiedProperties();
    5.     if (GUILayout.Button("New Option")) {
    6.         Undo.RecordObject(conversationOptionList, "New Option");
    7.         var child = new GameObject("Option", typeof(ConversationOption));
    8.         child.transform.setParent(conversationOptionList.transform);
    9.         conversationOptionList.options.Add(child.GetComponent<ConversationOption>());
    10.     }
    11. }
    This assumes you have a reference to your ConversationOptionList component named conversationOptionList, and it has a List<ConversationOption> named options.