简体   繁体   中英

Inheriting from generic class that also inherits from a generic singleton

I'm having difficulty inheriting from a generic class that is itself inherited from a generic singleton.

I am trying to make an inventory base class that is a singleton and derive different inventory types from this with different derived items.

Code has been simplified for brevity.

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static bool m_ShuttingDown = false;
    private static object m_Lock = new object();
    private static T m_Instance;

    public static T Instance
    {
        get
        {
            if (m_ShuttingDown)
                return null;
            lock (m_Lock)
            {
                if (m_Instance == null)
                {
                    m_Instance = (T)FindObjectOfType(typeof(T));    
                    if (m_Instance == null)
                    {
                        var singletonObject = new GameObject();
                        m_Instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";

                        DontDestroyOnLoad(singletonObject);
                    }
                }    
                return m_Instance;
            }
        }
    }
    private void OnApplicationQuit()
    {
        m_ShuttingDown = true;
    }
    private void OnDestroy()
    {
        m_ShuttingDown = true;
    }
}

public class Inventory<T> : Singleton<Inventory<T>> where T : Item
{   
    ...
}
public class EquipmentInventory : Inventory<Equipment>
{
    ...
}
public class Item : ScriptableObject
{
    public string Name = "Item";
}
public class Equipment : Item
{
    public string Name = "Equipment";
}

I can't access the Instance;

private EquipmentInventory equipmentInventory;
private Inventory<Item> inventory;

public void Run()
{
    var cachedInventory = Inventory<Item>.Instance;  //returns null
    var cachedEquipmentInventory = EquipmentInventory.Instance as EquipmentInventory;  //returns null
}

Both statements return null.

The purpose of this, is that each inventory type will be a singleton and each type of inventory will be implent different item types, so that the base inventory will use the Item type, while the Equipment inventory will be implemented using the Equipment item type.

Here is an alternate method, which seems to solve this

public abstract class Inventory<T, TClass> 
        : Singleton<TClass> where TClass 
        : MonoBehaviour where T : Item
    {

    }
public class EquipmentInventory : Inventory<Equipment, EquipmentInventory>
    {

    }

I haven't fully tested this yet with actual code, but will update when I have tested it more thoroughly

Please assist.

The main issue is caused by

public class Inventory<T> : Singleton<Inventory<T>> : where T : Item { }

Here you "pass in" the generic type Iventory<T> into the Singleton even though later you explicitly inherit from that Inventory<T> class.


Here is one possible solution though it might seem like a bit strange workaround at first:

Make your Inventory take a second generic type, use the first one inside the class as needed and "forward" the second one to the Singleton<T> like eg

// Note how for the limitation via where you still can use the generic type
// which makes sure no other MonoBehaviour can be passed to TSelf by accident
public class Inventory<TItem, TSelf> : Singleton<TSelf> where TItem : Item where TSelf : Inventory<TItem,TSelf>
{
    public TItem reference;

    private void Awake()
    {
        if (!reference) reference = ScriptableObject.CreateInstance<TItem>();
    }
}

Then in the implementation your pass additionally in your own final (non-generic) type so it can be properly "forwarded" to the Singleton<T> like eg

public class EquipmentInventory : Inventory<Equipment, EquipmentInventory> { }

Note that anyway this class has to be in a separated file called EquipmentInventory.cs otherwise it won't work as component in Unity.


This works now since now you explicitly pass in the type EquipmentInventory for TSelf which is then forwarded to the Signleton<T> so the return type of Instance is explicitly EquipmentInventory .


In general get used to rather have one script file for each individual class/type.

Additionally I would slightly alter your fields in Item and Equipment like eg

[CreateAssetMenu]
public class Item : ScriptableObject
{
    [SerializeField] private string _name;

    public string Name => _name;

    private void Awake()
    {
        _name = GetName();
    }

    protected virtual string GetName()
    {
       return nameof(Item);
    }
}

and

[CreateAssetMenu]
public class Equipment : Item
{
    protected override string GetName()
    {
        return nameof(Equipment);
    }
}

And this is how it looks like eg

public class Example : MonoBehaviour
{
    public EquipmentInventory equipmentInventory;

    [ContextMenu("Run")]
    public void Run()
    {
        equipmentInventory = EquipmentInventory.Instance;
    }
}

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM