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

Unity Object Pooling切换/刷新场景时功能失效问题

问题原因
  • 单例实现存在缺陷:原ObjectPool单例未做跨场景保活、重复实例校验,切场景时旧实例被销毁、新实例初始化时序不可控,极易出现引用丢失。
  • 初始化时序冲突:原逻辑将对象池字典的初始化放在Start生命周期,而Unity不保证不同脚本Awake/Start的执行顺序,GameManager在自身Start中调用生成接口时,可能ObjectPool的字典还未完成初始化,触发空引用(即你截图中的报错),导致对象生成失败。
  • 池对象引用失效:切场景时默认销毁场景内所有GameObject,原逻辑未将池内生成的对象设为跨场景存活,队列中存储的对象引用会变成无效空引用,后续取出调用激活逻辑自然无响应。
修复方案

1. 重写ObjectPool脚本逻辑

将初始化逻辑前移到Awake,补全单例校验、跨场景保活、无效对象过滤逻辑,修改后完整代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class ObjectPool : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }

    public static ObjectPool Instance;

    public GameObject diamondCollectedFX;

    public List<Pool> pools;
    public Dictionary<string, Queue<GameObject>> poolDictinory;

    private void Awake()
    {
        // 处理重复单例实例
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        // 标记对象池根节点跨场景不销毁
        DontDestroyOnLoad(gameObject);

        // 提前初始化池字典,保证其他脚本调用前已完成准备
        poolDictinory = new Dictionary<string, Queue<GameObject>>();
        foreach (Pool pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();
            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                // 池内对象同样标记跨场景不销毁
                DontDestroyOnLoad(obj);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
            }
            poolDictinory.Add(pool.tag, objectPool);
        }
    }

    public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictinory.ContainsKey(tag))
        {
            Debug.LogWarning($"Pool with tag {tag} doesn't exist");
            return null;
        }

        Queue<GameObject> targetPool = poolDictinory[tag];
        Pool targetConfig = pools.First(p => p.tag == tag);

        // 清理队列中已被意外销毁的无效对象,自动补位
        while (targetPool.Count > 0 && targetPool.Peek() == null)
        {
            targetPool.Dequeue();
            GameObject replaceObj = Instantiate(targetConfig.prefab);
            DontDestroyOnLoad(replaceObj);
            replaceObj.SetActive(false);
            targetPool.Enqueue(replaceObj);
        }

        // 池空时自动扩容
        if (targetPool.Count == 0)
        {
            GameObject newObj = Instantiate(targetConfig.prefab);
            DontDestroyOnLoad(newObj);
            targetPool.Enqueue(newObj);
        }

        GameObject objectToSpawn = targetPool.Dequeue();
        objectToSpawn.SetActive(true);
        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

        targetPool.Enqueue(objectToSpawn);
        return objectToSpawn;
    }
}

2. 场景配置调整

  • 仅在第一个启动场景放置ObjectPool挂载对象,后续所有场景不要再重复放置ObjectPool,避免触发重复实例销毁逻辑。
  • GameManager脚本中Awake方法里的objectPool = ObjectPool.Instance;这行可以直接删除,后续代码均通过ObjectPool.Instance直接调用即可,避免缓存失效引用。
原理解释

单场景下逻辑正常是因为Unity生命周期规则保证:同场景所有脚本的Awake全部执行完成后,才会开始执行所有脚本的Start。原逻辑中GameManager在Start里调用生成接口时,ObjectPool的Start必然已经执行完,字典初始化完成所以不会报错。切场景时多场景实例的生命周期执行顺序不可控,就会触发时序问题,修改后将初始化前移到最早的Awake阶段,加上跨场景保活逻辑,就能保证所有调用发生时对象池已经准备完成。

内容的提问来源于stack exchange,提问作者Emirhan Soylu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 19:24:31