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

Unity 2D平台跑酷游戏关卡时间记录与显示问题求助

问题排查与修复方案

现有代码的核心问题

  1. TimeDataManager未跨场景持久化:场景切换时该对象会被自动销毁,存储的时间数据全部丢失。每次加载新场景都会生成新的TimeDataManager实例,之前存储的数据自然无法保留。
  2. 数组未正确初始化:timeDataArr是public数组,但如果在Inspector里未将长度设为5(对应5个关卡),调用AddTimeData时会直接抛出索引越界异常。
  3. Update方法的Debug存在风险:直接访问timeDataArr[SceneManager.GetActiveScene().buildIndex-1],未做索引合法性校验,一旦数组未初始化或索引超出范围,会直接导致游戏崩溃。

修复现有代码的步骤

第一步:改造TimeDataManager实现跨场景持久化

修改脚本添加单例逻辑,确保整个游戏过程中只有一个实例且不会被场景销毁:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManager;

public class TimeDataManager : MonoBehaviour // 类名首字母大写,符合C#编码规范
{
    public static TimeDataManager Instance; // 单例实例
    public string[] timeDataArr;

    void Awake()
    {
        // 单例逻辑:确保全局唯一实例
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject); // 标记为跨场景不销毁
            timeDataArr = new string[5]; // 初始化数组长度为5,对应5个关卡
        }
        else
        {
            Destroy(gameObject); // 销毁重复实例
        }
    }

    void Update()
    {
        int currentIndex = SceneManager.GetActiveScene().buildIndex - 1;
        // 先校验索引合法性再打印,避免报错
        if (currentIndex >= 0 && currentIndex < timeDataArr.Length)
        {
            Debug.Log(timeDataArr[currentIndex]);
        }
    }

    public void AddTimeData(string timeData, int levelBuildIndex)
    {
        int index = levelBuildIndex - 1;
        if (index >= 0 && index < timeDataArr.Length)
        {
            timeDataArr[index] = timeData;
        }
        else
        {
            Debug.LogError("关卡索引超出数组范围");
        }
    }
}

第二步:优化EndPortal脚本

去掉冗余引用,改用单例获取TimeDataManager,提升可靠性:

using System.Collections;
using System.Collections.Generic; // 修复重复引用问题
using UnityEngine;
using UnityEngine.SceneManager;
using UnityEngine.UI;

public class EndPortal : MonoBehaviour
{
    public AudioSource aSrc;
    private int sceneNumber;
    public GameObject bgAudio;
    public Text scoreText;
    private TimeDataManager tDManager;

    void Start()
    {
        sceneNumber = SceneManager.GetActiveScene().buildIndex;
        tDManager = TimeDataManager.Instance; // 通过单例直接获取,比Find更稳定
    }
    
    void OnTriggerEnter2D(Collider2D col)
    {
        if (col.CompareTag("Player")) // CompareTag性能优于直接==标签
        {
            aSrc.Play();
            Destroy(bgAudio);
            string textData = scoreText.text;
            Debug.Log(textData);
            Debug.Log(sceneNumber);
            tDManager.AddTimeData(textData, sceneNumber);
            // 可选:延迟加载场景,确保通关音效播放完成
            StartCoroutine(LoadNextSceneAfterDelay(aSrc.clip.length));
        }
    }

    IEnumerator LoadNextSceneAfterDelay(float delay)
    {
        yield return new WaitForSeconds(delay);
        SceneManager.LoadScene(sceneNumber + 1);
    }
}

第三步:Inspector配置

  • 无需手动设置TimeDataManager的数组长度,代码已在Awake中完成初始化。
  • 若之前给TimeDataManager的GameObject设置了TimeDataManager标签,现在可以移除,因为已改用单例获取。

更可靠的替代实现思路

如果不想依赖数组和场景对象,可使用ScriptableObject存储时间数据,它是Unity原生的持久化数据容器,无需跨场景保留对象:

  1. 创建TimeDataSO脚本:
using UnityEngine;

[CreateAssetMenu(fileName = "TimeData", menuName = "Game/Time Data")]
public class TimeDataSO : ScriptableObject
{
    public string[] levelTimes = new string[5];
}
  1. 在Project窗口右键选择Game -> Time Data,创建一个TimeDataSO实例。
  2. 修改EndPortal脚本,直接引用该SO存储数据:
public TimeDataSO timeDataSO;

// 在触发通关逻辑时
timeDataSO.levelTimes[sceneNumber - 1] = scoreText.text;
  1. 通关后展示数据时,直接读取该SO的levelTimes数组即可。

这种方式的优势是数据可在编辑器中直接查看,若需永久保存到本地,还可结合JsonUtility将SO数据序列化到本地文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:45:24