108 lines
3.6 KiB
C#
108 lines
3.6 KiB
C#
|
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;
|
|||
|
}
|
|||
|
}
|