Search Unity

MonoBehaviour container with generic type

Discussion in 'Scripting' started by jister, Mar 2, 2015.

  1. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    I'm trying to make a Monobehaviour container that holds a instance of a custom serialized class.
    I'd like to keep the instance generic so that i can use it for all my custom serialized classes.
    but keep running into to errors...
    easiest way would be to keep the MonoBehaviour generic but thats doesn't seem to be supported.
    tried a custom Generic class but seems that i have to give it a type in the monobehaviour so no use there.
    tried same with an interface and some other things but i keep hitting a wall.
    any way of doing this right?
    Code (CSharp):
    1.  
    2. public class GenericType<T>
    3. {
    4.     public T instance;
    5.  
    6.     public void SetInstance<T>(T reference)
    7.     {
    8.         instance = (T)reference;
    9.     }
    10. }
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Container : MonoBehaviour
    5. {
    6.     public GenericType gType; //asking for a type
    7.  
    8.     public T GetInstance<T>()
    9.     {
    10.         return gType.instance;
    11.     }
    12.  
    13. }
     
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You're not passing T to your container anywhere

    Code (csharp):
    1.  
    2. public class Container<T> : MonoBehaviour
    3. {
    4.     public GenericType<T> gType;
    5.  
    6.     public T GetInstance()
    7.     {
    8.         return gType.instance;
    9.     }
    10. }
    11.  
    Also note that the cast to T in your SetInstance is unnecessary.
     
  3. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    yes but that was my first problem... you can't set monobehaviour as generic :-(
     
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    That's true. You can simply have your generic container class maintain a reference to some MonoBehaviour in that case.
     
  5. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    well i have a CharacterCreator that spits out Enemies, Players and Friendlies each having a custom serialized non monobehaviour class instance.
    I'd like the CharacterCreator to attach a monobehaviour container to the GameObject with a reference to this instance...
    the script below would be a great way to do it, if monobehaviour would allow generics. sadly enough i can't seem to find an easy workaround :-s
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Container<T> : MonoBehaviour
    5. {
    6.     public T instance;
    7.  
    8.     public T GetInstance<T>()
    9.     {
    10.         return instance;
    11.     }
    12.  
    13.     public void SetInstance<T>(T reference)
    14.     {
    15.         instance = reference;
    16.     }
    17.  
    18. }
    19.  
     
  6. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You could do something like this

    Code (csharp):
    1.  
    2. public class Container<T, M> where T : ScriptableObject where M : MonoBehaviour
    3. {
    4.     public T Data { get; set; }
    5.     public M Script { get; set; }
    6.  
    7.     public Container()
    8.    {
    9.         GameObject g = new GameObject();
    10.         Script = g.AddComponent<M>();
    11.    }
    12. }
    13.  
     
  7. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    ok i tried it like this:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Container<T, M> where T: Character where M : MonoBehaviour
    5. {
    6.     public T Data { get; set; }
    7.     public M Script { get; set; }
    8.  
    9.     public Container(T d, M s)
    10.     {
    11.         GameObject g = d.characterObject;
    12.         Script = g.AddComponent<M>();
    13.     }
    14.  
    15. }
    16.  
    and trying to call it like this:
    Code (CSharp):
    1. Player p = new Player(characterObject,characterName,walkSpeed,damage,health,abilityList,ability);
    2.             Container<Player, Container> c = new Container<Player, Container>(ref p, ref c);
    and for some reason i keep getting :
    Code (CSharp):
    1. Using the generic type `Container<T,M>' requires `2' type argument(s)
    also i don't quite see how i would have a monobehaviour container script that has acces to the Data instance?
     
  8. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Why are you explicitly passing reference types by reference? Also - you're trying to pass c as an argument in its own constructor.Which - aside from making no sense - doesn't fit the constraint on M (it's not a MonoBehaviour)
     
  9. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    even if i remove all that and just use this:
    Code (CSharp):
    1. public class Container<T, M> : MonoBehaviour where T: Character where M : MonoBehaviour
    2. {
    3.     public T Data { get; set; }
    4.     public M Script { get; set; }
    5.    
    6. //    public Container(T d, M s)
    7. //    {
    8. //        GameObject g = d.characterObject;
    9. //        Script = g.AddComponent<M>();
    10. //    }
    11.  
    12. }
    and try to make a new instance of container:
    Code (CSharp):
    1.             Container<Player, Container> c = new Container<Player, Container>();
    it keeps saying :
    Code (CSharp):
    1. Using the generic type `Container<T,M>' requires `2' type argument(s)
    2.  
     
  10. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    When you specify Container as M you're not giving it T and M.

    Why are you trying to pass Container as a parameter of Container? My suggestion was to have Script (M) be a separate MonoBehaviour derived class that does your game logic, not for Container to do both.
     
  11. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    ah ok is see... well i wanted the Container to have the reference to a Character derived class instance like Player...
    guess I'll just skip it being generic... :-(
     
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You can do that

    Code (csharp):
    1.  
    2. public class Container<T> where T : Character
    3. {
    4.     public T Instance { get; private set; }
    5.  
    6.     public Container(T instance)
    7.     {
    8.         this.Instance = instance;
    9.     }
    10. }
    11.  
    Code (csharp):
    1.  
    2. Player player = new Player();
    3. Container<Player> container = new Container<Player>(player);
    4.  
     
  13. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    ah and for Container to be a MonoBehaviour ;-)
    to bad MonoBehaviour has some restriction towards basic coding concepts... I prefer handling Objects in code more than the use of components. Or maybe i just don't see the power of MonoBehaviours...
     
  14. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    MonoBehaviours can be generic, you just have to inherit from an actual concrete type before you can attach it to anything or see it in the editor (how else would the editor know what "T" is supposed to be?) Like this:

    Code (csharp):
    1. public class Container<T> : MonoBehaviour
    Code (csharp):
    1. public class Player : Container<Player>
     
    coatline and roddles like this.
  15. Korno

    Korno

    Joined:
    Oct 26, 2014
    Posts:
    518
    I actually do something similar to allow my level buttons to share the same level loading functionality, even though there are many mission types:

    Code (CSharp):
    1. public class MissionGeneric<T> : MonoBehaviour  where T : MissionBase {
    2.  
    3.     [SerializeField]
    4.     public T Mission;
    5.  
    6.     protected Button mButton;
    7.  
    8.     // Use this for initialization
    9.     void Start () {
    10.         mButton = GetComponent<Button>();
    11.         mButton.onClick.AddListener(OnClick);
    12.     }
    13.  
    14.  
    15.     public void OnClick()
    16.     {
    17.         GameStateManager.TheMission = Mission;
    18.         Application.LoadLevel(Mission.LevelToLoad);
    19.     }
    20.  
    21.  
    This is my generic select level button, as you can see it just loads the level using the mission information supplied in T - which will 100% be from a MissionBase derived class.

    For my many types of missions - i just have empty MonoBehaviours -

    Code (CSharp):
    1. public class SurviveMissionButton : MissionGeneric<SurviveMission> {
    2.  
    3.  
    4. }
    This allows me to edit the mission type specific parameters in the editor and not have to worry about setting up the level loading code each time I add a new mission type.