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

Creating a MonoBehaviour using the 'new' keyword

Discussion in 'Scripting' started by Ok4j, Apr 26, 2022.

  1. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    I have a script.

    Code (CSharp):
    1. void add_passive_effects()
    2.     {
    3.         foreach (string feat_id in info.feats)
    4.         {
    5.             ability i = find_feats(feat_id);
    6.             if (i)
    7.                 foreach(Effect E in i.EffectList)
    8.                 {
    9.                     //get type
    10.                     System.Type MyScriptType = System.Type.GetType(E.effect_name + ",Assembly-CSharp");
    11.  
    12.                     //construct it so we have target
    13.                     ConstructorInfo magicConstructor = MyScriptType.GetConstructor(System.Type.EmptyTypes);
    14.                     object magicClassObject = magicConstructor.Invoke(new object[] { });
    15.  
    16.                     //find functions and execute them
    17.                     MethodInfo method = MyScriptType.GetMethod("SetParameters");
    18.                     method.Invoke(magicClassObject, new object[] {E.generic_bonus_value});
    19.  
    20.                     MethodInfo method2 = MyScriptType.GetMethod("AddPassiveEffect");
    21.                     method2.Invoke(magicClassObject, new object[] { });
    22.                 }      
    23.         }
    24.     }
    It is used to add some passive effects to a players character. MyScriptType is a MonoBehaviour with functions SetParameters() and AddPassiveEffect().

    When I run the code I get the warning: You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all.

    My question: Is what I'm doing dangerous or bad to do? Are there problems with garbage collection I have to worry about? Are these problems fixable in some way?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,844
    Yes, it is dangerous to do so. They're unmanaged and have C++ aspects in the native engine side to be considered as well. They don't fit within the confines of general c# object instantiation and their lifetimes. If you want the type of a component you should use the typeof keyword.

    In any case... there has to be better ways to achieve what you're doing rather than this roundabout method. You have a list of effects then you use reflection to get a method and invoke it??? Is that really necessary? Can you not just use a shallow inheritance hierarchy?
     
  3. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    Yes there are other ways of doing this. This is kinda my first 'bigger' project so there's probably ways of doing these things I haven't thought about.

    I've been scratching my head around this problem for a while now and every solution I could come up with needed some some long list of prefabs, or components usually in combination with a long switch case script. I wanted to avoid this since it would get very very long after a while. And the number of prefabs that has to be instantiated will also get very long depending on the number of passives.

    Is there a way of deleting the objects after they have been created? Would this make it less dangerous?
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,844
    Note the part of the warning "This is not allowed". It should read: You should not do this. At. All.

    Reflection should be an absolute last resort. If you have a question about how to design games systems, you should create a thread about that problem specifically to see what ideas you can garner.

    My first advice would be to look into scriptable objects. They're usually the first stop when in comes to inverting responsibility.
     
  5. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    you've already got a list of effect, why not reference the relevant prefab in there directly ? it wouldn't be a new list to manage, just prefab ref instead of a string with the name of the matching class...

    it would be new prefabs tho that's for sure, so if you really really want to avoid this, you can use AddComponent with the string name of the class:

    https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html
     
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Well, what kind of information do you actually want to extract from the class? Since you create a blank object it means the data you want to extract has to be static / constant data anyways. So it might make more sense to use an attribute on the class or a static method which you can access without the need of creating an instance. In essence you try to use this class for things it's not meant to be used for. MonoBehaviours are component behaviour scripts which live on gameobjects. So creating one on the fly makes no sense.

    What do the methods "SetParameters" and specifically "AddPassiveEffect" actually do? It seems you have a lot of hidden information in your logic which is exactly the reason why using things like singletons is often said to be bad because you can have magic cross interaction behind the scenes that no body can reason about without knowing the whole codebase.

    If you need and further help with this we need to have more context.
     
  7. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    I wanted to avoid either having a long list of prefabs to be instantiated, or a long list of switches where I check which component I'm supposed to add. That would also create a long list of components. Its possible that I have to do it this way however.

    SetParameters() initializes some parameters in the newly created MonoBehaviour object, namely an int called generic_bonus_value. This is initialized with the specific data from the Effect object, which is a scriptableObject that has an int generic_bonus_value and an effect_name which is a string corresponding to the MonoBehaviour I want to create. AddPassiveEffect() is a function on the MonoBehaviour object that is unique to every different effect. In the project there are many different passives and they all do different things, some add +1 to a number, some gives you a third arm.

    Basically the idea is that I want to find the effects of any passive ability and execute its effects, without having to go through long lists where I check every effect in the project, or having to instantiate long lists of prefabs for every effect. However if this it that dangerous to do I might just to do that instead.
     
    Last edited: Apr 26, 2022
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,844
    Why not just use plain classes to hold the logic and parameters for your passive abilities? Or code the passive ability logic into the scriptable objects and pass around references to those?
     
  9. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    I was just thinking about making it plain classes actually, will try that. I tried having the logic in the scriptable objects themselves, honestly I can't remember why that didn't work. However I remember testing it and running into problems with getting the correct scriptableObject, or something around those lines, which led me back to a giant switch case list.
     
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    But what is the point of creating this dead MonoBehaviour instance in memory in the first place? This instance can't do anything as it has absolutely zero connection to the Unity engine. It's just a dead monobehaviour instance. So what does "AddPassiveEffect" actually do? What objects does it interact with? Where are those objects referenced?

    As I said if this is just some sort of initializer function that has no relation to a specific instance of your class, this method should be static. If it actually belongs to the instance, you have to create a proper instance using AddComponent on a gameobject. You have your script type ( MyScriptType ) so you can use AddComponent on a gameobject to actually attach this script to that gameobject. That would create a proper component instance. Maybe that's what you want to do? Also if those methods you try to call have a fix signature, why don't you use an interface for those? Reflection in general is a sign of a bad design. There are some rare edge cases where reflection may be the only viable solution but in most cases there are alternatives.

    Just to be clear, you can do

    Code (CSharp):
    1. ISomeInterface inst = (ISomeInterface)gameObject.AddComponent(MyScriptType);
    This assumes that all potential components would implement this interface. So an interface like this

    Code (CSharp):
    1. public interface ISomeInterface
    2. {
    3.     void SetParameters(int aBonusValue);
    4.     void AddPassiveEffect();
    5. }
    Now you can directly call those methods in your "inst". Of course that component would now live on the "gameObject". Though as I already said, it's not clear where your MonoBehaviour should be attached to.

    Anyways, you can not create MonoBehaviour instances with "new" and somehow attach it later to a gameobject, only through AddComponent.
     
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    I just read your last reply:
    So the class does not actually represent a behaviour script for gameobjects after all? If that's the case, yes you can create ordinary C# classes which are not components with reflection. However if you're planning to do this I would highly recommend to use the Activator class to create your instance. Manually searching for constructors is not recommended.

    So for ordinary plain C# classes you would do

    Code (CSharp):
    1. System.Activator.CreateInstance(MyScriptType));
    I would still highly recommend that you use either a base class or an interface for calling your methods. It's much safer, faster and more reliable.
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,844
    These are the questions you should be making threads for.
     
  13. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    The MonoBehaviours I create actually implement an interface IPassiveEffect so this could work. Thank you. It is weird that "This instance can't do anything as it has absolutely zero connection to the Unity engine." because the script works and all the effects trigger as they should.

    Here is an example of one:

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class fortitudeBonus : effect_variable_archive, IPassiveEffect
    7. {
    8.     public void AddPassiveEffect()
    9.     {
    10.         FindCharacterInfo();
    11.         character_info.fortitude_save += generic_bonus_value;
    12.     }
    13. }
    14.  
    15.  
    Which inherits from:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class effect_variable_archive : MonoBehaviour
    6. {
    7.     public int generic_bonus_value;
    8.  
    9.     public populate_mainscene manager;
    10.     public character_info character_info;
    11.  
    12.     public void FindManager()
    13.     {
    14.         manager = FindObjectOfType<Canvas>().GetComponent<populate_mainscene>();
    15.     }
    16.     public void FindCharacterInfo()
    17.     {
    18.         character_info = FindObjectOfType<character_info>().GetComponent<character_info>();
    19.     }
    20.     public void SetParameters(int generic_bonus) //if updated: update pop_mainscene as well
    21.     {
    22.         generic_bonus_value = generic_bonus;
    23.     }
    24. }
    The IPasssiveEffect interface, at the moment this isn't really used, it was made when I was trying out different solutions:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public interface IPassiveEffect
    6. {
    7.     void AddPassiveEffect();
    8. }
    9.  
     
  14. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    I got them working as interfaces instead. Thank you all for the help!
     
  15. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Because you don't actually implemented anything even remotely related to the concept of a behaviour component. A MonoBehaviour implements various engine callbacks as you can find here. Also it provides context sensitive methods like GetComponent or properties like "name", "tag", "transform", etc which would all not work on your "dead" frankenstein instance when you create it with new.

    So yes, your class is a pure data class and there's absolutely no reason to derive it from MonoBehaviour. The concept and purpose of components is briefly explained here and here. You can still use just ordinary C# classes alongside Unity. I've seen even a large MMO made in Unity that only had a few actual components that act as glue between the engine and their own MVC concept. All the networking and business logic was pure C# code and there were only a few components which interacted with actual gameobjects. It's possible to do this, but you're loosing a lot of great debugging utilities built into Unity. The great thing about gameobjects and components is that you can select them even at runtime and inspect them in the inspector. Your own C# classes of course have no relation to the engine. They don't show up anywhere, they are just used by your code.
     
  16. Ok4j

    Ok4j

    Joined:
    Oct 5, 2021
    Posts:
    9
    I see. Never really thought about it, thanks for the explanation :)
     
  17. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    74
    You can't instantiate MonoBehavior unfortunately , and depends on your use case you can use the method below and without having it attached or added as component. Very useful for customEditors when working with Graph tooling or making addons that can be loaded from assets folder.

    Note: This is not recommended for Runtime use cases. CustomEditor is fine!


    Code (CSharp):
    1.        
    2. var assets = AssetDatabase.FindAssets("t:MonoScript", new[] { "Assets/somePathHere" });
    3.  
    4.         foreach (var guid in assets)
    5.         {
    6.             var e = AssetDatabase.LoadAssetAtPath<MonoScript>(AssetDatabase.GUIDToAssetPath(guid));
    7.  
    8.             if (e != null)
    9.             {
    10.                 var getMonoClass = e.GetClass();          
    11.                 var obj = FormatterServices.GetUninitializedObject(getMonoClass);
    12.                
    13.                //Your usual reflection code here
    14.             }
    15.         }
    16.  
    17.  
     
  18. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    But the question remains: Why? Why do you implement them as a MonoBehaviour? Creating an uninitialized instance means no field of the class is initialized. You essentially abusing a MonoBehaviour class like a normal C# class. Why don't you just implement a normal C# class and use reflection to find all classes that implement a certain interface, have a certain attribute or whatever.

    You said
    In such a case ScriptableObject would be the main choice here because those can be created with CreateInstance properly and those are not attached to gameobjects. Why MonoBehaviour?

    Note that almost anything in the Unity editor are ScriptableObjects. The EditorWindow and Editor classes are derived from ScriptableObject. Therefore they are properly tracked instances that can be serialized / deserialized and can "survive" an assembly reload or playmode changes.
     
  19. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    74
    Sure, but when you want to make Add-on system, where users can just drop their classes into a folder then have them listed in a ListView for example, or showed anywhere else automagically :) you can just make customAttributes, pull them out via reflections, then bam!..Very useful in many situations.. and as said, just for customEditors
     
    Last edited: Jun 9, 2022
  20. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,844
  21. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    74
    Simple, bcos of this
    Code (CSharp):
    1. The returned MethodCollection is read-only and thread-safe.
     
  22. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    How is that a reason? The collection being thread safe is a good thing. Being readonly only makes sense since you can't really add new classes or remove old ones, unless your code is recompiled and reloaded. Also why do you need an instance when it's just about displaying a list of the available classes? As already said, uninitialized instances do not contains any data (even read-only variables are not initialized). So what do you use the instance for? If you need metadata you would work with attributes attached to a class / type which you can access through reflection.

    This is like ordering a pizza just to throw the pizza away because you only want the delivery box to store nails and screws in it. You can do that but hopefully we agree that's not what you should do.
     
  23. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    74
    You can do bunch of things in the not-read-only manner, hope that would explain things...
     
  24. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,844
    Then just copy the collection into your own collection???
     
    Bunny83 likes this.
  25. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    I'm not sure he even realises that System.Type objects are readonly anyways as they are just metadata. You can't change anything in a System.Type object. You can use one to access and modify static fields of the type it describes, but the Type object is part of the reflection system and is read only.