我在Unity中工作,有一个称为GameManager的单例,其中包含一个名为GameSettings的对象,我想从其他类中访问该对象:
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
从另一个班级获得它。但是我想要一种更简单的编写方式,以使代码更清晰,因此在另一类中,我做到了:
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
。在大多数类中,此方法效果很好,但在一个类中,它却无效,并且给了我 NullReferenceException:对象引用未设置为对象的实例。
为什么会发生这种情况,而且只能在一堂课上发生?我应该检查什么,像这样“速记”一个单例实例是个坏主意吗?
答案 0 :(得分:2)
发生这种情况的原因是Script Execution Order
。
进入
编辑->项目设置->脚本执行顺序
按下+
,选择GameManager
并将其拖到“默认时间”之前,或将值更改为小于0的值。现在,您的GameManagers Awake()
将在默认时间之前执行。其他脚本Awake()
另一种方法是始终在Awake()
中分配单例,但是当您在其他类中获取引用时,则在Start()
中进行
答案 1 :(得分:1)
我的猜测是,您的Health类在GameManager类之前执行。您可以尝试使用execution order进行操作,也可以仅将代码行移动到将实例分配给Start()方法的位置。
答案 2 :(得分:1)
可能与执行顺序有关。在实际创建对象之前,一些脚本尝试访问该对象。注意,除非您不在项目设置中进行设置,否则您无法控制Awake函数的执行顺序。有很多解决方法。您可以在“启动”函数中分配引用,该函数始终在“唤醒”之后调用,因此您可以确保要访问的对象已经创建。另一种方法是更改项目设置中的执行顺序(最坏的解决方案)或使用延迟加载。 对于单例而言,延迟加载非常方便,但这并不是最佳实践。我宁愿建议您避免单例并在一个中心点初始化所有服务。通过构造函数传递依赖关系比单例模式更为清晰。您也可以使用一些依赖注入框架,例如zenject。
答案 3 :(得分:1)
此处的脚本执行顺序有误,但我发现您的代码还有另一个问题:
if(instance != null)
GameObject.Destroy(instance);
else
instance = this;
让我们说您有时会获得两个实例。
instance
视为空并进入else
语句,并将实例设置为自身instance
设置为第一个副本,并...对其进行销毁(然后退出,使instance
等于null!)。请注意,由于任何原因,此处的“第二份副本”可能是第二次执行Awake()
的第一份副本。