Unity中如何实现一次性可收集硬币?解决场景切换重生问题
解决Unity硬币收集后场景切换重生问题的方案
你的核心问题是所有硬币共用了同一个存档状态,且每个硬币都在覆盖同一份存档文件,导致无法区分单个硬币的收集状态。下面是基于JSON存档的完整解决方案,通过给每个硬币分配唯一ID、统一管理存档数据来实现精准控制。
核心思路
- 给每个硬币分配唯一标识ID,用于区分不同硬币
- 用单独的存档管理器(单例)统一处理所有硬币的存读逻辑,避免多对象写文件冲突
- 存档数据存储已收集硬币的ID列表,场景加载时通过ID判断当前硬币是否需要显示
代码实现
1. 存档管理器(单例模式)
创建一个空GameObject,挂载此脚本作为全局存档管理中心:
using System.Collections.Generic; using UnityEngine; using System.IO; public class SaveManager : MonoBehaviour { public static SaveManager Instance; private string savePath; private SaveData currentSaveData; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // 定义存档文件路径 savePath = Application.persistentDataPath + "/Player_Data/CoinSaveData.json"; LoadSaveData(); } // 加载存档,无存档则初始化 private void LoadSaveData() { if (File.Exists(savePath)) { string json = File.ReadAllText(savePath); currentSaveData = JsonUtility.FromJson<SaveData>(json); } else { currentSaveData = new SaveData(); currentSaveData.collectedCoinIDs = new List<string>(); SaveCurrentData(); } } // 保存当前存档数据 public void SaveCurrentData() { string json = JsonUtility.ToJson(currentSaveData, true); // 确保目录存在 Directory.CreateDirectory(Path.GetDirectoryName(savePath)); File.WriteAllText(savePath, json); } // 标记指定ID的硬币为已收集 public void MarkCoinAsCollected(string coinID) { if (!currentSaveData.collectedCoinIDs.Contains(coinID)) { currentSaveData.collectedCoinIDs.Add(coinID); SaveCurrentData(); } } // 检查指定ID的硬币是否已收集 public bool IsCoinCollected(string coinID) { return currentSaveData.collectedCoinIDs.Contains(coinID); } // 重置所有硬币收集状态(测试用) public void ResetCoinCollection() { currentSaveData.collectedCoinIDs.Clear(); SaveCurrentData(); } } [System.Serializable] public class SaveData { public List<string> collectedCoinIDs; }
2. 重构后的硬币脚本
删除原脚本中的存档相关逻辑,改为调用存档管理器:
using UnityEngine; public class CoinScript : MonoBehaviour { private Transform myTransform; [SerializeField] float rotationSpeed; [SerializeField] float speed; // 给每个硬币设置唯一ID,例如"Coin_01"、"Coin_02" [SerializeField] private string coinID; CoinCounterScript coinCounterScript; public Transform CoinCounterText; public GameObject CoinCounterTextOBJ; public GameObject player; private bool isCollected; void Start() { myTransform = transform; coinCounterScript = FindObjectOfType<CoinCounterScript>(); isCollected = false; // 加载时判断是否显示硬币 if (SaveManager.Instance.IsCoinCollected(coinID)) { gameObject.SetActive(false); } else { gameObject.SetActive(true); } } void OnEnable() { // 保留忽略玩家子弹碰撞的逻辑 GameObject[] otherObjects = GameObject.FindGameObjectsWithTag("PlayerProjectile"); foreach (GameObject obj in otherObjects) { Physics2D.IgnoreCollision(obj.GetComponent<Collider2D>(), GetComponent<Collider2D>()); } } void Update() { if (!isCollected) { myTransform.Rotate(rotationSpeed * Time.deltaTime, 0, 0); } else { myTransform.Rotate(0, 0, 0); transform.position = Vector3.MoveTowards(transform.position, CoinCounterText.position, speed * Time.deltaTime); Invoke("DestroySelf", 5); } // 保留空引用检查逻辑 if (player == null) { player = GameObject.FindGameObjectWithTag("Player"); } if (CoinCounterText == null) { CoinCounterTextOBJ = GameObject.FindGameObjectWithTag("UITestTag"); CoinCounterText = CoinCounterTextOBJ.GetComponent<Transform>(); } } void OnCollisionEnter2D(Collision2D col) { if (col.gameObject.CompareTag("Player") && !isCollected) { isCollected = true; int collectedLayer = LayerMask.NameToLayer("CollectibleUnCollideable"); gameObject.layer = collectedLayer; } } void OnTriggerEnter2D(Collider2D col) { if (col.gameObject.CompareTag("UITestTag")) { // 标记硬币为已收集并保存 SaveManager.Instance.MarkCoinAsCollected(coinID); Destroy(gameObject); // 调用硬币计数增加逻辑(需确保CoinCounterScript有AddCoin方法) coinCounterScript.AddCoin(); } } void DestroySelf() { Destroy(gameObject); } }
操作步骤
- 在场景中创建空GameObject,命名为
SaveManager,挂载上述SaveManager脚本 - 给每个硬币对象的
CoinScript组件中的coinID字段设置唯一值(例如场景中第一个硬币设为"Coin_01",第二个设为"Coin_02") - 确保
CoinCounterScript中实现AddCoin()方法,用于更新玩家硬币计数 - 删除原
CoinScript中的SaveObject类、dataPath、saveData、loadData等冗余代码
原代码问题分析
- 每个硬币都写入同一个存档文件,后处理的硬币会覆盖之前的存档数据,导致所有硬币共用一个状态
- Awake阶段就调用
saveData(),会初始化新的存档对象覆盖之前的存储数据,导致加载逻辑完全失效
内容的提问来源于stack exchange,提问作者Thomas Wilson
相关产品推荐
相关产品推荐

