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}";
}
}
}