使用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
相关产品推荐
相关产品推荐

