| 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; | 
|     [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] protected RectTransform _rectTransform; //界面默认添加根节点用于表现界面开启关闭动画 | 
|      | 
|     [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; | 
|   | 
|     [HideInInspector] public GameObject rootNode; // 根节点 | 
|   | 
|     // 子UI管理 | 
|     [HideInInspector] public List<UIBase> childrenUI = new List<UIBase>(); | 
|   | 
|     //  打开遮罩 | 
|     [SerializeField] public bool openMask = false; | 
|   | 
|     //  点击空白区域关闭界面 | 
|     [SerializeField] public bool clickEmptySpaceClose = false; | 
|   | 
|     private GameObject screenMask = null; | 
|   | 
|     private Button btnClickEmptyClose; | 
|   | 
|     //  跟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; | 
|   | 
|     private CanvasScaler canvasScaler; | 
|   | 
|     #endregion | 
|   | 
|     #region Unity生命周期 | 
|   | 
|     protected virtual void Awake() | 
|     { | 
|         //  防止有人不写base.InitComponent引发错误 所以拆分 | 
|         InitComponentInternal(); | 
|         // 在Awake中进行基本初始化 | 
|         InitComponent(); | 
|   | 
|         // 保存原始值用于动画 | 
|         if (_rectTransform != null) | 
|         { | 
|             originalPosition = _rectTransform.anchoredPosition; | 
|         } | 
|   | 
|         ApplySettings(); | 
|   | 
|         if (openMask) | 
|         { | 
|             screenMask = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/ScreenMask"), transform); | 
|             screenMask.transform.localScale = Vector3.one; | 
|             screenMask.transform.localPosition = Vector3.zero; | 
|             screenMask.transform.SetAsFirstSibling(); | 
|         } | 
|     } | 
|   | 
|     protected virtual void Start() | 
|     { | 
|         // 子类可以重写此方法进行额外初始化 | 
|     } | 
|   | 
|     protected async UniTask ApplySettings() | 
|     { | 
|         if (clickEmptySpaceClose) | 
|         { | 
|             //延迟创建会导致层级在ScreenMask之上 | 
|             GameObject goBtnESC = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/ClickEmptyCloseMask"), transform); | 
|             btnClickEmptyClose = goBtnESC.GetComponent<Button>(); | 
|             btnClickEmptyClose.AddListener(CloseWindow); | 
|             btnClickEmptyClose.transform.SetAsFirstSibling(); | 
|             await UniTask.DelayFrame(5); | 
|   | 
|             btnClickEmptyClose = goBtnESC.GetComponent<Button>(); | 
|             btnClickEmptyClose.AddListener(CloseWindow); | 
|         } | 
|     } | 
|   | 
|   | 
|     protected async void ExecuteNextFrame(Action _action) | 
|     { | 
|         await UniTask.DelayFrame(1); | 
|         _action?.Invoke(); | 
|     } | 
|   | 
|     protected virtual void OnDestroy() | 
|     { | 
|         // 确保动画被正确清理 | 
|         if (currentAnimation != null) | 
|         { | 
|             currentAnimation.Kill(); | 
|             currentAnimation = null; | 
|         } | 
|     } | 
|   | 
|     #endregion | 
|   | 
|     #region 初始化方法 | 
|   | 
|     private void InitComponentInternal() | 
|     { | 
|         // 获取或添加Canvas组件 | 
|         canvas = GetComponent<Canvas>(); | 
|         if (canvas == null) | 
|         { | 
|             canvas = gameObject.AddComponent<Canvas>(); | 
|         } | 
|   | 
|         // 设置Canvas属性 | 
|         canvas.overrideSorting = true; | 
|   | 
|         canvas.worldCamera = CameraManager.uiCamera; | 
|   | 
|         // 获取或添加CanvasGroup组件 | 
|         canvasGroup = GetComponent<CanvasGroup>(); | 
|         if (canvasGroup == null) | 
|         { | 
|             canvasGroup = gameObject.AddComponent<CanvasGroup>(); | 
|         } | 
|   | 
|         // 添加GraphicRaycaster组件(如果没有) | 
|         if (GetComponent<UnityEngine.UI.GraphicRaycaster>() == null) | 
|         { | 
|             gameObject.AddComponent<UnityEngine.UI.GraphicRaycaster>(); | 
|         } | 
|   | 
|         canvasScaler = GetComponent<CanvasScaler>(); | 
|     } | 
|   | 
|     // 获取必要的组件 | 
|     protected virtual void InitComponent() | 
|     { | 
|     } | 
|   | 
|     #endregion | 
|   | 
|     #region UI操作方法 | 
|   | 
|     // 设置UI层级 | 
|     public void SetSortingOrder(int order) | 
|     { | 
|         canvas.sortingOrder = order; | 
|     } | 
|   | 
|     protected virtual void OnPreOpen() | 
|     { | 
|         // 子类可以重写此方法进行额外的预打开操作 | 
|     } | 
|   | 
|     protected virtual void OnPreClose() | 
|     { | 
|         // 子类可以重写此方法进行额外的预关闭操作 | 
|     } | 
|   | 
|     // 打开UI | 
|     public void HandleOpen() | 
|     { | 
|         OnPreOpen(); | 
|         // 如果正在播放动画,先停止 | 
|         StopCurrentAnimation(); | 
|   | 
|         // 重置关闭标记 | 
|         isClosing = false; | 
|          | 
|         gameObject.SetActive(true); | 
|         isActive = true; | 
|   | 
|         // 根据动画类型播放打开动画 | 
|         PlayOpenAnimation(); | 
|   | 
|         OnOpen(); | 
|   | 
|         ExecuteNextFrame(NextFrameAfterOpen); | 
|     } | 
|   | 
|     protected virtual void NextFrameAfterOpen() | 
|     { | 
|   | 
|     } | 
|   | 
|     // 关闭UI - 修改后的方法 | 
|     public void HandleClose() | 
|     { | 
|         // 如果已经在关闭过程中,直接返回 | 
|         if (isClosing) return; | 
|          | 
|         OnPreClose(); | 
|          | 
|         // 如果正在播放动画,先停止 | 
|         StopCurrentAnimation(); | 
|   | 
|         // 设置关闭标记 | 
|         isClosing = true; | 
|         isActive = false; | 
|          | 
|         // 禁用交互但保持可见 | 
|         if (canvasGroup != null) | 
|         { | 
|             canvasGroup.blocksRaycasts = false; | 
|         } | 
|   | 
|         // 根据动画类型播放关闭动画 | 
|         PlayCloseAnimation(); | 
|   | 
|         // 调用关闭回调 | 
|         OnClose(); | 
|          | 
|         // 如果没有关闭动画,直接禁用游戏对象 | 
|         if (closeAnimationType == UIAnimationType.None) | 
|         { | 
|             CompleteClose(); | 
|         } | 
|         // 否则在动画完成后禁用游戏对象(在PlayCloseAnimation中处理) | 
|     } | 
|   | 
|     // 完成关闭过程 - 新增方法 | 
|     protected virtual void CompleteClose() | 
|     { | 
|         gameObject.SetActive(false); | 
|         isClosing = false; | 
|     } | 
|   | 
|     public virtual void CloseWindow() | 
|     { | 
|         UIManager.Instance.CloseWindow(this, false); | 
|     } | 
|   | 
|     // 刷新UI | 
|     public virtual void Refresh() | 
|     { | 
|         // 子类可以重写此方法实现UI刷新逻辑 | 
|     } | 
|   | 
|     // 销毁UI | 
|     public virtual void Destroy() | 
|     { | 
|         // 停止所有动画 | 
|         StopCurrentAnimation(); | 
|   | 
|         ClearChildrenUI(); | 
|   | 
|         if (parentUI != null) | 
|         { | 
|             parentUI.RemoveChildUI(this); | 
|         } | 
|   | 
|         Destroy(gameObject); | 
|     } | 
|   | 
|     #endregion | 
|      | 
|     #region 特效相关 | 
|      | 
|     /// <summary> | 
|     /// 播放UI特效 | 
|     /// </summary> | 
|     /// <param name="effectName">特效资源名称</param> | 
|     /// <param name="parent">特效父节点,默认为当前UI</param> | 
|     /// <param name="autoDestroy">是否自动销毁,默认为true</param> | 
|     /// <param name="destroyDelay">自动销毁延迟时间,默认为5秒</param> | 
|     /// <returns>特效游戏对象</returns> | 
|     public GameObject PlayUIEffect(int id, Transform parent = null, bool autoDestroy = true, float destroyDelay = 5f) | 
|     { | 
|         // 使用默认值 | 
|         if (parent == null) parent = transform; | 
|          | 
|         EffectConfig effectCfg = EffectConfig.Get(id); | 
|   | 
|         if (null == effectCfg) | 
|         { | 
|             return null; | 
|         } | 
|   | 
|         // 加载特效资源 | 
|         var effectPrefab = ResManager.Instance.LoadAsset<GameObject>("UIEffect/" + effectCfg.packageName, effectCfg.fxName); | 
|         if (effectPrefab == null) | 
|         { | 
|             Debug.LogError($"加载UI特效失败: {effectCfg.packageName}"); | 
|             return null; | 
|         } | 
|          | 
|         // 实例化特效 | 
|         GameObject effectObj = Instantiate(effectPrefab, parent); | 
|         effectObj.name = $"Effect_{effectCfg.packageName}"; | 
|          | 
|         // 添加特效穿透阻挡器 | 
|         EffectPenetrationBlocker blocker = effectObj.AddComponent<EffectPenetrationBlocker>(); | 
|         blocker.parentCanvas = canvas; | 
|   | 
|         //  延迟一帧才生效 | 
|         this.DelayFrame(blocker.UpdateSortingOrder); | 
|   | 
|         // blocker.UpdateSortingOrder(); | 
|          | 
|         // 自动销毁 | 
|         if (autoDestroy) | 
|         { | 
|             Destroy(effectObj, destroyDelay); | 
|         } | 
|          | 
|         return effectObj; | 
|     } | 
|      | 
|     /// <summary> | 
|     /// 在两个UI元素之间播放特效(按照sortingOrder的中间值) | 
|     /// </summary> | 
|     /// <param name="effectName">特效资源名称</param> | 
|     /// <param name="frontElement">前景UI元素(Image或RawImage)</param> | 
|     /// <param name="backElement">背景UI元素(Image或RawImage)</param> | 
|     /// <param name="autoDestroy">是否自动销毁,默认为true</param> | 
|     /// <param name="destroyDelay">自动销毁延迟时间,默认为5秒</param> | 
|     /// <returns>特效游戏对象</returns> | 
|     public async UniTask<GameObject> PlayEffectBetweenUIElements(string effectName, Graphic frontElement, Graphic backElement, bool autoDestroy = true, float destroyDelay = 5f) | 
|     { | 
|         if (frontElement == null || backElement == null) | 
|         { | 
|             Debug.LogError("前景或背景UI元素为空"); | 
|             return null; | 
|         } | 
|          | 
|         // 确保UI元素在当前UIBase的Canvas下 | 
|         if (frontElement.canvas != canvas || backElement.canvas != canvas) | 
|         { | 
|             Debug.LogError("UI元素不在当前UIBase的Canvas下"); | 
|             return null; | 
|         } | 
|          | 
|         // 加载特效资源 | 
|         GameObject effectPrefab = ResManager.Instance.LoadAsset<GameObject>("UIEffect", effectName); | 
|         if (effectPrefab == null) | 
|         { | 
|             Debug.LogError($"加载UI特效失败: {effectName}"); | 
|             return null; | 
|         } | 
|          | 
|         // 创建一个新的GameObject作为特效容器 | 
|         GameObject container = new GameObject($"EffectContainer_{effectName}"); | 
|         container.transform.SetParent(transform, false); | 
|          | 
|         // 设置容器位置 | 
|         RectTransform containerRect = container.AddComponent<RectTransform>(); | 
|         containerRect.anchorMin = new Vector2(0.5f, 0.5f); | 
|         containerRect.anchorMax = new Vector2(0.5f, 0.5f); | 
|         containerRect.pivot = new Vector2(0.5f, 0.5f); | 
|         containerRect.anchoredPosition = Vector2.zero; | 
|         containerRect.sizeDelta = new Vector2(100, 100); // 默认大小,可以根据需要调整 | 
|          | 
|         // 获取前景和背景元素的siblingIndex | 
|         int frontIndex = frontElement.transform.GetSiblingIndex(); | 
|         int backIndex = backElement.transform.GetSiblingIndex(); | 
|          | 
|         // 设置特效容器的siblingIndex在两者之间 | 
|         if (frontIndex > backIndex) | 
|         { | 
|             // 前景在背景之后,特效应该在中间 | 
|             container.transform.SetSiblingIndex((frontIndex + backIndex) / 2 + 1); | 
|         } | 
|         else | 
|         { | 
|             // 背景在前景之后,特效应该在中间 | 
|             container.transform.SetSiblingIndex((frontIndex + backIndex) / 2); | 
|         } | 
|          | 
|         // 实例化特效 | 
|         GameObject effectObj = Instantiate(effectPrefab, container.transform); | 
|         effectObj.name = $"Effect_{effectName}"; | 
|          | 
|         // 添加特效穿透阻挡器 | 
|         EffectPenetrationBlocker blocker = effectObj.AddComponent<EffectPenetrationBlocker>(); | 
|          | 
|         // 直接设置特效渲染器的排序顺序 | 
|         Renderer[] renderers = effectObj.GetComponentsInChildren<Renderer>(true); | 
|         foreach (Renderer renderer in renderers) | 
|         { | 
|             renderer.sortingOrder = canvas.sortingOrder; | 
|             renderer.sortingLayerName = canvas.sortingLayerName; | 
|         } | 
|          | 
|         // 设置粒子系统渲染器的排序顺序 | 
|         ParticleSystem[] particleSystems = effectObj.GetComponentsInChildren<ParticleSystem>(true); | 
|         foreach (ParticleSystem ps in particleSystems) | 
|         { | 
|             ParticleSystemRenderer psRenderer = ps.GetComponent<ParticleSystemRenderer>(); | 
|             if (psRenderer != null) | 
|             { | 
|                 psRenderer.sortingOrder = canvas.sortingOrder; | 
|                 psRenderer.sortingLayerName = canvas.sortingLayerName; | 
|             } | 
|         } | 
|          | 
|         // 自动销毁 | 
|         if (autoDestroy) | 
|         { | 
|             Destroy(container, destroyDelay); | 
|         } | 
|          | 
|         return effectObj; | 
|     } | 
|     #endregion | 
|   | 
|     public bool raycastTarget | 
|     { | 
|         get; | 
|         set; | 
|     } = true; | 
|   | 
|     public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) | 
|     { | 
|         return raycastTarget; | 
|     } | 
|   | 
|     #region 动画方法 | 
|   | 
|     // 停止当前正在播放的动画 | 
|     protected void StopCurrentAnimation() | 
|     { | 
|         if (currentAnimation != null && currentAnimation.IsActive() && !currentAnimation.IsComplete()) | 
|         { | 
|             currentAnimation.Kill(false); // 不要完成动画,直接停止 | 
|             currentAnimation = null; | 
|         } | 
|         isAnimating = false; | 
|     } | 
|   | 
|     // 播放打开动画 | 
|     protected virtual void PlayOpenAnimation() | 
|     { | 
|   | 
|   | 
|         if (openAnimationType == UIAnimationType.None) | 
|         { | 
|             // 无动画,直接启用交互 | 
|             //  初始值设定好 | 
|             _ResetToBegin(); | 
|             return; | 
|         } | 
|   | 
|         isAnimating = true; | 
|   | 
|         // 初始化动画前的状态 | 
|         switch (openAnimationType) | 
|         { | 
|             case UIAnimationType.FadeInOut: | 
|                 if (canvasGroup != null) | 
|                 { | 
|                     canvasGroup.alpha = 0f; | 
|                     canvasGroup.blocksRaycasts = false; | 
|                 } | 
|                 if (canvasScaler != null) | 
|                 { | 
|                     canvasScaler.scaleFactor = 1f; | 
|                 } | 
|                 break; | 
|   | 
|             case UIAnimationType.ScaleInOut: | 
|                 if (canvasGroup != null) | 
|                 { | 
|                     canvasGroup.alpha = 1f; | 
|                     canvasGroup.blocksRaycasts = false; | 
|                 } | 
|                 if (canvasScaler != null) | 
|                 { | 
|                     canvasScaler.scaleFactor = 0.3f; | 
|                 } | 
|                 break; | 
|   | 
|             case UIAnimationType.SlideFromTop: | 
|                 _ResetToBegin(); | 
|                 if (_rectTransform != null) | 
|                 { | 
|                     Vector2 startPos = originalPosition; | 
|                     startPos.y = originalPosition.y + Screen.height; | 
|                     _rectTransform.anchoredPosition = startPos; | 
|                 } | 
|                 break; | 
|   | 
|             case UIAnimationType.SlideFromBottom: | 
|                 _ResetToBegin(); | 
|                 if (_rectTransform != null) | 
|                 { | 
|                     Vector2 startPos = originalPosition; | 
|                     startPos.y = originalPosition.y-Screen.height; | 
|                     _rectTransform.anchoredPosition = startPos; | 
|                 } | 
|                 break; | 
|   | 
|             case UIAnimationType.SlideFromLeft: | 
|                 _ResetToBegin(); | 
|                 if (_rectTransform != null) | 
|                 { | 
|                     Vector2 startPos = originalPosition; | 
|                     startPos.x = originalPosition.x-Screen.width; | 
|                     _rectTransform.anchoredPosition = startPos; | 
|                 } | 
|                 break; | 
|   | 
|             case UIAnimationType.SlideFromRight: | 
|                 _ResetToBegin(); | 
|                 if (_rectTransform != null) | 
|                 { | 
|                     Vector2 startPos = originalPosition; | 
|                     startPos.x = originalPosition.x+Screen.width; | 
|                     _rectTransform.anchoredPosition = startPos; | 
|                 } | 
|                 break; | 
|             case UIAnimationType.ScaleOverInOut: | 
|                 if (canvasGroup != null) | 
|                 { | 
|                     canvasGroup.alpha = 1f; | 
|                     canvasGroup.blocksRaycasts = false; | 
|                 } | 
|                 if (canvasScaler != null) | 
|                 { | 
|                     canvasScaler.scaleFactor = 0.3f; | 
|                 } | 
|                 break; | 
|         } | 
|   | 
|         try | 
|         { | 
|             // 创建动画序列 | 
|             currentAnimation = DOTween.Sequence(); | 
|   | 
|             // 添加动画 | 
|             switch (openAnimationType) | 
|             { | 
|                 case UIAnimationType.FadeInOut: | 
|                     if (canvasGroup != null) | 
|                     { | 
|                         currentAnimation.Append(canvasGroup.DOFade(1f, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.ScaleInOut: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         currentAnimation.Append(DOVirtual.Float(0.3f, 1f, animeDuration, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.SlideFromTop: | 
|                 case UIAnimationType.SlideFromBottom: | 
|                 case UIAnimationType.SlideFromLeft: | 
|                 case UIAnimationType.SlideFromRight: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         currentAnimation.Append(_rectTransform.DOAnchorPos(originalPosition, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|                 case UIAnimationType.ScaleOverInOut: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         if (null == scaleOverInOutCurve) | 
|                         { | 
|                             currentAnimation.Append(DOVirtual.Float(0.3f, 1.2f, animeDuration,  | 
|                             (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); | 
|                         } | 
|                         else | 
|                         { | 
|                             currentAnimation.Append(DOVirtual.Float(0.3f, 1.2f, animeDuration,  | 
|                                 (value) => {canvasScaler.scaleFactor = value;}).SetEase(scaleOverInOutCurve.curve)); | 
|                         } | 
|                          | 
|   | 
|                         // currentAnimation.Append(DOVirtual.Float(1.2f, 1f, 0.1f, (value) => {canvasScaler.scaleFactor = value;}).SetEase(scaleOverInOutCurve)); | 
|                     } | 
|                     break; | 
|             } | 
|   | 
|             // 动画完成后的回调 | 
|             currentAnimation.OnComplete(() => | 
|             { | 
|                 isAnimating = false; | 
|                 _ResetToBegin(); | 
|                 OnOpenAnimationComplete(); | 
|   | 
|                 // 启用交互 | 
|                 if (canvasGroup != null) | 
|                 { | 
|                     canvasGroup.blocksRaycasts = true; | 
|                 } | 
|   | 
|             }); | 
|   | 
|             // currentAnimation.ingoreTimeScale = true; | 
|   | 
|             currentAnimation.Play(); | 
|         } | 
|         catch (System.Exception e) | 
|         { | 
|             Debug.LogError($"播放打开动画时出错: {e.Message}"); | 
|   | 
|             // 出错时确保UI可见并可交互 | 
|             if (canvasGroup != null) | 
|             { | 
|                 canvasGroup.alpha = 1f; | 
|                 canvasGroup.blocksRaycasts = true; | 
|             } | 
|             isAnimating = false; | 
|         } | 
|     } | 
|   | 
|     // private void Update() | 
|     // { | 
|     //     Debug.LogError(_rectTransform.parent.name + ":" + _rectTransform.anchoredPosition); | 
|     // } | 
|   | 
|     private void _ResetToBegin() | 
|     { | 
|         if (canvasGroup != null) | 
|         { | 
|             canvasGroup.alpha = 1f; | 
|             canvasGroup.blocksRaycasts = true; | 
|         } | 
|         if (canvasScaler != null) | 
|         { | 
|             canvasScaler.scaleFactor = 1f; | 
|         } | 
|   | 
|         if (_rectTransform != null) | 
|         { | 
|             _rectTransform.anchoredPosition = originalPosition; | 
|         } | 
|     } | 
|   | 
|     protected virtual void OnOpenAnimationComplete() | 
|     { | 
|   | 
|     } | 
|   | 
|     // 播放关闭动画 - 修改后的方法 | 
|     protected virtual void PlayCloseAnimation() | 
|     { | 
|         if (closeAnimationType == UIAnimationType.None) | 
|         { | 
|             // 无动画,直接返回,让HandleClose方法处理 | 
|             return; | 
|         } | 
|   | 
|         isAnimating = true; | 
|   | 
|         try | 
|         { | 
|             // 创建动画序列 | 
|             currentAnimation = DOTween.Sequence(); | 
|   | 
|             // 添加动画 | 
|             switch (closeAnimationType) | 
|             { | 
|                 case UIAnimationType.FadeInOut: | 
|                     if (canvasGroup != null) | 
|                     { | 
|                         currentAnimation.Append(canvasGroup.DOFade(0.1f, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.ScaleInOut: | 
|                 case UIAnimationType.ScaleOverInOut: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         currentAnimation.Append(DOVirtual.Float(1f, 0.3f, animeDuration, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.SlideFromTop: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         Vector2 endPos = originalPosition; | 
|                         endPos.y = Screen.height; | 
|                         currentAnimation.Append(_rectTransform.DOAnchorPos(endPos, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.SlideFromBottom: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         Vector2 endPos = originalPosition; | 
|                         endPos.y = -Screen.height; | 
|                         currentAnimation.Append(_rectTransform.DOAnchorPos(endPos, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.SlideFromLeft: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         Vector2 endPos = originalPosition; | 
|                         endPos.x = -Screen.width; | 
|                         currentAnimation.Append(_rectTransform.DOAnchorPos(endPos, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|   | 
|                 case UIAnimationType.SlideFromRight: | 
|                     if (_rectTransform != null) | 
|                     { | 
|                         Vector2 endPos = originalPosition; | 
|                         endPos.x = Screen.width; | 
|                         currentAnimation.Append(_rectTransform.DOAnchorPos(endPos, animeDuration).SetEase(animationEase)); | 
|                     } | 
|                     break; | 
|             } | 
|   | 
|             // 动画完成后的回调 - 修改为调用CompleteClose | 
|             currentAnimation.OnComplete(() => | 
|             { | 
|                 isAnimating = false; | 
|                  | 
|                 // 动画完成后,完成关闭过程 | 
|                 if (isClosing) | 
|                 { | 
|                     CompleteClose(); | 
|                 } | 
|             }); | 
|   | 
|             currentAnimation.Play(); | 
|         } | 
|         catch (System.Exception e) | 
|         { | 
|             Debug.LogError($"播放关闭动画时出错: {e.Message}"); | 
|              | 
|             // 出错时直接完成关闭 | 
|             isAnimating = false; | 
|             CompleteClose(); | 
|         } | 
|     } | 
|   | 
|     #endregion | 
|   | 
|     #region 子UI管理 | 
|   | 
|     // 添加子UI | 
|     public void AddChildUI(UIBase childUI) | 
|     { | 
|         if (childUI != null && !childrenUI.Contains(childUI)) | 
|         { | 
|             childrenUI.Add(childUI); | 
|             childUI.parentUI = this; | 
|         } | 
|     } | 
|   | 
|     // 移除子UI | 
|     public void RemoveChildUI(UIBase childUI) | 
|     { | 
|         if (childUI != null && childrenUI.Contains(childUI)) | 
|         { | 
|             childrenUI.Remove(childUI); | 
|             childUI.parentUI = null; | 
|         } | 
|     } | 
|   | 
|     // 清空所有子UI | 
|     public void ClearChildrenUI() | 
|     { | 
|         for (int i = childrenUI.Count - 1; i >= 0; i--) | 
|         { | 
|             if (childrenUI[i] != null) | 
|             { | 
|                 childrenUI[i].parentUI = null; | 
|             } | 
|         } | 
|   | 
|         childrenUI.Clear(); | 
|     } | 
|   | 
|     public bool IsActive() | 
|     { | 
|         return isActive; | 
|     } | 
|   | 
|     #endregion | 
|   | 
|     #region 回调方法 | 
|   | 
|     // UI打开时的回调 | 
|     protected virtual void OnOpen() | 
|     { | 
|         // 子类可以重写此方法实现打开UI时的逻辑 | 
|     } | 
|   | 
|     // UI关闭时的回调 | 
|     protected virtual void OnClose() | 
|     { | 
|         // 子类可以重写此方法实现关闭UI时的逻辑 | 
|     } | 
|   | 
|     #endregion | 
| } |