单例模式GameManager始终为空,DontDestroyOnLoad引发空引用异常
GameManager单例+DontDestroyOnLoad空引用异常解决
问题根源
日志同时输出“Game Manager not found”和“Here”,说明在当前GameManager的Awake方法执行完成前,已有其他代码访问了GameManager.Instance——此时instance还未被赋值,触发了getter里的null提示,之后Awake完成instance赋值,输出“Here”。此外原代码还有两处关键问题:
- 单例重复实例处理错误:存在旧instance时,销毁的是
objectsNotToDestroy列表里的对象,而非新的GameManager自身,导致场景内残留无效对象。 - Player组件获取逻辑不安全:无论instance是否重复,都执行
objectsNotToDestroy[1].GetComponent,重复加载场景时,新GameManager的列表是场景内新对象,可能未初始化或索引越界,引发空引用。
修正步骤
1. 修复单例核心逻辑
确保存在旧instance时直接销毁当前GameManager,不执行后续代码;将instance赋值和DontDestroyOnLoad调用放在Awake最前端,避免提前访问导致null。
2. 安全获取Player组件
仅在instance初始化完成(即首次创建GameManager)时,才去获取Player组件,同时增加列表索引合法性检查。
3. 避免提前访问Instance
确保所有访问GameManager.Instance的代码都放在Start方法中,而非Awake/OnEnable;或在Unity编辑器的Project Settings > Script Execution Order中,将GameManager的执行顺序设为最高。
修正后的代码
using System.Collections.Generic; using UnityEngine.SceneManagement; using UnityEngine; public class GameManager : MonoBehaviour { private static GameManager instance; public static GameManager Instance { get { if (instance == null) { Debug.Log("Game Manager not found"); } return instance; } } [SerializeField] private List<GameObject> objectsNotToDestroy; [SerializeField] private float dayAndNightTime; private PlayerMovement player; public float CurrentTime { get; set; } public float DayTime { get; private set; } private void Awake() { // 单例核心逻辑:存在旧实例则销毁自身 if (instance != null) { Debug.Log("Existing GameManager found, destroying new instance"); Destroy(gameObject); return; // 终止后续代码执行 } // 初始化单例并标记不销毁 instance = this; DontDestroyOnLoad(gameObject); objectsNotToDestroy.ForEach(DontDestroyOnLoad); // 安全获取Player组件 if (objectsNotToDestroy != null && objectsNotToDestroy.Count > 1) { PlayerMovement playerMovement = objectsNotToDestroy[1].GetComponent<PlayerMovement>(); if (playerMovement != null) { player = playerMovement; } else { Debug.LogError("PlayerMovement script not found on object at index 1 in objectsNotToDestroy list."); } } else { Debug.LogError("objectsNotToDestroy list is null or does not have enough elements."); } DayTime = dayAndNightTime; } public void Load(int buildIndex) { // 修正时间累计逻辑:Time.time是游戏启动总时间,若需记录场景间隔,建议改用Time.unscaledDeltaTime CurrentTime += Time.time; SceneManager.LoadSceneAsync(buildIndex); // 增加空引用检查 if (player != null) { player.Running(false); } else { Debug.LogWarning("Player reference is null, cannot call Running(false)"); } } }
额外注意事项
- 确保
objectsNotToDestroy列表中的对象在所有场景中不会被重复创建,否则切换场景后会出现多个同名对象。 - 若Player是动态生成的,建议通过
FindObjectOfType<PlayerMovement>()或事件注册的方式获取引用,而非依赖列表索引。
内容的提问来源于stack exchange,提问作者Nikola Savić
相关产品推荐
相关产品推荐

