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

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