Unity多次重载场景时列表元素不重复随机选取问题排查
问题根因
场景重载后列表显示Missing
- 你给
TrialHandler加了DontDestroyOnLoad,切/重载场景时这个脚本对象不会被销毁,但你拖到objectsList里的所有物品图像都是当前场景内的物体,场景重载时旧场景物体会被Unity销毁,脚本持有的旧引用自然变成空的Missing状态。 - 单例判断存在语法错误:
if (Instance = null)是赋值操作不是相等判断,会导致单例逻辑完全失效,每次重载场景都会生成新的TrialHandler实例,进一步加剧引用混乱。
物品重复出现、去重失效
- 移除已选物品的逻辑写在
Update里,是场景启动3秒后自动执行,完全没有和「玩家找到物品按下按钮」的触发时机绑定。 - 移除逻辑本身有索引错误:调用
objectsList.RemoveAt(currentObject)后,列表元素会前移,紧接着执行objectsList[currentObject].SetActive(false)操作的是移除后新占这个索引的元素,根本没有正确禁用之前选中的目标。 - 每次场景重载
Start方法都会重新执行,你没有持久化存储已经找过的物品记录,也没有在场景加载后重新初始化物品列表,自然会出现重复选中的问题。
修复方案
- 修正单例语法错误,将持久化的单例脚本和场景内物体引用解耦,每次场景加载完成后重新扫描获取当前场景的有效物品,避免引用丢失。
- 删除无意义的3秒自动移除逻辑,新增公开的物品找到回调方法,绑定到VR控制器的触发按钮上,只有玩家确认找到物品时才执行下一轮流程。
- 用全局哈希集存储已经找到过的物品唯一标识,每次随机选目标时直接排除已找到的物品,从根源避免重复。
修复后完整代码
直接替换原有脚本内容即可,其中ItemIdentifier是辅助组件,和主脚本写在同一个文件里不需要额外新建:
using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class TrialHandler : MonoBehaviour { public static TrialHandler Instance; // 存储当前场景内可选择的未找到物品 private List<GameObject> availableTargets = new List<GameObject>(); // 全局记录已经找到过的物品ID,持久化不随场景重置 private HashSet<string> foundItemIds = new HashSet<string>(); private GameObject currentActiveTarget; private void Awake() { // 修正单例判断逻辑,原先是=赋值错误 if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // 注册场景加载完成回调 SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDestroy() { // 移除回调避免内存泄漏 SceneManager.sceneLoaded -= OnSceneLoaded; } // 每次场景加载完成后自动执行初始化 private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { availableTargets.Clear(); // 扫描当前场景所有带物品标识的物体 ItemIdentifier[] allSceneItems = FindObjectsOfType<ItemIdentifier>(); foreach (var item in allSceneItems) { // 只把没找到过的物品加入可选列表 if (!foundItemIds.Contains(item.itemId)) { availableTargets.Add(item.gameObject); // 默认全部禁用 item.gameObject.SetActive(false); } } // 所有物品找完的通关逻辑,可自行扩展 if (availableTargets.Count == 0) { Debug.Log("全部物品搜寻完成,游戏结束"); return; } // 随机选择新目标显示在屏幕上 int randomIndex = Random.Range(0, availableTargets.Count); currentActiveTarget = availableTargets[randomIndex]; currentActiveTarget.SetActive(true); } // 给VR触发按钮绑定的公开方法,找到物品时调用 public void ConfirmFoundItem() { if (currentActiveTarget == null) return; // 标记当前物品为已找到 string targetId = currentActiveTarget.GetComponent<ItemIdentifier>().itemId; foundItemIds.Add(targetId); currentActiveTarget.SetActive(false); // 重载场景进入下一轮 SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); } } // 挂在每个待搜寻物品/电视显示图像上的标识组件 public class ItemIdentifier : MonoBehaviour { [Tooltip("每个物品填唯一不重复的ID,比如cup、book即可")] public string itemId; }
配置步骤
- 场景内每个待搜寻的目标物品、电视上显示的对应图像物体,都挂上
ItemIdentifier组件,给每个物体填写不重复的itemId。 - 场景中新建空物体挂载
TrialHandler脚本,原脚本里需要手动拖元素的objectsList字段可以直接删掉,不需要手动赋值,脚本会自动扫描场景内的有效物品。 - 把VR手柄的触发按钮事件,绑定到
TrialHandler.Instance.ConfirmFoundItem()方法上,玩家对准物品按下按钮即可进入下一轮。
内容的提问来源于stack exchange,提问作者Phoenix
相关产品推荐
相关产品推荐

