using System;
using System.Collections.Generic;
using System.Linq;
using UI;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Base
{
///
/// UI窗口输入控制和管理类。
/// 负责根据输入显示/隐藏UI,并根据UI状态管理游戏暂停。
///
public class UIInputControl : Utils.MonoSingleton, ITickUI
{
// 存储场景中所有UIBase的实例
private List _allWindows = new List();
// 缓存当前可见的窗口
private readonly List _visibleWindows = new List();
// 标记是否需要更新可见窗口缓存和暂停状态
private bool needUpdate = false;
///
/// 当UI窗口的可见性状态发生改变时触发的事件。
///
/// - 参数1: 发生改变的UIBase实例。
/// - 参数2: 窗口的新可见状态 (true为显示,false为隐藏)。
///
///
public event Action OnWindowVisibilityChanged;
///
/// 获取所有已注册的UI窗口的总数量。
///
public int AllWindowCount => _allWindows.Count;
///
/// 获取当前可见的UI窗口的数量。
///
public int VisibleWindowCount => _visibleWindows.Count;
///
/// 查询指定名称的UI窗口是否当前可见。
///
/// UI窗口的名称。
/// 如果窗口可见则返回true,否则返回false。
public bool IsWindowVisible(string uiName)
{
return _visibleWindows.Any(window => window != null && window.name == uiName && window.IsVisible);
}
///
/// 查询指定名称的UI窗口是否当前处于可见状态且占用了输入。
///
/// UI窗口的名称。
/// 如果窗口可见且占用了输入则返回true,否则返回false。
public bool IsWindowInputOccupied(string uiName)
{
return _visibleWindows.Any(window => window != null && window.name == uiName && window.IsVisible && window.isInputOccupied);
}
///
/// 根据名称获取已注册的UI窗口实例。
///
/// UI窗口的名称。
/// 匹配的UIBase实例,如果未找到则返回null。
public UIBase GetWindow(string uiName)
{
return _allWindows.FirstOrDefault(window => window != null && window.name == uiName);
}
///
/// 查找并注册场景中所有的UI窗口,包括非激活状态的。
///
private void RegisterAllWindows()
{
_allWindows.Clear();
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
if (!activeScene.isLoaded)
{
return;
}
foreach (var rootGameObject in activeScene.GetRootGameObjects())
{
var windows = rootGameObject.GetComponentsInChildren(true);
_allWindows.AddRange(windows);
}
_allWindows = _allWindows.Distinct().ToList();
foreach (var window in _allWindows)
{
if (window != null && window.gameObject != null)
{
window.Hide();
}
}
needUpdate = true;
}
///
/// UI逻辑更新循环,需要被外部的某个管理器在Update中调用。
///
public void TickUI()
{
// 使用这个是为了让输入独占窗口关闭自己后不会立即激活其他窗口的按键,延迟一帧
if (needUpdate)
{
UpdateVisibleWindowsCache();
UpdatePauseState();
needUpdate = false;
return;
}
if (_visibleWindows.Any(window => window && window.isInputOccupied))
return;
foreach (var window in _allWindows)
{
if (!window || !window.gameObject) continue;
if (window.actionButton == KeyCode.None || !Input.GetKeyDown(window.actionButton)) continue;
if (window.IsVisible)
{
if (!window.isInputOccupied)
{
Hide(window);
}
}
else
{
Show(window);
}
}
}
///
/// 公开的显示窗口方法。
///
/// 要显示的窗口。
public void Show(UIBase windowToShow)
{
if (!windowToShow || !windowToShow.gameObject || windowToShow.IsVisible) return;
if (windowToShow.exclusive)
{
var windowsToHide = _visibleWindows.ToList();
foreach (var visibleWindow in windowsToHide)
{
if (visibleWindow && visibleWindow != windowToShow && visibleWindow.IsVisible)
{
Hide(visibleWindow);
}
}
}
windowToShow.Show();
OnWindowVisibilityChanged?.Invoke(windowToShow, true);
needUpdate = true;
}
///
/// 根据名称显示UI窗口。
///
/// 要显示的UI窗口名称。
public void Show(string uiName)
{
var window = GetWindow(uiName);
if (window)
{
Show(window);
return;
}
Debug.LogWarning($"[UIInputControl] 未找到名称为 '{uiName}' 的窗口来显示。");
}
///
/// 公开的隐藏窗口方法。
///
/// 要隐藏的窗口。
public void Hide(UIBase windowToHide)
{
if (!windowToHide || !windowToHide.gameObject || !windowToHide.IsVisible) return;
windowToHide.Hide();
OnWindowVisibilityChanged?.Invoke(windowToHide, false);
needUpdate = true;
}
///
/// 根据名称隐藏UI窗口。
///
/// 要隐藏的UI窗口名称。
public void Hide(string uiName)
{
var visibleWindowToHide = _visibleWindows.FirstOrDefault(window => window != null && window.name == uiName && window.IsVisible);
if (visibleWindowToHide != null)
{
Hide(visibleWindowToHide);
return;
}
Debug.LogWarning($"[UIInputControl] 未找到名称为 '{uiName}' 且当前可见的窗口来隐藏。");
}
///
/// 隐藏所有当前可见的UI窗口。
///
public void HideAll()
{
var windowsToHide = _visibleWindows.ToList();
foreach (var visibleWindow in windowsToHide)
{
if (visibleWindow != null && visibleWindow.IsVisible)
{
Hide(visibleWindow);
}
}
}
///
/// 根据当前所有可见窗口的 needPause 属性来更新游戏时钟的暂停状态。
///
private void UpdatePauseState()
{
var shouldPause = _visibleWindows.Any(w => w && w.needPause);
if (Base.Clock.Instance.Pause != shouldPause)
{
Base.Clock.Instance.Pause = shouldPause;
}
}
///
/// 更新当前可见窗口的缓存列表。
///
private void UpdateVisibleWindowsCache()
{
_visibleWindows.Clear();
foreach (var window in _allWindows)
{
if (window && window.IsVisible)
{
_visibleWindows.Add(window);
}
}
}
///
/// 当脚本实例被销毁时调用。
/// 用于在销毁时取消订阅场景加载事件,防止内存泄漏。
///
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
Clock.RemoveTickUI(this);
}
///
/// MonoSingleton 的 OnStart 方法,在单例首次创建并激活时调用,早于普通的 Start 方法。
/// 用于订阅场景加载事件并在首次启动时注册UI窗口。
///
protected override void OnStart()
{
SceneManager.sceneLoaded += OnSceneLoaded;
Clock.AddTickUI(this);
}
///
/// 当场景加载完成时调用。
/// 用于在场景加载后重新查找并注册所有UI窗口。
///
/// 已加载的场景。
/// 场景加载模式。
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
RegisterAllWindows();
}
}
}