346 lines
14 KiB
C#
346 lines
14 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|
||
}
|