如何让 Unity singleton base class 支持泛型?
How to make Unity singleton base class to support generics?
在我的项目中,我有一个 class 结构,如图所示。
绿色classes是旧代码,运行良好。红框里的classes是新加的代码。没有编译错误,但是在Unity中点击播放运行到新代码时,三个class无法正确初始化。
Unity 控制台在这一行发出警告,指出 "The class named 'DataMgrBase`2' is generic. Generic MonoBehaviours are not supported! UnityEngine.GameObject:AddComponent()":"instance = obj.AddComponent ();"
我该如何解决这个问题?
下面是一些代码供大家参考,谢谢!
单例基础的实现class:
using UnityEngine;
using System.Collections;
public class UnitySingletonPersistent<T> : MonoBehaviour where T : Component
{
private static T instance;
public static T Instance {
get {
if (instance == null) {
instance = FindObjectOfType<T> ();
if (instance == null) {
GameObject obj = new GameObject ();
obj.name = typeof(T).Name;
obj.hideFlags = HideFlags.DontSave;
instance = obj.AddComponent<T> ();
}
}
return instance;
}
}
public virtual void Awake ()
{
DontDestroyOnLoad (this.gameObject);
if (instance == null) {
instance = this as T;
} else {
Destroy (gameObject);
}
}
}
DataMgrBase 的实现:
public class DataMgrBase<TKey, TValue>: UnitySingletonPersistent<DataMgrBase<TKey, TValue>> {
protected Dictionary<TKey, TValue> dataDict;
public override void Awake()
{
base.Awake();
dataDict = new Dictionary<TKey, TValue>();
}
public TValue GetDataForKey(TKey key)
{
TValue data;
if (dataDict.TryGetValue(key, out data))
{
return data;
}
else
{
data = LoadDataForKey(key);
if (data != null)
{
dataDict.Add(key, data);
}
return data;
}
}
virtual protected TValue LoadDataForKey(TKey key)
{
if (dataDict.ContainsKey(key))
{
return GetDataForKey(key);
}
else
{
return default(TValue);
}
}
}
你想要这样的东西:
public abstract class UnitySingletonPersistent<T> : MonoBehaviour where T:UnitySingletonPersistent<T>
{
...
}
然后在你的具体class:
public class DataMgrBase<TKey, TValue> : UnitySingletonPersistent<DataMgrBase<TKey, TValue> >
{
...
}
这个答案并不能解决您的问题,但可以解释问题。
MonoBehaviour
至少有两个原因不能通用:
1. 假设您想从 Unity3D 编辑器在 Inspector 中添加通用组件。现在引擎需要确切地知道这个组件中的所有类型,这不仅是因为它会在这一刻被编译,而且还因为你可能有 public 个具有未删除类型的字段。尝试直接在 Inspector 中分配你的 UnitySingletonPersistent
,你会发现这是不可能的。
2. 使用 AddComponent<T>
其中 T
是通用的看起来可以工作,而且在这个引擎中你可以从实例化中制作所谓的预制件GameObjects,如果这个 GameObject 包含通用组件,Unity3D 引擎将需要支持某种烘焙类型,而在实践中,这将导致生成脚本,每个脚本都有不同的类型,并在项目中造成大混乱。希望大家关注我
但为什么它适用于您标记为绿色的组件?简单地导致 Unity3D 引擎在将此组件添加到 GameObject 时知道所有类型。
要支持所有这些,Unity Technologies 需要对 Unity3D 引擎及其现在的工作方式进行核心更改。它将使 Unity3D 完全不同于现在的引擎。
所以要解决你的问题,只有一种方法:不添加运行时通用组件,去掉DataMgrBase
class。所以你需要在每个组件中实现 DataMgrBase
逻辑。
我自己解决了如下:
更改基础 class 以获得新的泛型(class 的泛型类型,将从它派生,并将此类型传递给单例基础 class)
public class DataMgrBase<TKey, TValue, TClass>: UnitySingletonPersistent<TClass> where TClass: Component
对于所有其他三个 class 想要从它派生的,将它们更改为以下形式:
public class MobSettingDataMgr : DataMgrBase<int, MobSettingData, MobSettingDataMgr>
在我的项目中,我有一个 class 结构,如图所示。
绿色classes是旧代码,运行良好。红框里的classes是新加的代码。没有编译错误,但是在Unity中点击播放运行到新代码时,三个class无法正确初始化。
Unity 控制台在这一行发出警告,指出 "The class named 'DataMgrBase`2' is generic. Generic MonoBehaviours are not supported! UnityEngine.GameObject:AddComponent()":"instance = obj.AddComponent ();"
我该如何解决这个问题?
下面是一些代码供大家参考,谢谢!
单例基础的实现class:
using UnityEngine;
using System.Collections;
public class UnitySingletonPersistent<T> : MonoBehaviour where T : Component
{
private static T instance;
public static T Instance {
get {
if (instance == null) {
instance = FindObjectOfType<T> ();
if (instance == null) {
GameObject obj = new GameObject ();
obj.name = typeof(T).Name;
obj.hideFlags = HideFlags.DontSave;
instance = obj.AddComponent<T> ();
}
}
return instance;
}
}
public virtual void Awake ()
{
DontDestroyOnLoad (this.gameObject);
if (instance == null) {
instance = this as T;
} else {
Destroy (gameObject);
}
}
}
DataMgrBase 的实现:
public class DataMgrBase<TKey, TValue>: UnitySingletonPersistent<DataMgrBase<TKey, TValue>> {
protected Dictionary<TKey, TValue> dataDict;
public override void Awake()
{
base.Awake();
dataDict = new Dictionary<TKey, TValue>();
}
public TValue GetDataForKey(TKey key)
{
TValue data;
if (dataDict.TryGetValue(key, out data))
{
return data;
}
else
{
data = LoadDataForKey(key);
if (data != null)
{
dataDict.Add(key, data);
}
return data;
}
}
virtual protected TValue LoadDataForKey(TKey key)
{
if (dataDict.ContainsKey(key))
{
return GetDataForKey(key);
}
else
{
return default(TValue);
}
}
}
你想要这样的东西:
public abstract class UnitySingletonPersistent<T> : MonoBehaviour where T:UnitySingletonPersistent<T>
{
...
}
然后在你的具体class:
public class DataMgrBase<TKey, TValue> : UnitySingletonPersistent<DataMgrBase<TKey, TValue> >
{
...
}
这个答案并不能解决您的问题,但可以解释问题。
MonoBehaviour
至少有两个原因不能通用:
1. 假设您想从 Unity3D 编辑器在 Inspector 中添加通用组件。现在引擎需要确切地知道这个组件中的所有类型,这不仅是因为它会在这一刻被编译,而且还因为你可能有 public 个具有未删除类型的字段。尝试直接在 Inspector 中分配你的 UnitySingletonPersistent
,你会发现这是不可能的。
2. 使用 AddComponent<T>
其中 T
是通用的看起来可以工作,而且在这个引擎中你可以从实例化中制作所谓的预制件GameObjects,如果这个 GameObject 包含通用组件,Unity3D 引擎将需要支持某种烘焙类型,而在实践中,这将导致生成脚本,每个脚本都有不同的类型,并在项目中造成大混乱。希望大家关注我
但为什么它适用于您标记为绿色的组件?简单地导致 Unity3D 引擎在将此组件添加到 GameObject 时知道所有类型。
要支持所有这些,Unity Technologies 需要对 Unity3D 引擎及其现在的工作方式进行核心更改。它将使 Unity3D 完全不同于现在的引擎。
所以要解决你的问题,只有一种方法:不添加运行时通用组件,去掉DataMgrBase
class。所以你需要在每个组件中实现 DataMgrBase
逻辑。
我自己解决了如下:
更改基础 class 以获得新的泛型(class 的泛型类型,将从它派生,并将此类型传递给单例基础 class)
public class DataMgrBase<TKey, TValue, TClass>: UnitySingletonPersistent<TClass> where TClass: Component
对于所有其他三个 class 想要从它派生的,将它们更改为以下形式:
public class MobSettingDataMgr : DataMgrBase<int, MobSettingData, MobSettingDataMgr>