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

Question [SOLVED] Storing types of component into a single variable?

Discussion in 'Scripting' started by Kagyu, Jan 9, 2021.

  1. Kagyu

    Kagyu

    Joined:
    Mar 5, 2016
    Posts:
    95
    Hi.
    I would like to make a simple editor expansion script with which I can delete all specific components from a designated game object and its children.
    At this moment, I have several editor expansion scripts for each corresponded component, which works fine but getting messy as the project getting complexed.
    What I think ideal would be having a public input field for component, dragging any component script to the field and hit delete button.
    In other word, I like to know if there is a type of variable which can store various types of components.
    Is there anything which can make this happen?
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    Well, if your script (probably should be a ScriptableWizard) should only be able to remove custom components (i.e. MonoBehaviour derived classes) you can create a field of type MonoScript. You can directly drag the script file onto this field. MonoScript is an editor only type, so it can only be used in an editor script. The MonoScript class has a method called GetClass which returns the System.Type of the class that is defined in this script file. You can use this in GetComponent / GetComponents / GetComponentsInChildren / ... to get the components of that type.

    Note an alternative would be to just create a field of type Component and have the user drag in any component of that type. In your code you would simply use the instance reference of that component and use the GetType method to get the System.Type of that component type. The rest is the same as above. However this approach allows you to "select" any kind of component, even built-in components like rigidbody, colliders, renderers and so on.

    I remember that I made such a wizard one day. However this was already years ago and currently I don't have my PC at hand ^^. It may be useful to just provide a UnityEngine.Object field in the wizard so the user could drop any kind of object. In this selection window you may just analyse what was dropped and display a selection of possibilities to the user. So if he drops a gameobject reference you can just use GetComponents on that object and display the component names as selection.
     
  3. Kagyu

    Kagyu

    Joined:
    Mar 5, 2016
    Posts:
    95
    @Bunny83
    Thank you so much for the advise. I had no idea about ScriptableWizard and I will look into it.

    Regarding the alternative method, I think I need a bit more of your help.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class ComponentsRemover: MonoBehaviour
    6. {
    7. public Component component;
    8. }
    When I declare "component" Component variable like above, I cannot drag any script into that field in the editor though I can drag an game object. Do I misunderstand your suggestion?
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    Yes you can only drag instances of objects into that field. However the point was to just extract the type from that. Keep in mind that built-in types do not have a script asset that can be dragged onto gameobjects. So you would simply drag a component from an existing gameobject onto that field and in code you can use GetType on that instance to get the type.

    Many people don't know that you can drag individual components onto variables by grabbing the header of the component in the inspector.

    Your script is not an editor script but a runtime script (since you derived your type from MonoBehaviour). That doesn't make much sense and would complicate the usage.

    I did create an EditorWindow (ScriptableWizard is just a specialized version) with that functionality in the past. As I said I don't have a Unity installation at hand, so It's difficult to write code here. However my code looked something like that:

    Code (CSharp):
    1. // MyComponentDeleter.cs
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections.Generic;
    5.  
    6. public class ComponentNode
    7. {
    8.     public System.Type type;
    9.     public bool shouldDelete;
    10.     public void Draw()
    11.     {
    12.         GUILayout.BeginHorizontal("box");
    13.         GUILayout.Label(type.Name);
    14.         GUILayout.FlexibleSpace();
    15.         if (GUILayout.Button("x", GUILayout.Width(45f)))
    16.             shouldDelete = true;
    17.         GUILayout.EndHorizontal();
    18.     }
    19. }
    20.  
    21. public class MyComponentDeleter : EditorWindow
    22. {
    23.     [MenuItem("Tools/B83/ComponentDeleter")]
    24.     public static void Init()
    25.     {
    26.         GetWindow<MyComponentDeleter>();
    27.     }
    28.     List<ComponentNode> componentTypes = new List<ComponentNode>();
    29.     void AddComponent(System.Type aType)
    30.     {
    31.         if (componentTypes.Find(e => e.type == aType) == null)
    32.             componentTypes.Add(new ComponentNode { type = aType });
    33.     }
    34.     void AddComponents(UnityEngine.Object aObj)
    35.     {
    36.         if (aObj is MonoScript ms)
    37.             AddComponent(ms.GetClass());
    38.         else if (aObj is Component comp)
    39.             AddComponent(comp.GetType())
    40.         else if (aObj is GameObject go)
    41.         {
    42.             foreach(var comp in go.GetComponents<Component>())
    43.                 AddComponent(comp.GetType())
    44.         }
    45.     }
    46.  
    47.     void DeleteComponents(GameObject[] aGameObjects)
    48.     {
    49.         var comps = new List<Component>();
    50.         foreach(var type in componentTypes)
    51.         {
    52.             foreach(var go in aGameObjects)
    53.             {
    54.                 var tmp = go.GetComponents(type.type);
    55.                 if (tmp != null && tmp.Length > 0)
    56.                     comps.AddRange(tmp);
    57.             }
    58.         }
    59.         if (comps.Count == 0)
    60.         {
    61.             EditorUtility.DisplayDialog("No Components Found", "Nothing has been deleted", "ok");
    62.             return;
    63.         }
    64.         if (EditorUtility.DisplayDialog("Delete Components", "Found " + comps.Count+" components on the selectec gameobjects. Are you sure you want to delete them?", "yes, delete", "NO! keep them"))
    65.         {
    66.             foreach(var comp in comps)
    67.                 Undo.DestroyObjectImmediate(comp);
    68.             Undo.SetCurrentGroupName("Components deleted");
    69.         }
    70.     }
    71.     void OnGUI()
    72.     {
    73.         UnityEngine.Object obj = EditorGUILayout.ObjectField(null, typeof(UnityEngine.Object), true);
    74.         if (obj)
    75.             AddComponents(obj);
    76.         foreach(var c in componentTypes)
    77.             c.Draw();
    78.         for(int i = componentTypes.Count-1; i >=0; i--)
    79.             if (componentTypes[i].shouldDelete)
    80.                 componentTypes.RemoveAt(i);
    81.         if (GUILayout.Button("Delete choosen components from selected gameobjects"))
    82.         {
    83.             var gObjs = Selection.gameObjects;
    84.             if (gObjs == null || gObjs.Length < 0)
    85.                 return;
    86.             DeleteComponents(gObjs);
    87.         }
    88.     }
    89. }
    I've written this editor window here in the browser without testing it on a horrible notebook keyboard -.- Though I think it should work unless there's a typo somewhere. Just place the file in an "editor" folder of your project.

    You can drag and drop either scripts (MonoScript assets), a template gameobject or individual component instances in the object field at the top of the editor window. When you do the component(s) will be added to the list of components which will be shown under the object field. You can add more components or remove them from the list by pressing the "x" button behind one. This is just the component selection part.

    To actually deleting components you just have to select one or multiple objects in the hierarchy. When you selected some objects you can press the button at the end of the editor window. It should show a confirmation dialog, stating how many components will be deleted. When you press yes they should be deleted with an undo action.
     
    Last edited: Jan 9, 2021
    Kagyu likes this.
  5. Kagyu

    Kagyu

    Joined:
    Mar 5, 2016
    Posts:
    95
    @Bunny83
    Wow, thank you so much. This is much more than the answer. So many information I really need to learn!

    Since I did not know about ScriptableWizard, I always wrote another script which inherit Editor.
    In this case, I did something like this.
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. [CustomEditor(typeof(ComponentRemover))]
    5. public class ComponentRemoverEditor : Editor
    6. {
    7.     public override void OnInspectorGUI()
    8.     {
    9.         //Draw Default Inspector
    10.         base.OnInspectorGUI();
    11.  
    12.         //Get target component
    13.         ComponentRemover componentRemover = target as ComponentRemover;
    14.  
    15.         //Draw Button
    16.         if (GUILayout.Button("Remove"))
    17.         {
    18.             componentRemover.Remove();
    19.         }
    20.     }
    21. }
    As a result, I always needed to write two separate scripts, which worked fine but a bit not handy. I will learn the way you showed me above.

    BTW, I totally forgot this aspect of the Editor regarding this subject. Thank you for pointing this out!