SD-20250415ABSO\Administrator 321e38cb79 冠军框架迁移
2025-04-18 19:18:15 +08:00

700 lines
21 KiB
C#

using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using YooAsset;
namespace TEngine
{
/// <summary>
/// UI模块。
/// </summary>
[DisallowMultipleComponent]
public sealed partial class UIModule : Module
{
[SerializeField] private Transform m_InstanceRoot = null;
[SerializeField] private bool m_dontDestroyUIRoot = true;
[SerializeField] private bool m_enableErrorLog = true;
[SerializeField] private Camera m_UICamera = null;
private readonly List<UIWindow> _stack = new List<UIWindow>(100);
public const int LAYER_DEEP = 2000;
public const int WINDOW_DEEP = 100;
public const int WINDOW_HIDE_LAYER = 2; // Ignore Raycast
public const int WINDOW_SHOW_LAYER = 5; // UI
/// <summary>
/// UI根节点。
/// </summary>
public Transform UIRoot => m_InstanceRoot;
public static Transform UIRootStatic;
/// <summary>
/// UI根节点。
/// </summary>
public Camera UICamera => m_UICamera;
private UIModuleImpl _uiModuleImpl;
private ErrorLogger _errorLogger;
private void Start()
{
RootModule rootModule = ModuleSystem.GetModule<RootModule>();
if (rootModule == null)
{
Log.Fatal("Base component is invalid.");
return;
}
_uiModuleImpl = ModuleImpSystem.GetModule<UIModuleImpl>();
_uiModuleImpl.Initialize(_stack);
if (m_InstanceRoot == null)
{
m_InstanceRoot = new GameObject("UI Form Instances").transform;
m_InstanceRoot.SetParent(gameObject.transform);
m_InstanceRoot.localScale = Vector3.one;
}
else if (m_dontDestroyUIRoot)
{
DontDestroyOnLoad(m_InstanceRoot.parent != null ? m_InstanceRoot.parent : m_InstanceRoot);
}
m_InstanceRoot.gameObject.layer = LayerMask.NameToLayer("UI");
UIRootStatic = m_InstanceRoot;
switch (GameModule.Debugger.ActiveWindowType)
{
case DebuggerActiveWindowType.AlwaysOpen:
m_enableErrorLog = true;
break;
case DebuggerActiveWindowType.OnlyOpenWhenDevelopment:
m_enableErrorLog = Debug.isDebugBuild;
break;
case DebuggerActiveWindowType.OnlyOpenInEditor:
m_enableErrorLog = Application.isEditor;
break;
default:
m_enableErrorLog = false;
break;
}
if (m_enableErrorLog)
{
_errorLogger = new ErrorLogger();
}
}
private void OnDestroy()
{
if (_errorLogger != null)
{
_errorLogger.Dispose();
_errorLogger = null;
}
CloseAll(isShutDown:true);
if (m_InstanceRoot != null && m_InstanceRoot.parent != null)
{
Destroy(m_InstanceRoot.parent.gameObject);
}
}
#region
/// <summary>
/// 设置屏幕安全区域(异形屏支持)。
/// </summary>
/// <param name="safeRect">安全区域</param>
public static void ApplyScreenSafeRect(Rect safeRect)
{
CanvasScaler scaler = UIRootStatic.GetComponentInParent<CanvasScaler>();
if (scaler == null)
{
Log.Error($"Not found {nameof(CanvasScaler)} !");
return;
}
// Convert safe area rectangle from absolute pixels to UGUI coordinates
float rateX = scaler.referenceResolution.x / Screen.width;
float rateY = scaler.referenceResolution.y / Screen.height;
float posX = (int)(safeRect.position.x * rateX);
float posY = (int)(safeRect.position.y * rateY);
float width = (int)(safeRect.size.x * rateX);
float height = (int)(safeRect.size.y * rateY);
float offsetMaxX = scaler.referenceResolution.x - width - posX;
float offsetMaxY = scaler.referenceResolution.y - height - posY;
// 注意:安全区坐标系的原点为左下角
var rectTrans = UIRootStatic.transform as RectTransform;
if (rectTrans != null)
{
rectTrans.offsetMin = new Vector2(posX, posY); //锚框状态下的屏幕左下角偏移向量
rectTrans.offsetMax = new Vector2(-offsetMaxX, -offsetMaxY); //锚框状态下的屏幕右上角偏移向量
}
}
/// <summary>
/// 模拟IPhoneX异形屏
/// </summary>
public static void SimulateIPhoneXNotchScreen()
{
Rect rect;
if (Screen.height > Screen.width)
{
// 竖屏Portrait
float deviceWidth = 1125;
float deviceHeight = 2436;
rect = new Rect(0f / deviceWidth, 102f / deviceHeight, 1125f / deviceWidth, 2202f / deviceHeight);
}
else
{
// 横屏Landscape
float deviceWidth = 2436;
float deviceHeight = 1125;
rect = new Rect(132f / deviceWidth, 63f / deviceHeight, 2172f / deviceWidth, 1062f / deviceHeight);
}
Rect safeArea = new Rect(Screen.width * rect.x, Screen.height * rect.y, Screen.width * rect.width, Screen.height * rect.height);
ApplyScreenSafeRect(safeArea);
}
#endregion
/// <summary>
/// 获取所有层级下顶部的窗口名称。
/// </summary>
public string GetTopWindow()
{
if (_stack.Count == 0)
{
return string.Empty;
}
UIWindow topWindow = _stack[^1];
return topWindow.WindowName;
}
/// <summary>
/// 获取指定层级下顶部的窗口名称。
/// </summary>
public string GetTopWindow(int layer)
{
UIWindow lastOne = null;
for (int i = 0; i < _stack.Count; i++)
{
if (_stack[i].WindowLayer == layer)
lastOne = _stack[i];
}
if (lastOne == null)
return string.Empty;
return lastOne.WindowName;
}
/// <summary>
/// 是否有任意窗口正在加载。
/// </summary>
public bool IsAnyLoading()
{
for (int i = 0; i < _stack.Count; i++)
{
var window = _stack[i];
if (window.IsLoadDone == false)
return true;
}
return false;
}
/// <summary>
/// 查询窗口是否存在。
/// </summary>
/// <typeparam name="T">界面类型。</typeparam>
/// <returns>是否存在。</returns>
public bool HasWindow<T>()
{
return HasWindow(typeof(T));
}
/// <summary>
/// 查询窗口是否存在。
/// </summary>
/// <param name="type">界面类型。</param>
/// <returns>是否存在。</returns>
public bool HasWindow(Type type)
{
return IsContains(type.FullName);
}
/// <summary>
/// 异步打开窗口。
/// </summary>
/// <param name="userDatas">用户自定义数据。</param>
/// <returns>打开窗口操作句柄。</returns>
public void ShowUIAsync<T>(params System.Object[] userDatas) where T : UIWindow
{
ShowUIImp(typeof(T), true, userDatas);
}
/// <summary>
/// 异步打开窗口。
/// </summary>
/// <param name="type">界面类型。</param>
/// <param name="userDatas">用户自定义数据。</param>
/// <returns>打开窗口操作句柄。</returns>
public void ShowUIAsync(Type type, params System.Object[] userDatas)
{
ShowUIImp(type, true, userDatas);
}
/// <summary>
/// 同步打开窗口。
/// </summary>
/// <typeparam name="T">窗口类。</typeparam>
/// <param name="userDatas">用户自定义数据。</param>
/// <returns>打开窗口操作句柄。</returns>
public void ShowUI<T>(params System.Object[] userDatas) where T : UIWindow
{
ShowUIImp(typeof(T), false, userDatas);
}
/// <summary>
/// 异步打开窗口。
/// </summary>
/// <param name="userDatas">用户自定义数据。</param>
/// <returns>打开窗口操作句柄。</returns>
public async UniTask<UIWindow> ShowUIAsyncAwait<T>(params System.Object[] userDatas) where T : UIWindow
{
return await ShowUIAwaitImp(typeof(T), true, userDatas);
}
/// <summary>
/// 同步打开窗口。
/// </summary>
/// <param name="type"></param>
/// <param name="userDatas"></param>
/// <returns>打开窗口操作句柄。</returns>
public void ShowUI(Type type, params System.Object[] userDatas)
{
ShowUIImp(type, false, userDatas);
}
private void ShowUIImp(Type type, bool isAsync, params System.Object[] userDatas)
{
string windowName = type.FullName;
// 如果窗口已经存在
if (IsContains(windowName))
{
UIWindow window = GetWindow(windowName);
Pop(window); //弹出窗口
Push(window); //重新压入
window.TryInvoke(OnWindowPrepare, userDatas);
}
else
{
UIWindow window = CreateInstance(type);
Push(window); //首次压入
window.InternalLoad(window.AssetName, OnWindowPrepare, isAsync, userDatas).Forget();
}
}
private async UniTask<UIWindow> ShowUIAwaitImp(Type type, bool isAsync, params System.Object[] userDatas)
{
string windowName = type.FullName;
// 如果窗口已经存在
if (IsContains(windowName))
{
UIWindow window = GetWindow(windowName);
Pop(window); //弹出窗口
Push(window); //重新压入
window.TryInvoke(OnWindowPrepare, userDatas);
return window;
}
else
{
UIWindow window = CreateInstance(type);
Push(window); //首次压入
window.InternalLoad(window.AssetName, OnWindowPrepare, isAsync, userDatas).Forget();
float time = 0f;
while (!window.IsLoadDone)
{
time += Time.deltaTime;
if (time > 60f)
{
break;
}
await UniTask.Yield();
}
return window;
}
}
/// <summary>
/// 关闭窗口
/// </summary>
public void CloseUI<T>() where T : UIWindow
{
CloseUI(typeof(T));
}
public void CloseUI(Type type)
{
string windowName = type.FullName;
UIWindow window = GetWindow(windowName);
if (window == null)
return;
window.InternalDestroy();
Pop(window);
OnSortWindowDepth(window.WindowLayer);
OnSetWindowVisible();
}
public void HideUI<T>() where T : UIWindow
{
HideUI(typeof(T));
}
public void HideUI(Type type)
{
string windowName = type.FullName;
UIWindow window = GetWindow(windowName);
if (window == null)
{
return;
}
if (window.HideTimeToClose <= 0)
{
CloseUI(type);
return;
}
window.Visible = false;
window.IsHide = true;
window.HideTimerId = GameModule.Timer.AddTimer((arg) =>
{
CloseUI(type);
},window.HideTimeToClose);
if (window.FullScreen)
{
OnSetWindowVisible();
}
}
/// <summary>
/// 关闭所有窗口。
/// </summary>
public void CloseAll(bool isShutDown = false)
{
for (int i = 0; i < _stack.Count; i++)
{
UIWindow window = _stack[i];
window.InternalDestroy(isShutDown);
}
_stack.Clear();
}
/// <summary>
/// 关闭所有窗口除了。
/// </summary>
public void CloseAllWithOut(UIWindow withOut)
{
for (int i = _stack.Count - 1; i >= 0; i--)
{
UIWindow window = _stack[i];
if (window == withOut)
{
continue;
}
window.InternalDestroy();
_stack.RemoveAt(i);
}
}
/// <summary>
/// 关闭所有窗口除了。
/// </summary>
public void CloseAllWithOut<T>() where T : UIWindow
{
for (int i = _stack.Count - 1; i >= 0; i--)
{
UIWindow window = _stack[i];
if (window.GetType() == typeof(T))
{
continue;
}
window.InternalDestroy();
_stack.RemoveAt(i);
}
}
private void OnWindowPrepare(UIWindow window)
{
OnSortWindowDepth(window.WindowLayer);
window.InternalCreate();
window.InternalRefresh();
OnSetWindowVisible();
}
private void OnSortWindowDepth(int layer)
{
int depth = layer * LAYER_DEEP;
for (int i = 0; i < _stack.Count; i++)
{
if (_stack[i].WindowLayer == layer)
{
_stack[i].Depth = depth;
depth += WINDOW_DEEP;
}
}
}
private void OnSetWindowVisible()
{
bool isHideNext = false;
for (int i = _stack.Count - 1; i >= 0; i--)
{
UIWindow window = _stack[i];
if (isHideNext == false)
{
if (window.IsHide)
{
continue;
}
window.Visible = true;
if (window.IsPrepare && window.FullScreen)
{
isHideNext = true;
}
}
else
{
window.Visible = false;
}
}
}
private UIWindow CreateInstance(Type type)
{
UIWindow window = Activator.CreateInstance(type) as UIWindow;
WindowAttribute attribute = Attribute.GetCustomAttribute(type, typeof(WindowAttribute)) as WindowAttribute;
if (window == null)
throw new GameFrameworkException($"Window {type.FullName} create instance failed.");
if (attribute != null)
{
string assetName = string.IsNullOrEmpty(attribute.Location) ? type.Name : attribute.Location;
window.Init(type.FullName, attribute.WindowLayer, attribute.FullScreen, assetName, attribute.FromResources, attribute.HideTimeToClose);
}
else
{
window.Init(type.FullName, (int)UILayer.UI, fullScreen: window.FullScreen, assetName: type.Name, fromResources: false, hideTimeToClose: 10);
}
return window;
}
/// <summary>
/// 异步获取窗口。
/// </summary>
/// <returns>打开窗口操作句柄。</returns>
public async UniTask<T> GetUIAsyncAwait<T>() where T : UIWindow
{
string windowName = typeof(T).FullName;
var window = GetWindow(windowName);
if (window == null)
{
return null;
}
var ret = window as T;
if (ret == null)
{
return null;
}
if (ret.IsLoadDone)
{
return ret;
}
float time = 0f;
while (!ret.IsLoadDone)
{
time += Time.deltaTime;
if (time > 60f)
{
break;
}
await UniTask.Yield();
}
return ret;
}
/// <summary>
/// 异步获取窗口。
/// </summary>
/// <param name="callback">回调。</param>
/// <returns>打开窗口操作句柄。</returns>
public void GetUIAsync<T>(Action<T> callback) where T : UIWindow
{
string windowName = typeof(T).FullName;
var window = GetWindow(windowName);
if (window == null)
{
return;
}
var ret = window as T;
if (ret == null)
{
return;
}
GetUIAsyncImp(callback).Forget();
async UniTaskVoid GetUIAsyncImp(Action<T> ctx)
{
float time = 0f;
while (!ret.IsLoadDone)
{
time += Time.deltaTime;
if (time > 60f)
{
break;
}
await UniTask.Yield();
}
ctx?.Invoke(ret);
}
}
private UIWindow GetWindow(string windowName)
{
for (int i = 0; i < _stack.Count; i++)
{
UIWindow window = _stack[i];
if (window.WindowName == windowName)
{
return window;
}
}
return null;
}
private bool IsContains(string windowName)
{
for (int i = 0; i < _stack.Count; i++)
{
UIWindow window = _stack[i];
if (window.WindowName == windowName)
{
return true;
}
}
return false;
}
private void Push(UIWindow window)
{
// 如果已经存在
if (IsContains(window.WindowName))
throw new System.Exception($"Window {window.WindowName} is exist.");
// 获取插入到所属层级的位置
int insertIndex = -1;
for (int i = 0; i < _stack.Count; i++)
{
if (window.WindowLayer == _stack[i].WindowLayer)
{
insertIndex = i + 1;
}
}
// 如果没有所属层级,找到相邻层级
if (insertIndex == -1)
{
for (int i = 0; i < _stack.Count; i++)
{
if (window.WindowLayer > _stack[i].WindowLayer)
{
insertIndex = i + 1;
}
}
}
// 如果是空栈或没有找到插入位置
if (insertIndex == -1)
{
insertIndex = 0;
}
// 最后插入到堆栈
_stack.Insert(insertIndex, window);
}
private void Pop(UIWindow window)
{
// 从堆栈里移除
_stack.Remove(window);
}
}
[UpdateModule]
internal sealed partial class UIModuleImpl : ModuleImp
{
private List<UIWindow> _stack;
internal void Initialize(List<UIWindow> stack)
{
_stack = stack;
}
internal override void Shutdown()
{
}
internal override void Update(float elapseSeconds, float realElapseSeconds)
{
if (_stack == null)
{
return;
}
int count = _stack.Count;
for (int i = 0; i < _stack.Count; i++)
{
if (_stack.Count != count)
{
break;
}
var window = _stack[i];
window.InternalUpdate();
}
}
}
}