Unity秒表跨场景及重载后不重置持续计时问题求助
解决Unity秒表跨场景/重载后持续计时的问题
核心问题原因
你的秒表脚本挂载的GameObject在场景切换/重载时会被销毁,timer等变量随之重置;之前用PlayerPrefs没成功,大概率是没有在合适的时机读写数据——比如场景切换前没保存计时状态,加载新场景后没读取旧数据。
方案一:单例模式+DontDestroyOnLoad(最常用的跨场景持久化方案)
把秒表所在的GameObject设置为跨场景不销毁,同时用单例确保全局唯一实例,避免重复创建:
修改后的完整代码:
public class Stopwatch : MonoBehaviour { // 全局唯一实例 public static Stopwatch Instance { get; private set; } float timer; float seconds; float minutes; float hours; bool start; [SerializeField] TextMeshProUGUI stopWatchText; private void Awake() { // 单例逻辑:保证全局只有一个秒表实例 if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; // 标记该对象跨场景不销毁 DontDestroyOnLoad(gameObject); } } private void Start() { // 仅初始化状态,不要重置timer(保留之前的计时数据) UpdateStopwatchUI(); } private void Update() { StopWatchCalcul(); } void StopWatchCalcul() { if (start) { timer += Time.deltaTime; seconds = (int)(timer % 60); minutes = (int)((timer / 60) % 60); hours = (int)(timer / 3600); UpdateStopwatchUI(); } } // 单独封装UI更新逻辑,方便复用 void UpdateStopwatchUI() { if (stopWatchText != null) { stopWatchText.text = $"{hours:00}:{minutes:00}:{seconds:00}"; } } public void startTimer() { start = true; } public void stopTimer() { start = false; } public void resetTimer() { start = false; timer = 0; UpdateStopwatchUI(); } // 可选:新场景加载后重新绑定UI组件 public void BindUI(TextMeshProUGUI newText) { stopWatchText = newText; UpdateStopwatchUI(); } }
使用步骤:
- 把脚本挂载到一个空GameObject上(命名比如
GlobalStopwatch) - 确保首次启动游戏时加载包含该对象的场景,之后切换场景它会自动保留
- 如果新场景里有秒表UI,在新场景的初始化脚本中调用
Stopwatch.Instance.BindUI(你的Text组件)完成绑定
方案二:正确使用PlayerPrefs(适合需要持久化到本地的场景)
如果需要退出游戏再打开也能保留计时,用PlayerPrefs在场景销毁、应用暂停时保存数据,启动时读取:
修改后的代码:
public class Stopwatch : MonoBehaviour { float timer; float seconds; float minutes; float hours; bool start; [SerializeField] TextMeshProUGUI stopWatchText; // 定义存储用的键名 private const string TimerKey = "Stopwatch_Timer"; private const string StartStateKey = "Stopwatch_StartState"; private void Start() { // 加载之前保存的计时数据 LoadTimerData(); UpdateStopwatchUI(); } // 场景销毁前保存数据 private void OnDestroy() { SaveTimerData(); } // 应用暂停时也保存数据(避免意外退出丢失) private void OnApplicationPause(bool pauseStatus) { if (pauseStatus) { SaveTimerData(); } } private void Update() { StopWatchCalcul(); } void StopWatchCalcul() { if (start) { timer += Time.deltaTime; seconds = (int)(timer % 60); minutes = (int)((timer / 60) % 60); hours = (int)(timer / 3600); UpdateStopwatchUI(); } } void UpdateStopwatchUI() { if (stopWatchText != null) { stopWatchText.text = $"{hours:00}:{minutes:00}:{seconds:00}"; } } // 保存计时数据到PlayerPrefs void SaveTimerData() { PlayerPrefs.SetFloat(TimerKey, timer); PlayerPrefs.SetInt(StartStateKey, start ? 1 : 0); PlayerPrefs.Save(); // 强制写入磁盘,避免数据丢失 } // 从PlayerPrefs加载数据 void LoadTimerData() { timer = PlayerPrefs.GetFloat(TimerKey, 0f); start = PlayerPrefs.GetInt(StartStateKey, 0) == 1; // 重新计算时分秒 seconds = (int)(timer % 60); minutes = (int)((timer / 60) % 60); hours = (int)(timer / 3600); } public void startTimer() { start = true; } public void stopTimer() { start = false; } public void resetTimer() { start = false; timer = 0; UpdateStopwatchUI(); SaveTimerData(); // 重置后同步保存数据 } }
注意事项:
- PlayerPrefs仅适合存储小体积数据,不要用它存大量内容
- 必须调用
PlayerPrefs.Save(),否则数据可能只存在内存中,未写入磁盘
方案三:用ScriptableObject共享状态(适合多场景同步计时)
创建ScriptableObject资产存储计时数据,所有场景的秒表脚本都引用同一个资产,实现状态共享:
- 先创建ScriptableObject类:
using UnityEngine; [CreateAssetMenu(fileName = "StopwatchData", menuName = "ScriptableObjects/StopwatchData")] public class StopwatchData : ScriptableObject { public float timer; public bool isRunning; }
在Unity编辑器右键选择
ScriptableObjects/StopwatchData,创建一个资产(命名比如GlobalStopwatchData)修改秒表脚本:
public class Stopwatch : MonoBehaviour { float seconds; float minutes; float hours; [SerializeField] StopwatchData stopwatchData; [SerializeField] TextMeshProUGUI stopWatchText; private void Start() { UpdateStopwatchUI(); } private void Update() { StopWatchCalcul(); } void StopWatchCalcul() { if (stopwatchData.isRunning) { stopwatchData.timer += Time.deltaTime; seconds = (int)(stopwatchData.timer % 60); minutes = (int)((stopwatchData.timer / 60) % 60); hours = (int)(stopwatchData.timer / 3600); UpdateStopwatchUI(); } } void UpdateStopwatchUI() { if (stopWatchText != null) { stopWatchText.text = $"{hours:00}:{minutes:00}:{seconds:00}"; } } public void startTimer() { stopwatchData.isRunning = true; } public void stopTimer() { stopwatchData.isRunning = false; } public void resetTimer() { stopwatchData.isRunning = false; stopwatchData.timer = 0; UpdateStopwatchUI(); } }
使用说明:
- 把创建的
GlobalStopwatchData资产拖到脚本的stopwatchData字段上 - 任何场景的秒表脚本只要引用同一个资产,就能同步计时状态
- 注意:ScriptableObject的数据在编辑模式下会保留,但打包后运行时修改的数据不会保存到磁盘(需要持久化的话,要结合PlayerPrefs)
内容的提问来源于stack exchange,提问作者Dylan Freed
相关产品推荐
相关产品推荐

