Unity 2D平台跑酷游戏关卡时间记录与显示问题求助
问题排查与修复方案
现有代码的核心问题
- TimeDataManager未跨场景持久化:场景切换时该对象会被自动销毁,存储的时间数据全部丢失。每次加载新场景都会生成新的TimeDataManager实例,之前存储的数据自然无法保留。
- 数组未正确初始化:
timeDataArr是public数组,但如果在Inspector里未将长度设为5(对应5个关卡),调用AddTimeData时会直接抛出索引越界异常。 - 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原生的持久化数据容器,无需跨场景保留对象:
- 创建TimeDataSO脚本:
using UnityEngine; [CreateAssetMenu(fileName = "TimeData", menuName = "Game/Time Data")] public class TimeDataSO : ScriptableObject { public string[] levelTimes = new string[5]; }
- 在Project窗口右键选择
Game -> Time Data,创建一个TimeDataSO实例。 - 修改EndPortal脚本,直接引用该SO存储数据:
public TimeDataSO timeDataSO; // 在触发通关逻辑时 timeDataSO.levelTimes[sceneNumber - 1] = scoreText.text;
- 通关后展示数据时,直接读取该SO的
levelTimes数组即可。
这种方式的优势是数据可在编辑器中直接查看,若需永久保存到本地,还可结合JsonUtility将SO数据序列化到本地文件。
内容的提问来源于stack exchange,提问作者unitylearns
相关产品推荐
相关产品推荐

