using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.EventSystems; using DG.Tweening; using YooAsset; namespace Stary.Evo { public enum UIBlackType { None, // 无黑边,全适应 Height, // 保持高度填满,两边黑边 Width, // 保持宽度填满, 上下黑边 AutoBlack, // 自动黑边(选中左右或上下黑边最少的一方) } public class UIManager : SingletonMono { public int width = 1920; public int height = 1080; public UIBlackType uiBlackType = UIBlackType.None; private Transform _root; private Camera _worldCamera; private Camera _uiCamera; /// /// 屏幕渐变遮罩 /// private CanvasGroup _blackMask; private CanvasGroup _backgroundMask; private Tweener _fadeTweener; /// /// 黑边 /// private RectTransform[] _blacks = new RectTransform[2]; private Dictionary _viewControllers; private Dictionary _layers; private HashSet _openViews; private HashSet _residentViews; public EventSystem EventSystem { get; private set; } private void Awake() { Initialize(); } private void Initialize() { _layers = new Dictionary(); _viewControllers = new Dictionary(); _openViews = new HashSet(); _residentViews = new HashSet(); _worldCamera = Camera.main; _worldCamera.cullingMask &= int.MaxValue ^ (1 << Layer.UI); var root = GameObject.Find("UIRoot"); if (root == null) { root = new GameObject("UIRoot"); } root.layer = Layer.UI; GameObject.DontDestroyOnLoad(root); _root = root.transform; var camera = GameObject.Find("UICamera"); if (camera == null) { camera = new GameObject("UICamera"); } _uiCamera = camera.GetOrAddComponent(); _uiCamera.cullingMask = 1 << Layer.UI; _uiCamera.transform.SetParent(_root); _uiCamera.orthographic = true; _uiCamera.clearFlags = CameraClearFlags.Depth; EventSystem = EventSystem.current; var layers = Enum.GetValues(typeof(UILayer)); foreach (UILayer layer in layers) { bool is3d = layer == UILayer.SceneLayer; Canvas layerCanvas = UIExtension.CreateLayerCanvas(layer, is3d, _root, is3d ? _worldCamera : _uiCamera, width, height); UILayerLogic uILayerLogic = new UILayerLogic(layer, layerCanvas); _layers.Add(layer, uILayerLogic); } _blackMask = UIExtension.CreateBlackMask(_layers[UILayer.BlackMaskLayer].canvas.transform); _backgroundMask = UIExtension.CreateBlackMask(_layers[UILayer.BackgroundLayer].canvas.transform); } private void Update() { // TODO 不应该Update设置应该放在屏幕状态变动事件里 ChangeOrCreateBlack(); } /// /// 创建或者调整黑边,需间隔触发,由于有些设备屏幕是可以转动,是动态的 /// private void ChangeOrCreateBlack() { if (_layers == null) return; var parent = _layers[UILayer.BackgroundLayer].canvas.transform as RectTransform; var uIBlackType = GetUIBlackType(); switch (uIBlackType) { case UIBlackType.Height: // 高度适配时的左右黑边 var rect = _blacks[0]; if (rect == null) { _blacks[0] = rect = UIExtension.CreateBlackMask(parent, 1, "right").transform as RectTransform; } else if (Mathf.Abs(rect.anchoredPosition.x * 2 + parent.rect.width - width) < 1) { return; } rect.pivot = new Vector2(0, 0.5f); rect.anchorMin = new Vector2(1, 0); rect.anchorMax = new Vector2(1, 1); rect.sizeDelta = new Vector2(Mathf.Abs(width - parent.rect.width), 0); rect.anchoredPosition = new Vector2((width - parent.rect.width) / 2, 0); rect = _blacks[1]; if (rect == null) { _blacks[1] = rect = UIExtension.CreateBlackMask(parent, 1, "left").transform as RectTransform; } rect.pivot = new Vector2(1, 0.5f); rect.anchorMin = new Vector2(0, 0); rect.anchorMax = new Vector2(0, 1); rect.sizeDelta = new Vector2(Mathf.Abs(width - parent.rect.width), 0); rect.anchoredPosition = new Vector2(-(width - parent.rect.width) / 2, 0); break; case UIBlackType.Width: // 宽度适配时的上下黑边 rect = _blacks[0]; if (rect == null) { _blacks[0] = rect = UIExtension.CreateBlackMask(parent, 1, "top").transform as RectTransform; } else if (Mathf.Abs(rect.anchoredPosition.y * 2 + parent.rect.height - height) < 1) { return; } rect.pivot = new Vector2(0.5f, 0); rect.anchorMin = new Vector2(0, 1); rect.anchorMax = new Vector2(1, 1); rect.sizeDelta = new Vector2(0, Mathf.Abs(height - parent.rect.height)); rect.anchoredPosition = new Vector2(0, (height - parent.rect.height) / 2); rect = _blacks[1]; if (rect == null) { _blacks[1] = rect = UIExtension.CreateBlackMask(parent, 1, "bottom").transform as RectTransform; } rect.pivot = new Vector2(0.5f, 1); rect.anchorMin = new Vector2(0, 0); rect.anchorMax = new Vector2(1, 0); rect.sizeDelta = new Vector2(0, Mathf.Abs(height - parent.rect.height)); rect.anchoredPosition = new Vector2(0, -(height - parent.rect.height) / 2); break; default: break; } } public UIBlackType GetUIBlackType() { var uIBlackType = uiBlackType; if (uIBlackType == UIBlackType.AutoBlack) { var parent = _layers[UILayer.BackgroundLayer].canvas.transform as RectTransform; float widthDis = Mathf.Abs(width - parent.rect.width); float heightDis = Mathf.Abs(height - parent.rect.height); if (widthDis < 1 && heightDis < 1) uIBlackType = UIBlackType.None; else if (widthDis > heightDis) uIBlackType = UIBlackType.Height; else uIBlackType = UIBlackType.Width; } return uIBlackType; } public Rect GetSafeArea() { Rect rect = Screen.safeArea; if (uiBlackType == UIBlackType.Width) { var parent = _layers[UILayer.BackgroundLayer].canvas.transform as RectTransform; float blackArea = Mathf.Abs(height - parent.rect.height) / 2; rect.yMin = Mathf.Max(0, rect.yMin - blackArea); rect.yMax = Mathf.Min(rect.yMax + blackArea, Screen.height); } else if (uiBlackType == UIBlackType.Height) { var parent = _layers[UILayer.BackgroundLayer].canvas.transform as RectTransform; float blackArea = Mathf.Abs(width - parent.rect.width) / 2; rect.xMin = Mathf.Max(0, rect.xMin - blackArea); rect.xMax = Mathf.Min(rect.xMax + blackArea, Screen.width); } return rect; } public void EnableBackgroundMask(bool enable) { _backgroundMask.alpha = enable ? 1 : 0; } public async UniTask InitUIConfig() { // 初始化需要加载所有UI的配置 var handle = YooAssets.LoadAssetSync("Config_UIConfig"); await handle.Task; UIConfig uiConfig = handle.GetAssetObject(); if (uiConfig == null) { Debug.LogError($"UnityEvo:UIConfig初始化失败,请检查配置文件"); return; } var list = uiConfig.uiConfigJsons; foreach (var cfg in list) { if (_viewControllers.ContainsKey(cfg.uiType)) { Debug.LogErrorFormat("存在相同的uiType:{0}, 请检查UIConfig是否重复!", cfg.uiType.ToString()); continue; } _viewControllers.Add(cfg.uiType, new UIViewController { uiPath = cfg.pathId, uiType = cfg.uiType, uiLayer = _layers[cfg.uiLayer], isWindow = cfg.isWindow, }); } } /// /// 注册常驻UI /// public void AddResidentUI() where T : UIView { _residentViews.Add(typeof(T).Name); } public void Open(object userData = null, Action callback = null) where T : UIView { if (!_viewControllers.ContainsKey(typeof(T).Name)) { Debug.LogErrorFormat("未配置uiType:{0}, 请检查UIConfig.cs!", typeof(T).Name); return; } _openViews.Add(typeof(T).Name); _viewControllers[typeof(T).Name].Open(userData, callback); } public async UniTask Preload() where T : UIView { if (!_viewControllers.TryGetValue(typeof(T).Name, out var controller)) { Debug.LogErrorFormat("未配置uiType:{0}, 请检查UIConfig.cs!", typeof(T).Name); return; } await controller.Load(); } public async UniTask PreloadAll() { foreach (var controller in _viewControllers.Values) { var handle = YooAssets.LoadAssetSync(controller.uiPath); await handle.Task; GameObject gameObject = handle.GetAssetObject(); } } public bool IsOpen() where T : UIView { return _openViews.Contains(typeof(T).Name); } public void Close(Action callback = null) where T : UIView { if (!_viewControllers.ContainsKey(typeof(T).Name)) { Debug.LogErrorFormat("未配置uiType:{0}, 请检查UIConfig.cs!", typeof(T).Name); return; } _openViews.Remove(typeof(T).Name); _viewControllers[typeof(T).Name].Close(callback); } public void Close(T view, Action callback = null) where T : UIView { Close(); } /// /// UI建议都用事件进行交互,最好不使用该接口 /// public T GetView() where T : UIView { if (!_viewControllers.ContainsKey(typeof(T).Name)) { Debug.LogErrorFormat("未配置uiType:{0}, 请检查UIConfig.cs!", typeof(T).Name); return null; } return _viewControllers[typeof(T).Name].uiView as T; } public void CloseAll(bool closeResidentView = false) { var list = ListPool.Get(); foreach (var uiType in _openViews) { if (closeResidentView || !_residentViews.Contains(uiType)) { _viewControllers[uiType].Close(); list.Add(uiType); } } foreach (var uiType in list) { _openViews.Remove(uiType); } ListPool.Release(list); } public void ReleaseAll() { foreach (var controller in _viewControllers.Values) { if (!_residentViews.Contains(controller.uiType)) { _openViews.Remove(controller.uiType); controller.Release(); } } } public void FadeIn(float duration = 0.5f, TweenCallback callback = null) { if (_fadeTweener != null && _fadeTweener.IsPlaying()) _fadeTweener.Complete(); _fadeTweener = _blackMask.DOFade(1.0f, duration); _fadeTweener.onComplete = callback; } public void FadeOut(float duration = 0.5f, TweenCallback callback = null) { if (_fadeTweener != null && _fadeTweener.IsPlaying()) _fadeTweener.Complete(); _fadeTweener = _blackMask.DOFade(0.0f, duration); _fadeTweener.onComplete = callback; } public void FadeInOut(float duration = 1.0f, TweenCallback callback = null) { if (_fadeTweener != null && _fadeTweener.IsPlaying()) _fadeTweener.Complete(); _fadeTweener = _blackMask.DOFade(1.0f, duration * 0.5f); _fadeTweener.onComplete += () => { _fadeTweener = _blackMask.DOFade(0.0f, duration * 0.5f); _fadeTweener.onComplete = callback; }; } public void Cancel() { if (_layers.TryGetValue(UILayer.NormalLayer, out var layer) && layer.openedViews.Count > 0) { var viewController = layer.openedViews.Peek(); if (viewController.uiView != null) { viewController.uiView.OnCancel(); } } } } }