Shorthand 对于单例实例,在某些 类 中给出 NullException
Shorthand for singleton instance, giving NullException in some classes
我在 Unity 工作,有一个名为 GameManager 的单例,其中包含一个名为 GameSettings 的对象,我想从其他 classes:
public class GameManager : MonoBehaviour {
public static GameManager instance;
public GameSettings gameSettings;
void Awake() {
if(instance != null)
GameObject.Destroy(instance);
else
instance = this;
DontDestroyOnLoad(this);
}
}
例如,我可以使用 GameManager.instance.gameSettings.offlineMode
从另一个 class 到达它。但我想要一种更简单的写法,让代码更清晰,所以在另一个 class 中我做了:
public class Health : Photon.MonoBehaviour {
private GameManager gm;
void Awake(){
// Get components
anim = transform.GetChild(0).GetComponent<Animator>();
unitController = GetComponent<UnitController>();
gm = GameManager.instance;
}
}
那我就可以用gm.gameSettings.offlineMode
了。这在大多数 classes 中运行良好,但在一个 class 中它不起作用,并给我 NullReferenceException: Object reference not set to an instance of an object.
为什么会发生这种情况,而且只有一个 class?我应该检查什么,"shorthand" 这样的单例实例是个坏主意吗?
发生这种情况是因为 Script Execution Order
。
进入
Edit -> Project Settings -> Script Execution Order
按 +
、select 您的 GameManager
并将其向上拖动 "before" 默认时间,或将值更改为低于 0 的值。现在,您的 GameManagers Awake()
将在其他脚本 Awake()
之前执行
另一种方法是始终在 Awake()
中分配单例,但是当您在其他 类 中获取引用时,您会在 Start()
中执行此操作
我的猜测是您的 Health class 在 GameManager class 之前执行。您可以尝试使用 execution order,或者只是移动将实例分配给 Start() 方法的代码行。
应该是执行顺序的问题吧。某些脚本在实际创建此对象之前尝试访问对象。请注意,除非您不在项目设置中进行设置,否则您无法控制唤醒函数的执行顺序。有很多方法可以解决它。您可以在 Start 函数中分配您的引用,该函数始终在 Awake 之后调用,因此您将确保您想要访问的对象已经创建。另一种方法是更改项目设置中的执行顺序(最坏的解决方案)或使用延迟加载。
延迟加载对于单例来说非常方便,但这不是最佳实践。我宁愿建议您避免单例并在一个中心点初始化所有服务。通过构造函数传递依赖比单例模式要清晰得多。你也可以使用一些依赖注入框架,比如 zenject。
此处的脚本执行顺序有误,但我发现您的代码存在另一个问题:
if(instance != null)
GameObject.Destroy(instance);
else
instance = this;
假设您在某个时候得到两个实例。
- 第一个副本将
instance
视为 null 并落入 else
语句并将实例设置为其自身
- 第二个副本将
instance
设置为第一个副本并... 销毁它(然后退出,使 instance
等于 null !).
请注意,此处的 "second copy" 可能是第一个副本出于任何原因第二次执行 Awake()
。
我在 Unity 工作,有一个名为 GameManager 的单例,其中包含一个名为 GameSettings 的对象,我想从其他 classes:
public class GameManager : MonoBehaviour {
public static GameManager instance;
public GameSettings gameSettings;
void Awake() {
if(instance != null)
GameObject.Destroy(instance);
else
instance = this;
DontDestroyOnLoad(this);
}
}
例如,我可以使用 GameManager.instance.gameSettings.offlineMode
从另一个 class 到达它。但我想要一种更简单的写法,让代码更清晰,所以在另一个 class 中我做了:
public class Health : Photon.MonoBehaviour {
private GameManager gm;
void Awake(){
// Get components
anim = transform.GetChild(0).GetComponent<Animator>();
unitController = GetComponent<UnitController>();
gm = GameManager.instance;
}
}
那我就可以用gm.gameSettings.offlineMode
了。这在大多数 classes 中运行良好,但在一个 class 中它不起作用,并给我 NullReferenceException: Object reference not set to an instance of an object.
为什么会发生这种情况,而且只有一个 class?我应该检查什么,"shorthand" 这样的单例实例是个坏主意吗?
发生这种情况是因为 Script Execution Order
。
进入
Edit -> Project Settings -> Script Execution Order
按 +
、select 您的 GameManager
并将其向上拖动 "before" 默认时间,或将值更改为低于 0 的值。现在,您的 GameManagers Awake()
将在其他脚本 Awake()
另一种方法是始终在 Awake()
中分配单例,但是当您在其他 类 中获取引用时,您会在 Start()
我的猜测是您的 Health class 在 GameManager class 之前执行。您可以尝试使用 execution order,或者只是移动将实例分配给 Start() 方法的代码行。
应该是执行顺序的问题吧。某些脚本在实际创建此对象之前尝试访问对象。请注意,除非您不在项目设置中进行设置,否则您无法控制唤醒函数的执行顺序。有很多方法可以解决它。您可以在 Start 函数中分配您的引用,该函数始终在 Awake 之后调用,因此您将确保您想要访问的对象已经创建。另一种方法是更改项目设置中的执行顺序(最坏的解决方案)或使用延迟加载。 延迟加载对于单例来说非常方便,但这不是最佳实践。我宁愿建议您避免单例并在一个中心点初始化所有服务。通过构造函数传递依赖比单例模式要清晰得多。你也可以使用一些依赖注入框架,比如 zenject。
此处的脚本执行顺序有误,但我发现您的代码存在另一个问题:
if(instance != null)
GameObject.Destroy(instance);
else
instance = this;
假设您在某个时候得到两个实例。
- 第一个副本将
instance
视为 null 并落入else
语句并将实例设置为其自身 - 第二个副本将
instance
设置为第一个副本并... 销毁它(然后退出,使instance
等于 null !).
请注意,此处的 "second copy" 可能是第一个副本出于任何原因第二次执行 Awake()
。