using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using UnityEngine.UI; // DOTween 插件引用 using Cysharp.Threading.Tasks; using System; public enum UILayer { Static, // 静态UI 适合做 战斗 主界面 Bottom, // 部分界面特殊处理层级用 Mid, // 大部分功能窗口都放这层,便于跳转上下层管理(一个界面可以同时存在多个) System, // 网络弹窗,信息提示等,其他重要弹窗 Loading, // 加载界面 } public enum UIAnimationType { None, // 无动画 FadeInOut, // 淡入淡出 ScaleInOut, // 缩放 SlideFromTop, // 从顶部滑入 SlideFromBottom, // 从底部滑入 SlideFromLeft, // 从左侧滑入 SlideFromRight, // 从右侧滑入 ScaleOverInOut,// 缩放根据曲线 } [RequireComponent(typeof(Canvas))] [RequireComponent(typeof(CanvasGroup))] [RequireComponent(typeof(CanvasScaler))] public class UIBase : MonoBehaviour { #region 属性和变量 // UI基本属性 [SerializeField] public UILayer uiLayer = UILayer.Mid; [SerializeField][HideInInspector] public string uiName; [SerializeField] public bool isMainUI = false; //同时勾选supportParentChildRelation 会当作新的父节点,但不作为子节点 // 是否支持父子关系,即UI拥有的上下级链式关系 // 拥有父子关系 在关闭父界面的时候 子界面会连同一起关闭 子界面打开时 // 在非特定情况下 都要拥有父子关系 (一般来说功能都要有父子关系 例外的是例如系统弹窗 // 附加说明:功能基本做在Middle层 [SerializeField] public bool supportParentChildRelation = true; // 持久化相关 [SerializeField] public bool isPersistent = false; [SerializeField][HideInInspector] public int maxIdleRounds = 20; // 动画相关 [SerializeField] public UIAnimationType openAnimationType = UIAnimationType.None; [SerializeField] public UIAnimationType closeAnimationType = UIAnimationType.None; [SerializeField]/*[HideInInspector]*/ public float animeDuration = 0.2f; [SerializeField]public TweenCurve scaleOverInOutCurve; [SerializeField][HideInInspector] public Ease animationEase = Ease.OutQuad; // 确保使用 DG.Tweening.Ease // 运行时状态 [HideInInspector] public int lastUsedRound = 0; [HideInInspector] public UIBase parentUI; // 子UI管理 [HideInInspector] public List childrenUI = new List(); [Header("所有UI排版应该在此节点内层")] [SerializeField] protected RectTransform _rectTransform; //界面默认添加根节点用于表现界面开启关闭动画,或者设置适配用 //遮罩组件在界面开发中生成,遮罩开关和点击空白为其中的组件特性 // 打开遮罩 [Header("遮罩(透明)开关")] [SerializeField] public bool openMask = false; // 默认点击空白区域关闭界面 [Header("点击空白关闭")] [SerializeField] public bool clickEmptySpaceClose = false; public GameObject screenMask = null; private Button btnClickEmptyClose; public Action btnClickEmptyCloseEvent = null; //提供点击空白区域关闭界面的回调 // 跟OneLevelWin联动 实际上是需要继承自OneLevelWin才能生效的值 使用需要注意 int m_FunctionOrder = 0; public int functionOrder { get { return m_FunctionOrder; } set { m_FunctionOrder = value; } } // 内部状态 protected bool isActive = false; protected bool isAnimating = false; protected bool isClosing = false; // 新增:标记是否正在关闭 // 组件引用 protected Canvas canvas; protected CanvasGroup canvasGroup; // 动画相关 protected Vector3 originalPosition; protected Sequence currentAnimation; public CanvasScaler canvasScaler { get; private set; } public const int SafeHeightUp = 50; public const int SafeHeightDown = 30; #endregion #region Unity生命周期 protected virtual void Awake() { try { InitComponentInternal(); } catch (Exception e) { Debug.LogError($"{uiName}界面的InitComponentInternal报错: {e.StackTrace}"); } try { InitComponent(); } catch (Exception e) { Debug.LogError($"{uiName}界面的InitComponent报错: {e.StackTrace}"); } // 保存原始值用于动画 if (_rectTransform != null) { if (Screen.height / Screen.width > 1.8)//宽屏需要适配 { //上下各间隔SafeWidth _rectTransform.offsetMax = new Vector2(0, -SafeHeightUp); //上 _rectTransform.offsetMin = new Vector2(0, SafeHeightDown); //下 } originalPosition = _rectTransform.anchoredPosition; } if (screenMask != null) screenMask.transform.SetAsFirstSibling(); InitClickEmptySpaceBtn(); } protected virtual void Start() { // 子类可以重写此方法进行额外初始化 } protected async UniTask ApplyClickEmptySpaceClose() { if (clickEmptySpaceClose) { //延迟x帧后可点击,防止点击过快立即关闭了 await UniTask.Delay(200); btnClickEmptyClose.enabled = true; } } private void InitClickEmptySpaceBtn() { if (!clickEmptySpaceClose) { return; } btnClickEmptyClose = screenMask.GetComponent