feat(TimeCountDown): 实现倒计时功能
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
"name": "Core",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c"
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
108
Assets/Script/Core/UniTimer.cs
Normal file
108
Assets/Script/Core/UniTimer.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
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;
|
||||
}
|
||||
}
|
3
Assets/Script/Core/UniTimer.cs.meta
Normal file
3
Assets/Script/Core/UniTimer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ddb70eec4a545f29d26f24f8370baae
|
||||
timeCreated: 1760751050
|
89
Assets/Script/Gameplay/Global/GameCountdownManager.cs
Normal file
89
Assets/Script/Gameplay/Global/GameCountdownManager.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Core;
|
||||
|
||||
namespace Script.Gameplay.Global
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局关卡倒计时管理器(UniTimer 版)
|
||||
/// </summary>
|
||||
public sealed class GameCountdownManager : MonoSingleton<GameCountdownManager>
|
||||
{
|
||||
|
||||
[Header("默认关卡时长(秒)")] [SerializeField] private float defaultDuration = 60f;
|
||||
|
||||
[Header("是否受 Time.timeScale 影响")] [SerializeField]
|
||||
private bool useUnscaledTime = false;
|
||||
|
||||
[Header("事件:每秒刷新(剩余秒数)")] public UnityEvent<float> OnTick;
|
||||
|
||||
[Header("事件:倒计时结束")] public UnityEvent OnFinish;
|
||||
|
||||
private UniTimer _timer = new UniTimer();
|
||||
|
||||
/*------------------------------------------------------*/
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
// 确保事件实例化,避免其他脚本订阅时报 NullReferenceException
|
||||
OnTick ??= new UnityEvent<float>();
|
||||
OnFinish ??= new UnityEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动关卡倒计时(默认时长)
|
||||
/// </summary>
|
||||
public UniTask StartLevelTimer() => StartLevelTimer(defaultDuration);
|
||||
|
||||
/// <summary>
|
||||
/// 启动关卡倒计时(指定时长)
|
||||
/// </summary>
|
||||
/// <param name="duration">秒</param>
|
||||
public UniTask StartLevelTimer(float duration)
|
||||
{
|
||||
StopTimer(); // 先清掉旧计时器
|
||||
|
||||
// 用 unscaledDeltaTime 就不受 Time.timeScale 影响
|
||||
Time.timeScale = 1f; // 可根据需求保留或去掉
|
||||
return _timer.StartAsync(
|
||||
duration,
|
||||
isCountDown: true,
|
||||
onTick: left => { OnTick?.Invoke(left); },
|
||||
onComplete: () =>
|
||||
{
|
||||
OnFinish?.Invoke();
|
||||
// 这里可以统一处理“时间到”逻辑
|
||||
TimeOutHandle();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary> 暂停 </summary>
|
||||
public void Pause() => _timer.Pause();
|
||||
|
||||
/// <summary> 继续 </summary>
|
||||
public void Resume() => _timer.Resume();
|
||||
|
||||
/// <summary> 立即结束并触发 OnFinish </summary>
|
||||
public void StopTimer() => _timer.Stop();
|
||||
|
||||
/// <summary> 剩余时间(秒) </summary>
|
||||
public float Remaining => _timer.Remaining;
|
||||
|
||||
/// <summary> 是否正在跑 </summary>
|
||||
public bool IsRunning => _timer.IsRunning;
|
||||
|
||||
/*------------------------------------------------------*/
|
||||
// 统一处理“时间到”
|
||||
private void TimeOutHandle()
|
||||
{
|
||||
Debug.Log("[GameCountdown] 关卡时间到!");
|
||||
// 可以在这里弹 UI、播放音效、加载结算场景等
|
||||
}
|
||||
|
||||
/*------------------------------------------------------*/
|
||||
// 方便外部 await 等待结束
|
||||
public UniTask WaitForTimeOutAsync() => _timer.WaitForFinishAsync();
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdaad0392e5549f0ac761238b649cc70
|
||||
timeCreated: 1760751427
|
24
Assets/Script/Gameplay/Global/GameFlowManager.cs
Normal file
24
Assets/Script/Gameplay/Global/GameFlowManager.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Script.Gameplay.Global
|
||||
{
|
||||
public enum GameState
|
||||
{
|
||||
Boot, // 初始化
|
||||
MainMenu, // 主菜单
|
||||
Playing, // 游戏中
|
||||
Paused, // 暂停
|
||||
GameOver, // 游戏结束
|
||||
Victory // 胜利
|
||||
}
|
||||
|
||||
public class GameFlowManager : MonoSingleton<GameFlowManager>
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
GameCountdownManager.Instance.StartLevelTimer();
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Script/Gameplay/Global/GameFlowManager.cs.meta
Normal file
3
Assets/Script/Gameplay/Global/GameFlowManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ff98d4d16514d27a14dc271bbbd19e7
|
||||
timeCreated: 1760752445
|
37
Assets/Script/Gameplay/UI/GameCountDownViewer.cs
Normal file
37
Assets/Script/Gameplay/UI/GameCountDownViewer.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Script.Gameplay.Global;
|
||||
namespace UI
|
||||
{
|
||||
public class GameCountDownViewer : MonoBehaviour
|
||||
{
|
||||
public Text countdownText;
|
||||
private GameCountdownManager _countdownManager;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_countdownManager = GameCountdownManager.Instance;
|
||||
if (_countdownManager != null)
|
||||
{
|
||||
_countdownManager.OnTick.AddListener(UpdateCountdown);
|
||||
}
|
||||
}
|
||||
public void UpdateCountdown(float secondsLeft)
|
||||
{
|
||||
if (countdownText != null)
|
||||
{
|
||||
int seconds = Mathf.CeilToInt(secondsLeft);
|
||||
countdownText.text = "倒计时: " + seconds.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_countdownManager != null)
|
||||
{
|
||||
_countdownManager.OnTick.RemoveListener(UpdateCountdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Script/Gameplay/UI/GameCountDownViewer.cs.meta
Normal file
3
Assets/Script/Gameplay/UI/GameCountDownViewer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1aa400ea9061498897f3c03b0336cfda
|
||||
timeCreated: 1760751782
|
Reference in New Issue
Block a user