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

Question AddComponent() via Inspector 'Script' field

Discussion in 'Scripting' started by moonsky70, May 19, 2023.

  1. moonsky70

    moonsky70

    Joined:
    Feb 28, 2015
    Posts:
    3
    Hi all!
    While it's relatively straightforward to use AddComponent with an explicitly provided type in script, i.e
    Code (CSharp):
    1. ExampleComponent x = AddComponent<ExampleComponent>()
    I'd like for the type of the component (in this case 'ExampleComponent') to be defined via a field in a ScriptableObject, so that it can determine the behaviour to add to an object based on which ScriptableObject is selected. The closest I've been able to get to this is using MonoScripts:
    Code (CSharp):
    1. public class ExampleClass : MonoBehaviour
    2. {
    3.     public MonoScript behaviour;
    4.     public void AddBehaviour()
    5.         {
    6.             AddComponent(behaviour.GetClass());
    7.         }
    8.     ...
    9. }
    This "works", but the obvious drawback is that MonoScripts aren't usable outside of the editor. Less importantly, but still frustratingly, I can't limit the selection field to scripts of MonoBehaviours that inherit a certain type- the selection menu will contain all MonoScripts.

    Alternatively, I've seen one or two folks store their behaviours as components in prefabs-- if this is really the best choice I would probably do it, but it seems very undesirable, as it requires making and storing a new .prefab file for every child of ExampleClass that I want to use in this way (ew).

    I had limited success using a custom editor-- it could handle the Monoscript and its field, was responsible for type constraints, and set a Type property in the target object instead of a MonoScript. However, since the target object could (by definition) not store MonoScripts, and the editor wasn't capable of saving per script, there was no way to save the selected script once focus was changed.
    Code (CSharp):
    1. public class ExampleClassEditor : Editor
    2. {
    3.     MonoScript script = null;
    4.     public override void OnInspectorGUI()
    5.     {
    6.         ExampleClass targetObject = (ExampleClass) target;
    7.         EditorGUI.BeginChangeCheck();
    8.         script = (MonoScript)EditorGUILayout.ObjectField("Behaviour Script", script, typeof(MonoScript), false);
    9.         if(EditorGUI.EndChangeCheck())
    10.         {
    11.           //changed 'behaviour' to type 'Type'
    12.            targetObject.behaviour = script.GetClass();
    13.         }
    14.         DrawDefaultInspector();
    15.     }
    16. }
    That last issue might just be my relative inexperience working with custom editors, though.

    Last, and most unappealing, I saw one user suggest using an enum to represent types that inherit from my ExampleClass, populating it via script using reflection, and using the result as a dropdown field in my ScriptableObjects. I was always taught to avoid reflection when possible, so... unless I'm wrong, this is kind of a last resort.

    I'm mostly just wondering if this is possible, or if there's a preferable alternative without significant restructuring? I think I understand why MonoBehaviours can't be directly selected from their associated MonoScripts, but given that it's largely doable using prefabs, it just feels strange and frustrating. I would really like a clean and elegant solution for choosing a MonoBehaviour type to add at runtime.
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,611
    I think for this kind of stuff, a custom editor is always required, AND sometimes custom serialisation will be needed too, though ready made solutions should already exist. I know @Bunny83 has posted their serialisable type wrapper class, so you could look through their posts to find that.

    You could also use SerializeReference to serialise plain classes, which could return a type.
    Code (CSharp):
    1. [System.Serializable]
    2. public abstract TypeProvider
    3. {
    4.     public abstract System.Type Type { get; }
    5. }
    6.  
    7. [System.Serializable]
    8. public class ExampleTypeProvider : TypeProvider
    9. {
    10.     public override System.Type Type => typeof(ExampleType);
    11. }
    And then serialise these into a field using SerializeReference... which will also require a property drawer. Though at that point you might as well use these plain classes for what you need, rather than components.