Files
2025TapTapGameJam/Assets/Script/Core/UniTimer.cs

108 lines
3.6 KiB
C#
Raw Normal View History

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace Core
{
/// <summary>
/// 通用计时器(正计时 / 倒计时均可)
/// 全程 UniTask无 GC支持暂停、继续、提前终止
/// </summary>
public sealed class UniTimer
{
public float Duration { get; private set; }
public float Elapsed { get; private set; }
public float Remaining => Mathf.Max(0, Duration - Elapsed);
public bool IsRunning { get; private set; }
public bool IsPaused { get; private set; }
/// <summary> 每帧回调:参数为当前已走时间 </summary>
public event Action<float> OnTick;
/// <summary> 计时结束回调 </summary>
public event Action OnComplete;
private CancellationTokenSource _cts;
private UniTaskCompletionSource<bool> _taskSource;
/*------------------------------------------------------*/
/// <summary>
/// 开始计时
/// </summary>
/// <param name="duration">时长(秒)</param>
/// <param name="isCountDown">true=倒计时false=正计时</param>
/// <param name="onTick">可选:每帧刷新</param>
/// <param name="onComplete">可选:结束回调</param>
public UniTask StartAsync(float duration,
bool isCountDown = false,
Action<float> onTick = null,
Action onComplete = null)
{
Stop(); // 先清理旧任务
_taskSource = new UniTaskCompletionSource<bool>(); // 新建
Duration = duration;
Elapsed = isCountDown ? duration : 0f;
OnTick = onTick;
OnComplete = onComplete;
IsRunning = true;
IsPaused = false;
_cts = new CancellationTokenSource();
return RunAsync(_cts.Token, isCountDown);
}
/// <summary> 立即终止(不可恢复) </summary>
public void Stop()
{
if (!IsRunning) return;
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
IsRunning = false;
_taskSource?.TrySetCanceled(); // 提前终止也通知
IsPaused = false;
}
/// <summary> 暂停 </summary>
public void Pause() => IsPaused = true;
/// <summary> 继续 </summary>
public void Resume() => IsPaused = false;
/*------------------------------------------------------*/
private async UniTask RunAsync(System.Threading.CancellationToken token, bool countDown)
{
while (!token.IsCancellationRequested)
{
if (!IsPaused)
{
float dt = Time.deltaTime;
Elapsed += countDown ? -dt : dt;
// 触发回调
OnTick?.Invoke(Elapsed);
// 结束条件
if (countDown ? Elapsed <= 0f : Elapsed >= Duration)
{
Elapsed = countDown ? 0f : Duration;
OnTick?.Invoke(Elapsed); // 最后再给一次精确值
break;
}
}
await UniTask.Yield(PlayerLoopTiming.Update, token);
}
IsRunning = false;
_taskSource?.TrySetResult(true); // 广播“结束”
if (!token.IsCancellationRequested)
OnComplete?.Invoke();
}
public UniTask WaitForFinishAsync() => _taskSource?.Task ?? UniTask.CompletedTask;
}
}