多轮审查和修复
This commit is contained in:
104
Assets/Scripts/Support/Speedrun/SpeedrunTimer.cs
Normal file
104
Assets/Scripts/Support/Speedrun/SpeedrunTimer.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
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 以免被 HitStop(timeScale < 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user