Files
zeling_v2/Assets/_Game/Scripts/Support/Speedrun/SpeedrunTimer.cs

105 lines
3.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using TMPro;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Core.Save;
namespace BaseGames.Support.Speedrun
{
/// <summary>
/// 速通计时器(架构 16_SupportingModules §8
/// 使用 Time.unscaledDeltaTime 以免被 HitStoptimeScale &lt; 1拉慢游戏暂停时须由外部调用 PauseTimer()。
/// 实现 ISaveable计时持久化到 <see cref="StatsSaveData.SpeedrunTime"/>。
/// </summary>
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;
/// <summary>上一帧已显示的整秒值,展示内容未变化时跳过字符串重建。</summary>
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}";
}
}
}