Files

340 lines
13 KiB
C#
Raw Permalink 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.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>
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;
}
}
}