(client) feat:添加游玩时UI相关贴图,添加3d模型场景按钮,添加多级调色进度条,添加SVG图片包,添加事件定义以及管理器,添加音频管理器,定义部分怪物,添加通信协议定义;fix:修复维度切换错误,修复LogUI显示不正确 (#55)
Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com> Reviewed-on: #55
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
using System; // Added for Action
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
@ -7,7 +8,7 @@ using UnityEngine.SceneManagement;
|
||||
namespace Base
|
||||
{
|
||||
/// <summary>
|
||||
/// UI窗口输入控制和管理类
|
||||
/// UI窗口输入控制和管理类。
|
||||
/// 负责根据输入显示/隐藏UI,并根据UI状态管理游戏暂停。
|
||||
/// </summary>
|
||||
public class UIInputControl : Utils.MonoSingleton<UIInputControl>, ITickUI
|
||||
@ -18,8 +19,59 @@ namespace Base
|
||||
private readonly List<UIBase> _visibleWindows = new List<UIBase>();
|
||||
|
||||
private bool needUpdate = false;
|
||||
|
||||
/// <summary>
|
||||
/// 查找并注册场景中所有的UI窗口,包括非激活状态的
|
||||
/// 当UI窗口的可见性状态发生改变时触发的事件。
|
||||
/// 参数1: 发生改变的UIBase实例。
|
||||
/// 参数2: 窗口的新可见状态 (true为显示,false为隐藏)。
|
||||
/// </summary>
|
||||
public event Action<UIBase, bool> OnWindowVisibilityChanged; // <--- 新增
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已注册的UI窗口的总数量。
|
||||
/// </summary>
|
||||
public int AllWindowCount => _allWindows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前可见的UI窗口的数量。
|
||||
/// </summary>
|
||||
public int VisibleWindowCount => _visibleWindows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定名称的UI窗口是否当前可见。
|
||||
/// </summary>
|
||||
/// <param name="uiName">UI窗口的名称。</param>
|
||||
/// <returns>如果窗口可见则返回true,否则返回false。</returns>
|
||||
public bool IsWindowVisible(string uiName)
|
||||
{
|
||||
// 使用 Any() 方法检查 _visibleWindows 列表中是否存在名称匹配且可见的窗口
|
||||
return _visibleWindows.Any(window => window != null && window.name == uiName && window.IsVisible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定名称的UI窗口是否当前处于可见状态且占用了输入。
|
||||
/// </summary>
|
||||
/// <param name="uiName">UI窗口的名称。</param>
|
||||
/// <returns>如果窗口可见且占用了输入则返回true,否则返回false。</returns>
|
||||
public bool IsWindowInputOccupied(string uiName)
|
||||
{
|
||||
// 检查 _visibleWindows 列表中是否存在名称匹配且同时占用输入的窗口
|
||||
return _visibleWindows.Any(window => window != null && window.name == uiName && window.IsVisible && window.isInputOccupied);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称获取已注册的UI窗口实例。
|
||||
/// </summary>
|
||||
/// <param name="uiName">UI窗口的名称。</param>
|
||||
/// <returns>匹配的UIBase实例,如果未找到则返回null。</returns>
|
||||
public UIBase GetWindow(string uiName)
|
||||
{
|
||||
// 从 _allWindows 列表中查找第一个名称匹配的窗口
|
||||
return _allWindows.FirstOrDefault(window => window != null && window.name == uiName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找并注册场景中所有的UI窗口,包括非激活状态的。
|
||||
/// </summary>
|
||||
private void RegisterAllWindows()
|
||||
{
|
||||
@ -29,7 +81,7 @@ namespace Base
|
||||
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
if (!activeScene.isLoaded)
|
||||
{
|
||||
Debug.LogWarning("当前场景未加载,无法注册窗口!");
|
||||
Debug.LogWarning("[UIInputControl] 当前场景未加载,无法注册窗口!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -46,22 +98,22 @@ namespace Base
|
||||
// 初始化所有窗口为隐藏状态
|
||||
foreach (var window in _allWindows)
|
||||
{
|
||||
// 确保窗口不为空且其GameObject未被销毁
|
||||
if (window != null && window.gameObject != null)
|
||||
{
|
||||
window.Hide();
|
||||
window.Hide(); // 隐藏操作会触发 OnWindowVisibilityChanged 事件
|
||||
}
|
||||
}
|
||||
|
||||
needUpdate = true;
|
||||
Debug.Log($"窗口数量{_allWindows.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI逻辑更新循环,需要被外部的某个管理器在Update中调用
|
||||
/// UI逻辑更新循环,需要被外部的某个管理器在Update中调用。
|
||||
/// </summary>
|
||||
public void TickUI()
|
||||
{
|
||||
//使用这个是为了让输入独占窗口关闭自己后不会立即激活其他窗口的按键,延迟一帧
|
||||
// 使用这个是为了让输入独占窗口关闭自己后不会立即激活其他窗口的按键,延迟一帧
|
||||
if (needUpdate)
|
||||
{
|
||||
// 更新可见窗口缓存和暂停状态
|
||||
@ -70,12 +122,19 @@ namespace Base
|
||||
needUpdate = false;
|
||||
return;
|
||||
}
|
||||
if (_visibleWindows.Any(window => window.isInputOccupied))
|
||||
|
||||
// 如果有任何可见窗口占用了输入,则阻止其他窗口通过按键进行操作
|
||||
if(_visibleWindows.Any(window => window && window.isInputOccupied))
|
||||
return;
|
||||
|
||||
foreach (var window in _allWindows)
|
||||
{
|
||||
// 确保窗口不为空且其GameObject未被销毁
|
||||
if (!window || !window.gameObject) continue;
|
||||
|
||||
// 检查窗口是否设置了有效的激活按键,并且该按键在本帧被按下
|
||||
if (window.actionButton == KeyCode.None || !Input.GetKeyDown(window.actionButton)) continue;
|
||||
|
||||
if (window.IsVisible)
|
||||
{
|
||||
// 如果窗口当前是可见的,且未占用输入,则通过按键隐藏它
|
||||
@ -93,20 +152,26 @@ namespace Base
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公开的显示窗口方法
|
||||
/// 公开的显示窗口方法。
|
||||
/// </summary>
|
||||
/// <param name="windowToShow">要显示的窗口</param>
|
||||
/// <param name="windowToShow">要显示的窗口。</param>
|
||||
public void Show(UIBase windowToShow)
|
||||
{
|
||||
if (!windowToShow || windowToShow.IsVisible) return;
|
||||
// 确保窗口不为空且未被销毁,并且当前不可见
|
||||
if (!windowToShow || !windowToShow.gameObject || windowToShow.IsVisible) return;
|
||||
|
||||
// 如果窗口是独占的,隐藏所有其他窗口
|
||||
if (windowToShow.exclusive)
|
||||
{
|
||||
var windowsToHide = new List<UIBase>(_visibleWindows);
|
||||
// 创建一个副本进行迭代,防止在 Hide() 调用中修改 _visibleWindows 导致迭代器失效
|
||||
var windowsToHide = _visibleWindows.ToList();
|
||||
foreach (var visibleWindow in windowsToHide)
|
||||
{
|
||||
Hide(visibleWindow);
|
||||
// 确保窗口不是要显示的窗口本身,且仍然可见
|
||||
if (visibleWindow && visibleWindow != windowToShow && visibleWindow.IsVisible)
|
||||
{
|
||||
Hide(visibleWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,62 +180,93 @@ namespace Base
|
||||
var itick = windowToShow as ITickUI;
|
||||
if (itick != null)
|
||||
Base.Clock.AddTickUI(itick);
|
||||
|
||||
// 触发事件通知窗口可见性已改变
|
||||
OnWindowVisibilityChanged?.Invoke(windowToShow, true); // <--- 修改点 2
|
||||
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称显示UI窗口。
|
||||
/// </summary>
|
||||
/// <param name="uiName">要显示的UI窗口名称。</param>
|
||||
public void Show(string uiName)
|
||||
{
|
||||
foreach (var window in _allWindows)
|
||||
// 使用 GetWindow 方法获取窗口实例
|
||||
var window = GetWindow(uiName);
|
||||
if (window != null)
|
||||
{
|
||||
if (window.name == uiName)
|
||||
{
|
||||
Show(window);
|
||||
return;
|
||||
}
|
||||
Show(window);
|
||||
return;
|
||||
}
|
||||
Debug.LogWarning($"未找到窗口{uiName}");
|
||||
Debug.LogWarning($"[UIInputControl] 未找到名称为 '{uiName}' 的窗口来显示。");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 公开的隐藏窗口方法
|
||||
/// 公开的隐藏窗口方法。
|
||||
/// </summary>
|
||||
/// <param name="windowToHide">要隐藏的窗口</param>
|
||||
/// <param name="windowToHide">要隐藏的窗口。</param>
|
||||
public void Hide(UIBase windowToHide)
|
||||
{
|
||||
if (!windowToHide || !windowToHide.IsVisible) return;
|
||||
// 确保窗口不为空且未被销毁,并且当前可见
|
||||
if (!windowToHide || !windowToHide.gameObject || !windowToHide.IsVisible) return;
|
||||
|
||||
// 隐藏目标窗口并更新缓存与暂停状态
|
||||
// 隐藏目标窗口
|
||||
windowToHide.Hide();
|
||||
// 当UI窗口被隐藏时,如果它实现了ITickUI接口,则必须将其从Clock中移除。
|
||||
// 这防止了隐藏窗口继续被Tick,避免性能开销和潜在的NullReferenceException。
|
||||
if (windowToHide is ITickUI iTick)
|
||||
Base.Clock.RemoveTickUI(iTick);
|
||||
|
||||
// 触发事件通知窗口可见性已改变
|
||||
OnWindowVisibilityChanged?.Invoke(windowToHide, false); // <--- 修改点 3
|
||||
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称隐藏UI窗口。
|
||||
/// </summary>
|
||||
/// <param name="uiName">要隐藏的UI窗口名称。</param>
|
||||
public void Hide(string uiName)
|
||||
{
|
||||
foreach (var visibleWindow in _visibleWindows)
|
||||
// 仅在可见窗口中查找并隐藏,更符合HideByName的即时操作期望
|
||||
// 如果需要隐藏所有名称匹配的窗口(包括隐藏的),则需要遍历 _allWindows
|
||||
var visibleWindowToHide = _visibleWindows.FirstOrDefault(window => window != null && window.name == uiName && window.IsVisible);
|
||||
if (visibleWindowToHide != null)
|
||||
{
|
||||
if (visibleWindow.name == uiName)
|
||||
Hide(visibleWindowToHide);
|
||||
return;
|
||||
}
|
||||
Debug.LogWarning($"[UIInputControl] 未找到名称为 '{uiName}' 且当前可见的窗口来隐藏。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏所有当前可见的UI窗口。
|
||||
/// </summary>
|
||||
public void HideAll()
|
||||
{
|
||||
// 创建 _visibleWindows 的一个副本进行迭代,以避免在循环中修改原列表导致迭代器失效
|
||||
var windowsToHide = _visibleWindows.ToList();
|
||||
foreach (var visibleWindow in windowsToHide)
|
||||
{
|
||||
// 再次检查窗口是否仍然可见,因为其他操作可能已经隐藏了它
|
||||
if (visibleWindow != null && visibleWindow.IsVisible)
|
||||
{
|
||||
Hide(visibleWindow);
|
||||
break;
|
||||
Hide(visibleWindow); // Hide() 方法会触发 OnWindowVisibilityChanged 事件
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HideAll()
|
||||
{
|
||||
foreach (var visibleWindow in _visibleWindows)
|
||||
{
|
||||
Hide(visibleWindow);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据当前所有可见窗口的 needPause 属性来更新游戏时钟的暂停状态
|
||||
/// 根据当前所有可见窗口的 needPause 属性来更新游戏时钟的暂停状态。
|
||||
/// </summary>
|
||||
private void UpdatePauseState()
|
||||
{
|
||||
var shouldPause = _visibleWindows.Any(w => w.needPause);
|
||||
// 确保 _visibleWindows 中的窗口都有效
|
||||
var shouldPause = _visibleWindows.Any(w => w && w.needPause);
|
||||
if (Base.Clock.Instance.Pause != shouldPause)
|
||||
{
|
||||
Base.Clock.Instance.Pause = shouldPause;
|
||||
@ -178,35 +274,55 @@ namespace Base
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新当前可见窗口的缓存列表
|
||||
/// 更新当前可见窗口的缓存列表。
|
||||
/// </summary>
|
||||
private void UpdateVisibleWindowsCache()
|
||||
{
|
||||
_visibleWindows.Clear();
|
||||
foreach (var window in _allWindows)
|
||||
{
|
||||
if (window.IsVisible)
|
||||
if (window && window.IsVisible) // 确保窗口有效且可见
|
||||
{
|
||||
_visibleWindows.Add(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当脚本实例被销毁时调用。
|
||||
/// 用于在销毁时取消订阅场景加载事件,防止内存泄漏。
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
// 在销毁时清空所有订阅者,防止因MonoSingleton持久化导致下一场景重新加载时出现旧的订阅者(如果单例不销毁,事件本身也不会自动清空订阅)
|
||||
// 如果 UIInputControl 是一个会随场景销毁的普通 MonoBehaviour 而不是持久化的 MonoSingleton, 某些情况下清除订阅者可以防止跨场景的引用问题。
|
||||
// 但对于 MonoSingleton,它通常是持久化的,所以事件在重新加载场景后仍然保留。明确清空可以避免不必要的资源占用,虽然在应用程序关闭时会自动释放。
|
||||
// OnWindowVisibilityChanged = null; // 谨慎使用,如果外部有长期订阅的需求,清空可能导致问题。通常更推荐外部在OnDestroy中取消订阅。
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MonoSingleton 的 OnStart 方法,在单例首次创建并激活时调用,早于普通的 Start 方法。
|
||||
/// 用于订阅场景加载事件并在首次启动时注册UI窗口。
|
||||
/// </summary>
|
||||
protected override void OnStart()
|
||||
{
|
||||
// 订阅场景加载事件,以便在新场景加载后重新注册UI窗口
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
|
||||
// 首次启动时也注册一次窗口(例如在进入第一个场景时)。
|
||||
RegisterAllWindows();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当场景加载完成时调用。
|
||||
/// 用于在场景加载后重新查找并注册所有UI窗口。
|
||||
/// </summary>
|
||||
/// <param name="scene">已加载的场景。</param>
|
||||
/// <param name="mode">场景加载模式。</param>
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
// 当场景加载时,重新查找并注册所有UI窗口
|
||||
RegisterAllWindows();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user