using UnityEngine; using TMPro; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Core.Save; namespace BaseGames.Support.Speedrun { /// /// 速通计时器(架构 16_SupportingModules §8)。 /// 使用 Time.unscaledDeltaTime 以免被 HitStop(timeScale < 1)拉慢;游戏暂停时须由外部调用 PauseTimer()。 /// 实现 ISaveable,计时持久化到 。 /// public class SpeedrunTimer : MonoBehaviour, ISaveable { [Header("显示设置")] [SerializeField] private TMP_Text _display; [SerializeField] private GlobalSettingsSO _settings; [Header("事件频道")] [SerializeField] private VoidEventChannelSO _onTimerReset; [SerializeField] private BoolEventChannelSO _onTimerVisibilityChanged; public float ElapsedSeconds { get; private set; } public bool IsRunning { get; private set; } private bool _paused; /// 上一帧已显示的整秒值,展示内容未变化时跳过字符串重建。 private int _lastDisplayedSecond = -1; private void Start() { bool show = _settings != null && _settings.ShowSpeedrunTimer; SetVisible(show); if (show) StartTimer(); } private void Update() { if (!IsRunning || _paused) return; ElapsedSeconds += Time.unscaledDeltaTime; // 仅当整秒数发生变化时才重建展示字符串,避免每帧分配 int currentSecond = (int)ElapsedSeconds; if (currentSecond != _lastDisplayedSecond) { _lastDisplayedSecond = currentSecond; UpdateDisplay(); } } // ── 控制接口 ────────────────────────────────────────────────────────────── public void StartTimer() { IsRunning = true; _paused = false; } public void StopTimer() => IsRunning = false; public void PauseTimer() => _paused = true; public void ResumeTimer() => _paused = false; public void ResetTimer() { ElapsedSeconds = 0f; UpdateDisplay(); _onTimerReset?.Raise(); } public void SetVisible(bool visible) { if (_display != null) _display.gameObject.SetActive(visible); _onTimerVisibilityChanged?.Raise(visible); } // ── ISaveable ───────────────────────────────────────────────────────────── public void OnSave(SaveData saveData) { if (saveData?.Stats != null) saveData.Stats.SpeedrunTime = ElapsedSeconds; } public void OnLoad(SaveData saveData) { if (saveData?.Stats != null) ElapsedSeconds = saveData.Stats.SpeedrunTime; UpdateDisplay(); } // ── 内部 ────────────────────────────────────────────────────────────────── private void UpdateDisplay() { if (_display == null) return; int hours = (int)(ElapsedSeconds / 3600); int minutes = (int)(ElapsedSeconds % 3600 / 60); int seconds = (int)(ElapsedSeconds % 60); int ms = (int)(ElapsedSeconds * 100 % 100); _display.text = hours > 0 ? $"{hours:00}:{minutes:00}:{seconds:00}.{ms:00}" : $"{minutes:00}:{seconds:00}.{ms:00}"; } } }