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

How to properly implement a generic class so that its type can be defined in the inspector?

Discussion in 'Scripting' started by ghostravenstorm, May 26, 2017.

  1. ghostravenstorm

    ghostravenstorm

    Joined:
    Oct 18, 2015
    Posts:
    88
    I have a base class here, and 3 types that derive from it. I want any of these types to be spawnable during runtime so I need a method that can create them. Obviously I don't want to build a spawner class for each type. That's seriously redundant.

    Code (CSharp):
    1. public class Monster{}
    2.  
    3. public class Demon : Monster{}
    4.  
    5. public class Ghost : Monster{}
    6.  
    7. public class Zombie : Monster{}
    So I have here a generic class that can be of any type with a Spawn method that
    creates the type. Unity doesn't accept a generic class

    Code (CSharp):
    1. public class MonsterSpawner<T> where T : new(){
    2.  
    3.    public T Spawn(){
    4.       return new T();
    5.    }
    6. }
    7.  
    8. // Hardcoded method where designers would have to alter this code if zombieSpawner
    9. // were to be something else.
    10. public class MonsterSpawnerBehaviour : MonoBehaviour{
    11.    
    12.    public MonsterSpawner<Zombie> zombieSpawner;
    13.    
    14.    private void Start(){
    15.      this.zombieSpawner = new MonsterSpawner<Zombie>();
    16.    }
    17. }
    18.  
    19. // Generic where T is defined in inspector from a type currently in the
    20. // assembly.
    21. public class MonsterSpawnerBehaviour : MonoBehaviour{
    22.  
    23.    public MonsterSpawner<T> monsterSpawner;
    24.  
    25.    private void Start(){
    26.       this.zombieSpawner = new MonsterSpawner<T>();
    27.    }
    28. }
    29.  
    30. // Another hardcoded method that switches based on another variable to set the
    31. // correct type.
    32. public class MonsterSpawnerBehaviour : MonoBehaviour{
    33.  
    34.    
    35.    private MonsterSpawner<Monster> monsterSpawner;
    36.    public EMonsterType monsterType;
    37.  
    38.    private void Start(){
    39.  
    40.       /*
    41.       If I'm already determining what type of spawner this is like this,
    42.       then what's the point of using a generic class?
    43.       */
    44.       switch(monsterType){
    45.          case EMonsterType.Demon:{
    46.             monsterSpawner = new MonsterSpawner<Demon>();
    47.          }
    48.       }
    49.    }
    50. }
    What makes me sick about any 3 of the above implementation is that it still requires the use of a third datatype for the sake of extending a MonoBehaviour type so the Spawner can physically exist since generic classes can't extend MonoBehaviour at all.

    If I really wanted to eliminate that third type, then I'd settle on the third implementation of my Spawner where I use a switch and make the behaviour class the actual spawner class.

    Another why I'm thinking in to just have some master class, like the main class in any C# or Java program, where I have my Spawner references as class fields and make a new instance for each one based on what I want the design of the level to be. This approach makes it unfriendly for designers though.

    Code (CSharp):
    1. public class GameManager : MonoBehaviour{
    2.  
    3.    private MonsterSpawner<Zombie> zombieSpawner1;
    4.    private MonsterSpawner<Zombie> zombieSpawner2;
    5.    private MonsterSpawner<Zombie> zombieSpawner3;
    6.    private MonsterSpawner<Demon> demonSpawner;
    7.    private MonsterSpawner<Ghost> ghostSpawner;
    8.  
    9.    private void Start(){
    10.        zombieSpawner1 = new MonsterSpawner<Zombie>();
    11.        zombieSpawner2 = new MonsterSpawner<Zombie>();
    12.        zombieSpawner3 = new MonsterSpawner<Zombie>();
    13.        demonSpawner = new MonsterSpawner<Demon>();
    14.        ghostSpawner = new MonsterSpawner<Ghost>();
    15.    }
    16.  
    17. }
     
  2. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    I don't really get what you want to achieve.
    Why do you need generics? Usually a Monster to be spawned by Unity is a prefab. With generics you can only spawn "classes" (no game objects).
    Why not define an enum in a non-generic monster spawner class and let the user select the type he wants to be spawned? Or even better just assign the prefab which shall to be spawned to any monster spanwer instance so you can reuse it for any type in future.
    Anyway. I think you are thinking WAY too complicated or you want to make somthing in a nun-Unity way (means against Unity's workflow) in which case you should have a good explanation why you do it this way.
     
  3. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    First off unity does support Generic classes. MonoBehaviour and ScriptableObject can be generic, the problem is that Unity doesn't support generic serialization (with List being the exception) so it can't draw them as generic and they can't be standalone components on a Gameobject. Thus any Monobehaviour/ScriptableObject that is to be generic must also at least be abstract.

    you may not even need to use generics. your monsterspawner could simply reference a prefab to spawn then the return the instance as the abstract monster type (assuming you meant that the Monster class is an abstract monobehaviour).

    Code (CSharp):
    1. [system.Serializable]public class MonsterSpawner
    2. {
    3.     public GameObject monsterPrefab;
    4.     public Transform spawnpoint;
    5.  
    6.     public Monster Spawn()
    7.     {
    8.         if(!monsterPrefab) return null;
    9.  
    10.         var hasMonsterScript = monsterPrefab.GetComponent<Monster>();
    11.         if(!hasMonsterScript) return null;
    12.  
    13.         var position = (spawnpoint)? spawnpoint.position: Vector3.zero;
    14.         var rotation = (spawnpoint)? spawnpoint.rotation: Quaternion.Identity;
    15.         var mob = (GameObject) GameObject.Instantiate(monsterPrefab,position, rotation);
    16.  
    17.         return mob.GetComponent<Monster>();
    18.     }
    19. }
    20.  
    21. public class GameManager: MonoBehaviour
    22. {
    23.     public List<MonsterSpawner> spawners;
    24. }
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You can't serialize generics. You can however serialize no generic classes that inherit from generics. So this:

    Code (CSharp):
    1. // This is not serializable
    2. public class MyGeneric <T> : MonoBehaviour {
    3.     // Some cool implementation
    4. }
    5.  
    6. // But add this empty class and now you can serialize it
    7. public class MyGenericInt : MyGeneric<int> {}
    But plus one for everyone's comments asking why you need generics in the first place. The normal spawner structure simply uses a prefab.