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

Unity多次重载场景时列表元素不重复随机选取问题排查

问题根因

场景重载后列表显示Missing

  • 你给TrialHandler加了DontDestroyOnLoad,切/重载场景时这个脚本对象不会被销毁,但你拖到objectsList里的所有物品图像都是当前场景内的物体,场景重载时旧场景物体会被Unity销毁,脚本持有的旧引用自然变成空的Missing状态。
  • 单例判断存在语法错误:if (Instance = null)是赋值操作不是相等判断,会导致单例逻辑完全失效,每次重载场景都会生成新的TrialHandler实例,进一步加剧引用混乱。

物品重复出现、去重失效

  • 移除已选物品的逻辑写在Update里,是场景启动3秒后自动执行,完全没有和「玩家找到物品按下按钮」的触发时机绑定。
  • 移除逻辑本身有索引错误:调用objectsList.RemoveAt(currentObject)后,列表元素会前移,紧接着执行objectsList[currentObject].SetActive(false)操作的是移除后新占这个索引的元素,根本没有正确禁用之前选中的目标。
  • 每次场景重载Start方法都会重新执行,你没有持久化存储已经找过的物品记录,也没有在场景加载后重新初始化物品列表,自然会出现重复选中的问题。
修复方案
  1. 修正单例语法错误,将持久化的单例脚本和场景内物体引用解耦,每次场景加载完成后重新扫描获取当前场景的有效物品,避免引用丢失。
  2. 删除无意义的3秒自动移除逻辑,新增公开的物品找到回调方法,绑定到VR控制器的触发按钮上,只有玩家确认找到物品时才执行下一轮流程。
  3. 用全局哈希集存储已经找到过的物品唯一标识,每次随机选目标时直接排除已找到的物品,从根源避免重复。
修复后完整代码

直接替换原有脚本内容即可,其中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 22:48:22