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

Unity中如何实现一次性可收集硬币?解决场景切换重生问题

解决Unity硬币收集后场景切换重生问题的方案

你的核心问题是所有硬币共用了同一个存档状态,且每个硬币都在覆盖同一份存档文件,导致无法区分单个硬币的收集状态。下面是基于JSON存档的完整解决方案,通过给每个硬币分配唯一ID、统一管理存档数据来实现精准控制。

核心思路

  1. 给每个硬币分配唯一标识ID,用于区分不同硬币
  2. 用单独的存档管理器(单例)统一处理所有硬币的存读逻辑,避免多对象写文件冲突
  3. 存档数据存储已收集硬币的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);
    }
}

操作步骤

  1. 在场景中创建空GameObject,命名为SaveManager,挂载上述SaveManager脚本
  2. 给每个硬币对象的CoinScript组件中的coinID字段设置唯一值(例如场景中第一个硬币设为"Coin_01",第二个设为"Coin_02")
  3. 确保CoinCounterScript中实现AddCoin()方法,用于更新玩家硬币计数
  4. 删除原CoinScript中的SaveObject类、dataPath、saveData、loadData等冗余代码

原代码问题分析

  • 每个硬币都写入同一个存档文件,后处理的硬币会覆盖之前的存档数据,导致所有硬币共用一个状态
  • Awake阶段就调用saveData(),会初始化新的存档对象覆盖之前的存储数据,导致加载逻辑完全失效

内容的提问来源于stack exchange,提问作者Thomas Wilson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:35:59