You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单例模式GameManager始终为空,DontDestroyOnLoad引发空引用异常

GameManager单例+DontDestroyOnLoad空引用异常解决

问题根源

日志同时输出“Game Manager not found”和“Here”,说明在当前GameManager的Awake方法执行完成前,已有其他代码访问了GameManager.Instance——此时instance还未被赋值,触发了getter里的null提示,之后Awake完成instance赋值,输出“Here”。此外原代码还有两处关键问题:

  1. 单例重复实例处理错误:存在旧instance时,销毁的是objectsNotToDestroy列表里的对象,而非新的GameManager自身,导致场景内残留无效对象。
  2. 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ć

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:55:02