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

使用DontDestroyOnLoad仍无法传递static变量weapons至下一场景

Unity场景切换后武器引用丢失问题解决

问题核心

本质问题是:直接存储场景对象的Transform引用,场景切换后原场景对象被销毁,引用会变为null——不管变量是static还是实例变量,只要存的是场景对象的引用,对象没了引用就失效。其他变量(比如钥匙、收集物)是值类型或独立于场景的数据结构,所以能正常保留。

解决思路

放弃存储场景对象的直接引用,改为存储武器的数据标识(比如ID、ScriptableObject数据),场景切换后根据这些标识重新初始化武器;同时用单例模式替代static变量,确保Inventory的全局访问性和数据一致性。

具体实现步骤

1. 定义武器数据结构

用ScriptableObject存储武器的基础数据(编辑器中配置更方便,也可用普通类):

[CreateAssetMenu(fileName = "WeaponData", menuName = "Inventory/WeaponData")]
public class WeaponData : ScriptableObject
{
    public string weaponId; // 武器唯一标识
    public GameObject weaponPrefab; // 武器预制体
    public AmmoType ammoType; // 对应弹药类型
    // 可扩展伤害、射速等属性
}

2. 重构PlayerInventory类

去掉static武器数组,改用单例模式,存储武器数据而非引用,并在场景加载完成后初始化武器:

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

public class PlayerInventory : MonoBehaviour
{
    public static PlayerInventory Instance; // 单例,全局访问入口
    public int AmmoCount { get; private set; }
    public int StuffCollected { get; private set; }

    private List<Key> keys = new List<Key>(); 
    public UnityEvent<PlayerInventory> OnStuffCollected;

    // 存储已解锁的武器数据
    private List<WeaponData> unlockedWeapons = new List<WeaponData>();
    // 存储各武器的当前弹药状态
    private Dictionary<string, int> weaponCurrentAmmo = new Dictionary<string, int>();
    private AmmoContainer ammoContainer = new AmmoContainer();

    private void Awake()
    {
        // 单例初始化,确保全局唯一且不被销毁
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            // 初始解锁小刀(需提前创建KnifeData的ScriptableObject资源)
            InitializeDefaultWeapon();
        }
        else
        {
            Destroy(gameObject); // 销毁重复实例,避免数据冲突
        }
    }

    private void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void OnDisable()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    // 场景加载完成后初始化UI和武器
    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // 刷新收集物UI
        InventoryUI ui = GameObject.Find("StuffCollectedCountText")?.GetComponent<InventoryUI>();
        if (ui != null)
        {
            OnStuffCollected.RemoveAllListeners();
            OnStuffCollected.AddListener(ui.UpdateStuffCollectedText);
            OnStuffCollected.Invoke(this);
        }

        // 初始化当前场景的武器
        SpawnOrRefreshWeapons();
    }

    // 初始解锁小刀
    private void InitializeDefaultWeapon()
    {
        WeaponData knifeData = Resources.Load<WeaponData>("WeaponData/KnifeData");
        if (knifeData != null)
        {
            unlockedWeapons.Add(knifeData);
            weaponCurrentAmmo[knifeData.weaponId] = ammoContainer.ammoTypeValues[knifeData.ammoType];
        }
    }

    // 根据已解锁数据生成或刷新武器
    private void SpawnOrRefreshWeapons()
    {
        foreach (var weaponData in unlockedWeapons)
        {
            // 检查场景中是否已有该武器,没有则生成
            GameObject weaponObj = GameObject.Find(weaponData.weaponPrefab.name);
            if (weaponObj == null)
            {
                weaponObj = Instantiate(weaponData.weaponPrefab, transform.position, Quaternion.identity);
            }

            // 刷新武器状态(比如弹药)
            Weapon weaponComp = weaponObj.GetComponent<Weapon>();
            if (weaponComp != null && weaponCurrentAmmo.ContainsKey(weaponData.weaponId))
            {
                weaponComp.currentAmmo = weaponCurrentAmmo[weaponData.weaponId];
            }
        }
    }

    // 收集物计数方法
    public void StuffCollectedCount()
    {
        StuffCollected++;
        OnStuffCollected.Invoke(this);
    }

    // 添加钥匙
    public void AddKey(Key key)
    {
        keys.Add(key);
    }

    // 检查是否有对应钥匙
    public bool HasKey(string keyId)
    {
        return keys.Any(key => key.keyId == keyId);
    }

    // 解锁新武器
    public void UnlockWeapon(WeaponData newWeapon)
    {
        if (!unlockedWeapons.Contains(newWeapon))
        {
            unlockedWeapons.Add(newWeapon);
            weaponCurrentAmmo[newWeapon.weaponId] = ammoContainer.ammoTypeValues[newWeapon.ammoType];
            // 若当前场景已加载,立即生成武器
            if (SceneManager.GetActiveScene().isLoaded)
            {
                SpawnOrRefreshWeapons();
            }
        }
    }

    // 更新武器弹药
    public void UpdateWeaponAmmo(string weaponId, int newAmmo)
    {
        if (weaponCurrentAmmo.ContainsKey(weaponId))
        {
            weaponCurrentAmmo[weaponId] = newAmmo;
        }
    }
}

3. 原问题原因复盘

  • 原代码中static Transform[] weapons存储的是旧场景中Knife对象的引用,场景切换后旧场景对象被销毁,引用直接变为null,新场景中自然无法访问。
  • 去掉static后,虽然Inventory对象保留,但数组里的Transform引用还是指向已销毁的旧对象,同样失效。
  • Start方法仅在对象首次创建时执行,场景切换后不会重新调用,无法获取新场景中的武器对象。

额外注意事项

  • 尽量避免使用GameObject.Find查找对象,建议给武器设置唯一标签,或用资源加载的方式获取预制体。
  • 所有跨场景保留的状态(比如武器弹药、解锁状态)都要存在DontDestroyOnLoad的对象中,不要依赖场景内的对象。
  • 单例模式要严格实现,避免场景中出现多个PlayerInventory实例导致数据冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:19:51