You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity中如何让实例化对象永久保留(非DontDestroyOnLoad方式)

这个场景动态生成对象的持久化问题我碰到过好多次了,刚好有几个完美符合你需求的解决方案,完全不用DontDestroyOnLoad,还能保证生成脚本只跑一次:

方案1:静态数据存储 + 场景加载恢复(最直接高效)

核心思路是:第一次进入场景时随机生成对象,同时把每个对象的关键信息(位置、对应预制体)存在全局静态类里;当你切换场景再返回时,直接读取这些数据重新生成对象,而非再次执行随机逻辑,同时用一个标记确保生成脚本只运行一次。

步骤1:创建静态数据存储类

用来保存生成对象的必要信息,静态类的数据在程序运行期间不会被销毁:

public static class SpawnedObjectsTracker
{
    // 存储所有生成对象的信息
    public static List<ObjectSpawnInfo> SavedObjects = new List<ObjectSpawnInfo>();
    // 标记是否已经完成第一次随机生成
    public static bool HasSpawnedOnce = false;
}

// 序列化类,用来存储单个对象的生成参数
[System.Serializable]
public class ObjectSpawnInfo
{
    public string PrefabName; // 预制体名称(用来匹配预制体)
    public Vector3 SpawnPosition;
    public Quaternion SpawnRotation;
}

步骤2:修改你的生成脚本

using UnityEngine;
using System.Collections.Generic;

public class RandomObjectSpawner : MonoBehaviour
{
    public List<GameObject> spawnPrefabs; // 拖入要随机生成的预制体
    public int spawnAmount = 5; // 生成数量
    public Vector3 spawnRange = new Vector3(10f, 0f, 10f); // 随机位置范围

    private void Start()
    {
        if (!SpawnedObjectsTracker.HasSpawnedOnce)
        {
            // 第一次进入场景:执行随机生成逻辑
            SpawnRandomObjects();
            SpawnedObjectsTracker.HasSpawnedOnce = true;
        }
        else
        {
            // 场景返回时:根据保存的数据恢复对象
            RestoreSpawnedObjects();
        }
    }

    private void SpawnRandomObjects()
    {
        SpawnedObjectsTracker.SavedObjects.Clear(); // 清空旧数据
        for (int i = 0; i < spawnAmount; i++)
        {
            // 随机选预制体、随机生成位置
            GameObject selectedPrefab = spawnPrefabs[Random.Range(0, spawnPrefabs.Count)];
            Vector3 randomPos = new Vector3(
                Random.Range(-spawnRange.x, spawnRange.x),
                spawnRange.y,
                Random.Range(-spawnRange.z, spawnRange.z)
            );
            GameObject spawnedObj = Instantiate(selectedPrefab, randomPos, Quaternion.identity);

            // 保存当前对象的生成信息
            SpawnedObjectsTracker.SavedObjects.Add(new ObjectSpawnInfo()
            {
                PrefabName = selectedPrefab.name,
                SpawnPosition = randomPos,
                SpawnRotation = Quaternion.identity
            });
        }
    }

    private void RestoreSpawnedObjects()
    {
        foreach (var objInfo in SpawnedObjectsTracker.SavedObjects)
        {
            // 根据预制体名称匹配到对应的预制体(确保预制体名称唯一)
            GameObject targetPrefab = spawnPrefabs.Find(prefab => prefab.name == objInfo.PrefabName);
            if (targetPrefab != null)
            {
                Instantiate(targetPrefab, objInfo.SpawnPosition, objInfo.SpawnRotation);
            }
        }
    }
}

注意事项

  • 确保预制体名称唯一,避免匹配错误;如果担心重名,可以改用预制体的资源路径或者给每个预制体加唯一ID的ScriptableObject标识。
  • 静态类的数据仅在单次游戏运行中有效,关闭游戏后会重置。
方案2:临时持久化父物体(适合不想用静态类的场景)

思路是:场景即将卸载时,把生成的对象临时转移到一个标记为DontDestroyOnLoad的空物体下;当场景重新加载后,再把这些对象移回当前场景,随后销毁临时父物体,既避免对象被销毁,又不会让它们一直存在于所有场景中。

修改后的生成脚本

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public class RandomObjectSpawner : MonoBehaviour
{
    public List<GameObject> spawnPrefabs;
    public int spawnAmount = 5;
    public Vector3 spawnRange = new Vector3(10f, 0f, 10f);
    private GameObject tempPersistentHolder;
    private static bool hasSpawnedOnce = false;

    private void Awake()
    {
        // 订阅场景加载/卸载事件
        SceneManager.sceneUnloaded += OnSceneUnloaded;
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void Start()
    {
        if (!hasSpawnedOnce)
        {
            SpawnRandomObjects();
            hasSpawnedOnce = true;
        }
        else if (tempPersistentHolder != null)
        {
            // 场景返回后,把对象移回当前场景
            foreach (Transform child in tempPersistentHolder.transform)
            {
                child.SetParent(null);
                SceneManager.MoveGameObjectToScene(child.gameObject, SceneManager.GetActiveScene());
            }
            Destroy(tempPersistentHolder);
        }
    }

    private void SpawnRandomObjects()
    {
        for (int i = 0; i < spawnAmount; i++)
        {
            GameObject selectedPrefab = spawnPrefabs[Random.Range(0, spawnPrefabs.Count)];
            Vector3 randomPos = new Vector3(
                Random.Range(-spawnRange.x, spawnRange.x),
                spawnRange.y,
                Random.Range(-spawnRange.z, spawnRange.z)
            );
            GameObject spawnedObj = Instantiate(selectedPrefab, randomPos, Quaternion.identity);
            spawnedObj.tag = "SpawnedDynamicObject"; // 给生成对象加统一标签
        }
    }

    private void OnSceneUnloaded(Scene scene)
    {
        // 确认卸载的是当前场景
        if (scene.name == gameObject.scene.name)
        {
            // 创建临时持久化父物体
            tempPersistentHolder = new GameObject("TempSpawnedObjectHolder");
            DontDestroyOnLoad(tempPersistentHolder);
            // 把所有生成的对象移到这个父物体下
            GameObject[] spawnedObjects = GameObject.FindGameObjectsWithTag("SpawnedDynamicObject");
            foreach (var obj in spawnedObjects)
            {
                obj.transform.SetParent(tempPersistentHolder.transform);
            }
        }
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // 场景加载完成后,后续在Start中处理对象迁移
    }

    private void OnDestroy()
    {
        // 取消订阅事件,防止内存泄漏
        SceneManager.sceneUnloaded -= OnSceneUnloaded;
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }
}

注意事项

  • 一定要给生成的对象加上统一标签(比如SpawnedDynamicObject),这样才能准确筛选出需要保留的对象。
  • 临时父物体仅在场景切换期间存在,场景加载完成后会被销毁,不会一直占用内存。
方案3:持久化存储(适合跨游戏会话保留)

如果需要关闭游戏再打开后,生成的对象位置和类型依然保留,可以用ScriptableObject或者PlayerPrefs来存储数据,用法和方案1类似,只是把静态类换成可序列化的持久化载体。比如用ScriptableObject

[CreateAssetMenu(fileName = "SpawnData", menuName = "Game/Spawn Data")]
public class SpawnDataSO : ScriptableObject
{
    public List<ObjectSpawnInfo> savedObjects;
    public bool hasSpawnedOnce;
}

之后在生成脚本中引用这个SO,替换方案1中的静态类即可,数据会被序列化保存到资源文件中。

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

火山引擎 最新活动