Files

346 lines
14 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.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Utils; // 假设此命名空间包含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>
{
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>
/// 存储所有需要在常规Update中执行Tick逻辑的对象。使用HashSet确保唯一性并提高查找效率。
/// </summary>
private readonly HashSet<ITick> _ticks = new();
/// <summary>
/// 存储所有需要在FixedUpdate中执行物理Tick逻辑的对象。使用HashSet确保唯一性并提高查找效率。
/// </summary>
private readonly HashSet<ITickPhysics> _tickPhysics = new();
/// <summary>
/// 存储所有需要在常规Update中执行UI Tick逻辑的对象。使用HashSet确保唯一性并提高查找效率。
/// </summary>
private readonly HashSet<ITickUI> _tickUIs = new();
/// <summary>
/// 待添加的ITick对象缓冲区。在LateUpdate中统一处理以避免在迭代主列表时修改列表。
/// </summary>
private readonly HashSet<ITick> _ticksToAdd = new();
/// <summary>
/// 待添加的ITickPhysics对象缓冲区。在LateUpdate中统一处理。
/// </summary>
private readonly HashSet<ITickPhysics> _tickPhysicsToAdd = new();
/// <summary>
/// 待添加的ITickUI对象缓冲区。在LateUpdate中统一处理。
/// </summary>
private readonly HashSet<ITickUI> _tickUIsToAdd = new();
/// <summary>
/// 待移除的ITick对象缓冲区。在LateUpdate中统一处理以避免在迭代主列表时修改列表。
/// </summary>
private readonly HashSet<ITick> _ticksToRemove = new();
/// <summary>
/// 待移除的ITickPhysics对象缓冲区。在LateUpdate中统一处理。
/// </summary>
private readonly HashSet<ITickPhysics> _tickPhysicsToRemove = new();
/// <summary>
/// 待移除的ITickUI对象缓冲区。在LateUpdate中统一处理。
/// </summary>
private readonly HashSet<ITickUI> _tickUIsToRemove = new();
private void Update()
{
// 如果游戏未暂停则执行常规的Tick更新。
if (!_pause)
{
// 迭代主列表确保_ticks在Update生命周期内不会被Add/Remove直接修改。
// 添加和移除操作通过缓冲区在LateUpdate处理。
foreach (var tick in _ticks)
{
tick.Tick();
}
}
// UI更新通常不受游戏暂停影响例如菜单动画、UI计时器等
// 这是当前的默认设计如果需要UI也暂停则需修改此处逻辑或引入单独的UI暂停状态。
foreach (var uiTick in _tickUIs)
{
uiTick.TickUI();
}
}
private void FixedUpdate()
{
// 如果游戏未暂停则执行物理Tick更新。
if (!_pause)
{
foreach (var physicsTick in _tickPhysics)
{
physicsTick.TickPhysics();
}
}
}
/// <summary>
/// 在所有Update操作完成后应用Tick注册和注销的缓冲区更改确保列表在迭代期间不被修改。
/// </summary>
private void LateUpdate()
{
ApplyBufferedChanges();
}
private void OnDestroy()
{
// 在对象销毁时,取消订阅场景加载事件,避免潜在的内存泄漏。
SceneManager.sceneLoaded -= OnSceneLoaded;
}
/// <summary>
/// 单例的初始化方法在Clock实例的生命周期开始时调用Unity的Awake后
/// 订阅场景加载事件,并执行初始设置。
/// </summary>
protected override void OnStart()
{
SceneManager.sceneLoaded += OnSceneLoaded;
Init(); // 初始化时清空所有列表并重新填充
}
/// <summary>
/// 场景加载完成时回调用于重置所有Tick列表以适应新场景中的对象。
/// </summary>
/// <param name="scene">已加载的场景。</param>
/// <param name="mode">场景加载模式。</param>
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Init(); // 场景加载时重置
}
/// <summary>
/// 初始化或重置计时器系统。清空所有注册列表和缓冲列表并重新扫描场景中的所有MonoBehaviour以注册相应的Tick接口。
/// </summary>
public void Init()
{
// 清空所有主列表
_ticks.Clear();
_tickPhysics.Clear();
_tickUIs.Clear();
// 清空所有缓冲区列表
_ticksToAdd.Clear();
_tickPhysicsToAdd.Clear();
_tickUIsToAdd.Clear();
_ticksToRemove.Clear();
_tickPhysicsToRemove.Clear();
_tickUIsToRemove.Clear();
// 扫描场景中所有MonoBehaviour并注册它们实现的Tick接口。
// 使用HashSet会自动处理重复添加确保列表唯一性。
foreach (var obj in FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None))
{
if (obj is ITick tickObj) _ticks.Add(tickObj);
if (obj is ITickPhysics physicsObj) _tickPhysics.Add(physicsObj);
if (obj is ITickUI uiObj) _tickUIs.Add(uiObj);
}
}
/// <summary>
/// 将一个ITick对象添加到待添加缓冲区。它将在下一个LateUpdate中被添加到主Tick列表。
/// 如果对象已经在待移除列表中,则会先从待移除列表中移除,以处理添加/移除冲突。
/// </summary>
/// <param name="tick">要添加的ITick对象。</param>
public static void AddTick(ITick tick)
{
// 确保Clock实例存在且对象不为空。
if (Instance != null && 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)
{
// 确保Clock实例存在且对象不为空。
if (Instance != null && 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)
{
// 确保Clock实例存在且对象不为空。
if (Instance != null && 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)
{
// 确保Clock实例存在且对象不为空。
if (Instance != null && 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)
{
// 确保Clock实例存在且对象不为空。
if (Instance != null && 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)
{
// 确保Clock实例存在且对象不为空。
if (Instance != null && 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); // 从主列表移除HashSet的移除操作平均为O(1)
}
_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)
{
// 添加到主列表。HashSet.Add平均为O(1),并自动处理重复添加(会忽略重复项)。
_ticks.Add(tick);
}
_ticksToAdd.Clear(); // 清空添加缓冲区
}
if (_tickPhysicsToAdd.Count > 0)
{
foreach (var physicsTick in _tickPhysicsToAdd)
{
_tickPhysics.Add(physicsTick); // HashSet.Add 会自动处理重复
}
_tickPhysicsToAdd.Clear();
}
if (_tickUIsToAdd.Count > 0)
{
foreach (var uiTick in _tickUIsToAdd)
{
_tickUIs.Add(uiTick); // HashSet.Add 会自动处理重复
}
_tickUIsToAdd.Clear();
}
}
}
}