Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Base/Clock.cs

340 lines
13 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Utils;
using Object = UnityEngine.Object;
// 假设此命名空间包含MonoSingleton
namespace Base
{
/// <summary>
/// 定义一个Tick更新接口用于在常规Update中执行逻辑。
/// </summary>
public interface ITick
{
public void Tick();
}
/// <summary>
/// 定义一个TickPhysics更新接口用于在FixedUpdate中执行物理逻辑。
/// </summary>
public interface ITickPhysics
{
public void TickPhysics();
}
/// <summary>
/// 定义一个TickUI更新接口用于在常规Update中执行UI逻辑。
/// </summary>
public interface ITickUI
{
public void TickUI();
}
/// <summary>
/// 全局计时器和更新管理器负责在Unity的Update、FixedUpdate和LateUpdate生命周期中调度注册的Ticks。
/// 支持游戏暂停、场景加载时自动重置以及缓冲区的添加/移除操作以避免迭代器错误。
/// </summary>
2025-07-09 17:54:54 +08:00
public class Clock : MonoSingleton<Clock>
{
// 存储所有需要在FixedUpdate中执行物理Tick逻辑的对象
private readonly HashSet<ITickPhysics> _tickPhysics = new();
// 待添加的ITickPhysics对象缓冲区
private readonly HashSet<ITickPhysics> _tickPhysicsToAdd = new();
// 待移除的ITickPhysics对象缓冲区
private readonly HashSet<ITickPhysics> _tickPhysicsToRemove = new();
// 存储所有需要在常规Update中执行Tick逻辑的对象
private readonly HashSet<ITick> _ticks = new();
// 待添加的ITick对象缓冲区
private readonly HashSet<ITick> _ticksToAdd = new();
// 待移除的ITick对象缓冲区
private readonly HashSet<ITick> _ticksToRemove = new();
// 存储所有需要在常规Update中执行UI Tick逻辑的对象
private readonly HashSet<ITickUI> _tickUIs = new();
// 待添加的ITickUI对象缓冲区
private readonly HashSet<ITickUI> _tickUIsToAdd = new();
// 待移除的ITickUI对象缓冲区
private readonly HashSet<ITickUI> _tickUIsToRemove = new();
// 游戏是否暂停
private bool _pause;
/// <summary>
/// 获取或设置游戏的暂停状态。当设置为true时Time.timeScale将变为0当设置为false时Time.timeScale将恢复为1。
/// 该操作会检查当前状态避免重复设置Time.timeScale。
/// </summary>
public bool Pause
{
get => _pause;
set
{
// 如果新值与当前暂停状态不同则更新Time.timeScale。
if (value != _pause)
{
Time.timeScale = value ? 0 : 1;
_pause = value;
}
}
}
/// <summary>
/// 每帧更新方法。
/// 如果游戏未暂停则执行所有注册的ITick对象的Tick方法
/// 执行所有注册的ITickUI对象的TickUI方法UI通常不受暂停影响
/// </summary>
private void Update()
{
ApplyBufferedChanges();
// 如果游戏未暂停则执行常规的Tick更新。
if (!_pause)
foreach (var tick in _ticks)
try
{
tick.Tick();
}
catch (Exception e)
{
Debug.LogError($"[Clock] ITick对象 '{tick.GetType().Name}' 在Tick()执行期间抛出异常: {e}");
}
// UI更新通常不受游戏暂停影响例如菜单动画、UI计时器等
foreach (var uiTick in _tickUIs)
try
{
uiTick.TickUI();
}
catch (Exception e)
{
Debug.LogError($"[Clock] ITickUI对象 '{uiTick.GetType().Name}' 在TickUI()执行期间抛出异常: {e}");
}
}
/// <summary>
/// 每固定帧更新方法。
/// 如果游戏未暂停则执行所有注册的ITickPhysics对象的TickPhysics方法。
/// </summary>
private void FixedUpdate()
{
// 如果游戏未暂停则执行物理Tick更新。
if (!_pause)
foreach (var physicsTick in _tickPhysics)
try
{
physicsTick.TickPhysics();
}
catch (Exception e)
{
Debug.LogError(
$"[Clock] ITickPhysics对象 '{physicsTick.GetType().Name}' 在TickPhysics()执行期间抛出异常: {e}");
}
}
/// <summary>
/// 在Clock组件被销毁时调用。
/// 取消订阅场景加载事件并清理所有内部Tick列表。
/// </summary>
protected void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoadedCallback;
ClearAllTicksInternal(false);
}
/// <summary>
/// 在单例首次创建时调用。
/// </summary>
protected override void OnStart()
{
SceneManager.sceneLoaded += OnSceneLoadedCallback;
}
/// <summary>
/// 场景加载完成时的回调方法。
/// 用于在加载新场景后清理所有Tick列表并重置游戏暂停状态。
/// </summary>
/// <param name="scene">被加载的场景。</param>
/// <param name="mode">场景加载模式。</param>
private void OnSceneLoadedCallback(Scene scene, LoadSceneMode mode)
{
ClearAllTicksInternal(true);
}
/// <summary>
/// 将一个ITick对象添加到待添加缓冲区。它将在下一个LateUpdate中被添加到主Tick列表。
/// 如果对象已经在待移除列表中,则会先从待移除列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="tick">要添加的ITick对象。</param>
public static void AddTick(ITick tick)
{
if (tick != null)
{
Instance._ticksToAdd.Add(tick);
Instance._ticksToRemove.Remove(tick);
}
}
/// <summary>
/// 将一个ITick对象添加到待移除缓冲区。它将在下一个LateUpdate中从主Tick列表移除。
/// 如果对象已经在待添加列表中,则会先从待添加列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="tick">要移除的ITick对象。</param>
public static void RemoveTick(ITick tick)
{
if (tick != null)
{
Instance._ticksToRemove.Add(tick);
Instance._ticksToAdd.Remove(tick);
}
}
/// <summary>
/// 将一个ITickPhysics对象添加到待添加缓冲区。它将在下一个LateUpdate中被添加到主物理Tick列表。
/// 如果对象已经在待移除列表中,则会先从待移除列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="physics">要添加的ITickPhysics对象。</param>
public static void AddTickPhysics(ITickPhysics physics)
{
if (physics != null)
{
Instance._tickPhysicsToAdd.Add(physics);
Instance._tickPhysicsToRemove.Remove(physics);
}
}
/// <summary>
/// 将一个ITickPhysics对象添加到待移除缓冲区。它将在下一个LateUpdate中从主物理Tick列表移除。
/// 如果对象已经在待添加列表中,则会先从待添加列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="physics">要移除的ITickPhysics对象。</param>
public static void RemoveTickPhysics(ITickPhysics physics)
{
if (physics != null)
{
Instance._tickPhysicsToRemove.Add(physics);
Instance._tickPhysicsToAdd.Remove(physics);
}
}
/// <summary>
/// 将一个ITickUI对象添加到待添加缓冲区。它将在下一个LateUpdate中被添加到主UI Tick列表。
/// 如果对象已经在待移除列表中,则会先从待移除列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="ui">要添加的ITickUI对象。</param>
public static void AddTickUI(ITickUI ui)
{
if (ui != null)
{
Instance._tickUIsToAdd.Add(ui);
Instance._tickUIsToRemove.Remove(ui);
}
}
/// <summary>
/// 将一个ITickUI对象添加到待移除缓冲区。它将在下一个LateUpdate中从主UI Tick列表移除。
/// 如果对象已经在待添加列表中,则会先从待添加列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="ui">要移除的ITickUI对象。</param>
public static void RemoveTickUI(ITickUI ui)
{
if (ui != null)
{
Instance._tickUIsToRemove.Add(ui);
Instance._tickUIsToAdd.Remove(ui);
}
}
/// <summary>
/// 将缓冲区中的添加和移除操作应用到主Tick列表中。
/// 此方法应在LateUpdate中调用以确保在所有Tick执行完毕后进行列表修改从而避免迭代器错误。
/// </summary>
private void ApplyBufferedChanges()
{
if (_ticksToRemove.Count > 0)
{
foreach (var tick in _ticksToRemove) _ticks.Remove(tick);
_ticksToRemove.Clear();
}
if (_tickPhysicsToRemove.Count > 0)
{
foreach (var physicsTick in _tickPhysicsToRemove) _tickPhysics.Remove(physicsTick);
_tickPhysicsToRemove.Clear();
}
if (_tickUIsToRemove.Count > 0)
{
foreach (var uiTick in _tickUIsToRemove) _tickUIs.Remove(uiTick);
_tickUIsToRemove.Clear();
}
if (_ticksToAdd.Count > 0)
{
foreach (var tick in _ticksToAdd)
{
if (tick is Object unityObject && !unityObject) continue; // 跳过已销毁的Unity对象
_ticks.Add(tick);
}
_ticksToAdd.Clear();
}
if (_tickPhysicsToAdd.Count > 0)
{
foreach (var physicsTick in _tickPhysicsToAdd)
{
if (physicsTick is Object unityObject && !unityObject) continue;
_tickPhysics.Add(physicsTick);
}
_tickPhysicsToAdd.Clear();
}
if (_tickUIsToAdd.Count > 0)
{
foreach (var uiTick in _tickUIsToAdd)
{
if (uiTick is Object unityObject && unityObject == null) continue;
_tickUIs.Add(uiTick);
}
_tickUIsToAdd.Clear();
}
}
/// <summary>
/// 集中处理所有Tick列表和缓冲区的清理。
/// 此方法会在场景加载、Clock被禁用或销毁时调用。
/// </summary>
/// <param name="clearPauseState">指示是否同时重置Pause状态和Time.timeScale。</param>
private void ClearAllTicksInternal(bool clearPauseState)
{
_ticks.Clear();
_tickPhysics.Clear();
_tickUIs.Clear();
// 逻辑修改:
// 在场景加载时不应清除_ToAdd缓冲区。
// 这些缓冲区用于收集当前帧内(包括场景加载期间)的有效注册请求。
// 清除它们会导致在场景加载过程中新注册的Ticks被立即抹除。
// 对于前一场景中已销毁的Tick引用将在ApplyBufferedChanges中进行过滤处理。
// _ticksToAdd.Clear();
// _tickPhysicsToAdd.Clear();
// _tickUIsToAdd.Clear();
_ticksToRemove.Clear();
_tickPhysicsToRemove.Clear();
_tickUIsToRemove.Clear();
if (clearPauseState) Pause = false;
}
}
}