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




