using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using UnityEngine.UI; // DOTween 插件引用 using Cysharp.Threading.Tasks; public enum UILayer { Static, // 静态UI 适合做 常驻用的如 主界面 Bottom, // 最底层 适合做 主界面上面的 一级窗口 一级界面 Mid, // 适合做 二级窗口 二级界面 Top, // 适合做提示弹窗 System // 网络弹窗/其他重要弹窗/系统公告 } public enum UIAnimationType { None, // 无动画 FadeInOut, // 淡入淡出 ScaleInOut, // 缩放 SlideFromTop, // 从顶部滑入 SlideFromBottom, // 从底部滑入 SlideFromLeft, // 从左侧滑入 SlideFromRight // 从右侧滑入 } [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 isPersistent = false; [SerializeField][HideInInspector] public int maxIdleRounds = 20; // 动画相关 [SerializeField] public UIAnimationType openAnimationType = UIAnimationType.None; [SerializeField] public UIAnimationType closeAnimationType = UIAnimationType.None; [SerializeField][HideInInspector] public float animationDuration = 0.3f; [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(); // 内部状态 protected bool isActive = false; protected bool isAnimating = false; protected bool isClosing = false; // 新增:标记是否正在关闭 // 组件引用 protected Canvas canvas; protected CanvasGroup canvasGroup; protected RectTransform rectTransform; // 动画相关 protected Vector3 originalScale; protected Vector3 originalPosition; protected Sequence currentAnimation; #endregion #region Unity生命周期 protected virtual void Awake() { // 确保 DOTween 已初始化 DOTween.SetTweensCapacity(500, 50); // 在Awake中进行基本初始化 InitComponent(); // 保存原始值用于动画 if (rectTransform != null) { originalScale = rectTransform.localScale; originalPosition = rectTransform.anchoredPosition; } } protected virtual void Start() { // 子类可以重写此方法进行额外初始化 } protected virtual void OnDestroy() { // 确保动画被正确清理 if (currentAnimation != null) { currentAnimation.Kill(); currentAnimation = null; } } #endregion #region 初始化方法 // 获取必要的组件 protected virtual void InitComponent() { // 获取或添加Canvas组件 canvas = GetComponent(); if (canvas == null) { canvas = gameObject.AddComponent(); } // 设置Canvas属性 canvas.overrideSorting = true; // 获取或添加CanvasGroup组件 canvasGroup = GetComponent(); if (canvasGroup == null) { canvasGroup = gameObject.AddComponent(); } // 添加GraphicRaycaster组件(如果没有) if (GetComponent() == null) { gameObject.AddComponent(); } // 获取RectTransform组件 rectTransform = GetComponent(); } #endregion #region UI操作方法 // 设置UI层级 public void SetSortingOrder(int order) { if (canvas != null) { canvas.sortingOrder = order; } } protected virtual void OnPreOpen() { // 子类可以重写此方法进行额外的预打开操作 } protected virtual void OnPreClose() { // 子类可以重写此方法进行额外的预关闭操作 } // 打开UI public virtual void HandleOpen() { OnPreOpen(); // 如果正在播放动画,先停止 StopCurrentAnimation(); // 重置关闭标记 isClosing = false; gameObject.SetActive(true); isActive = true; // 根据动画类型播放打开动画 PlayOpenAnimation(); OnOpen(); } // 关闭UI - 修改后的方法 public virtual void HandleClose() { // 如果已经在关闭过程中,直接返回 if (isClosing) return; OnPreClose(); // 如果正在播放动画,先停止 StopCurrentAnimation(); // 设置关闭标记 isClosing = true; isActive = false; // 禁用交互但保持可见 if (canvasGroup != null) { canvasGroup.interactable = false; 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); } // 刷新UI public virtual void Refresh() { // 子类可以重写此方法实现UI刷新逻辑 } // 销毁UI public virtual void Destroy() { // 停止所有动画 StopCurrentAnimation(); ClearChildrenUI(); if (parentUI != null) { parentUI.RemoveChildUI(this); } Destroy(gameObject); } #endregion #region 特效相关 /// /// 播放UI特效 /// /// 特效资源名称 /// 特效父节点,默认为当前UI /// 是否自动销毁,默认为true /// 自动销毁延迟时间,默认为5秒 /// 特效游戏对象 public async UniTask PlayUIEffect(string effectName, Transform parent = null, bool autoDestroy = true, float destroyDelay = 5f) { // 使用默认值 if (parent == null) parent = transform; // 加载特效资源 GameObject effectPrefab = await ResManager.Instance.LoadAsset("UIEffect", effectName); if (effectPrefab == null) { Debug.LogError($"加载UI特效失败: {effectName}"); return null; } // 实例化特效 GameObject effectObj = Instantiate(effectPrefab, parent); effectObj.name = $"Effect_{effectName}"; // 添加特效穿透阻挡器 EffectPenetrationBlocker blocker = effectObj.AddComponent(); blocker.parentCanvas = canvas; blocker.UpdateSortingOrder(); // 自动销毁 if (autoDestroy) { Destroy(effectObj, destroyDelay); } return effectObj; } /// /// 在两个UI元素之间播放特效(按照sortingOrder的中间值) /// /// 特效资源名称 /// 前景UI元素(Image或RawImage) /// 背景UI元素(Image或RawImage) /// 是否自动销毁,默认为true /// 自动销毁延迟时间,默认为5秒 /// 特效游戏对象 public async UniTask 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 = await ResManager.Instance.LoadAsset("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(); 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(); // 直接设置特效渲染器的排序顺序 Renderer[] renderers = effectObj.GetComponentsInChildren(true); foreach (Renderer renderer in renderers) { renderer.sortingOrder = canvas.sortingOrder; renderer.sortingLayerName = canvas.sortingLayerName; } // 设置粒子系统渲染器的排序顺序 ParticleSystem[] particleSystems = effectObj.GetComponentsInChildren(true); foreach (ParticleSystem ps in particleSystems) { ParticleSystemRenderer psRenderer = ps.GetComponent(); if (psRenderer != null) { psRenderer.sortingOrder = canvas.sortingOrder; psRenderer.sortingLayerName = canvas.sortingLayerName; } } // 自动销毁 if (autoDestroy) { Destroy(container, destroyDelay); } return effectObj; } #endregion #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) { // 无动画,直接启用交互 if (canvasGroup != null) { canvasGroup.alpha = 1f; canvasGroup.interactable = true; canvasGroup.blocksRaycasts = true; } return; } isAnimating = true; // 初始化动画前的状态 switch (openAnimationType) { case UIAnimationType.FadeInOut: if (canvasGroup != null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } break; case UIAnimationType.ScaleInOut: if (rectTransform != null) { rectTransform.localScale = Vector3.zero; } if (canvasGroup != null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } break; case UIAnimationType.SlideFromTop: if (rectTransform != null) { Vector2 startPos = originalPosition; startPos.y = Screen.height; rectTransform.anchoredPosition = startPos; } if (canvasGroup != null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } break; case UIAnimationType.SlideFromBottom: if (rectTransform != null) { Vector2 startPos = originalPosition; startPos.y = -Screen.height; rectTransform.anchoredPosition = startPos; } if (canvasGroup != null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } break; case UIAnimationType.SlideFromLeft: if (rectTransform != null) { Vector2 startPos = originalPosition; startPos.x = -Screen.width; rectTransform.anchoredPosition = startPos; } if (canvasGroup != null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } break; case UIAnimationType.SlideFromRight: if (rectTransform != null) { Vector2 startPos = originalPosition; startPos.x = Screen.width; rectTransform.anchoredPosition = startPos; } if (canvasGroup != null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } break; } try { // 创建动画序列 currentAnimation = DOTween.Sequence(); // 添加动画 switch (openAnimationType) { case UIAnimationType.FadeInOut: if (canvasGroup != null) { currentAnimation.Append(canvasGroup.DOFade(1f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.ScaleInOut: if (rectTransform != null) { currentAnimation.Append(rectTransform.DOScale(originalScale, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(1f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.SlideFromTop: case UIAnimationType.SlideFromBottom: case UIAnimationType.SlideFromLeft: case UIAnimationType.SlideFromRight: if (rectTransform != null) { currentAnimation.Append(rectTransform.DOAnchorPos(originalPosition, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(1f, animationDuration).SetEase(animationEase)); } break; } // 动画完成后的回调 currentAnimation.OnComplete(() => { isAnimating = false; // 启用交互 if (canvasGroup != null) { canvasGroup.interactable = true; canvasGroup.blocksRaycasts = true; } }); } catch (System.Exception e) { Debug.LogError($"播放打开动画时出错: {e.Message}"); // 出错时确保UI可见并可交互 if (canvasGroup != null) { canvasGroup.alpha = 1f; canvasGroup.interactable = true; canvasGroup.blocksRaycasts = true; } isAnimating = false; } } // 播放关闭动画 - 修改后的方法 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(0f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.ScaleInOut: if (rectTransform != null) { currentAnimation.Append(rectTransform.DOScale(Vector3.zero, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(0f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.SlideFromTop: if (rectTransform != null) { Vector2 endPos = originalPosition; endPos.y = Screen.height; currentAnimation.Append(rectTransform.DOAnchorPos(endPos, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(0f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.SlideFromBottom: if (rectTransform != null) { Vector2 endPos = originalPosition; endPos.y = -Screen.height; currentAnimation.Append(rectTransform.DOAnchorPos(endPos, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(0f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.SlideFromLeft: if (rectTransform != null) { Vector2 endPos = originalPosition; endPos.x = -Screen.width; currentAnimation.Append(rectTransform.DOAnchorPos(endPos, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(0f, animationDuration).SetEase(animationEase)); } break; case UIAnimationType.SlideFromRight: if (rectTransform != null) { Vector2 endPos = originalPosition; endPos.x = Screen.width; currentAnimation.Append(rectTransform.DOAnchorPos(endPos, animationDuration).SetEase(animationEase)); } if (canvasGroup != null) { currentAnimation.Join(canvasGroup.DOFade(0f, animationDuration).SetEase(animationEase)); } break; } // 动画完成后的回调 - 修改为调用CompleteClose currentAnimation.OnComplete(() => { isAnimating = false; // 动画完成后,完成关闭过程 if (isClosing) { CompleteClose(); } }); } 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(); } #endregion #region 回调方法 // UI打开时的回调 protected virtual void OnOpen() { // 子类可以重写此方法实现打开UI时的逻辑 } // UI关闭时的回调 protected virtual void OnClose() { // 子类可以重写此方法实现关闭UI时的逻辑 } #endregion }