| File was renamed from Main/UI/UIBase.cs |
| | |
| | | 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, // 适合做 二级窗口 二级界面
|
| | | 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 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 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<UIBase> childrenUI = new List<UIBase>();
|
| | |
|
| | | // 打开遮罩
|
| | | [SerializeField] public bool openMask = false;
|
| | |
|
| | | // 点击空白区域关闭界面
|
| | | [SerializeField] public bool clickEmptySpaceClose = false;
|
| | |
|
| | | private GameObject screenMask = null;
|
| | |
|
| | | private Button btnClickEmptyClose;
|
| | |
|
| | | protected int functionOrder = 0;
|
| | |
|
| | | // 内部状态
|
| | | 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;
|
| | | }
|
| | |
|
| | | 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()
|
| | | {
|
| | | await UniTask.DelayFrame(5);
|
| | |
|
| | | if (null != transform)
|
| | | {
|
| | | if (clickEmptySpaceClose)
|
| | | {
|
| | | GameObject goBtnESC = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/ClickEmptyCloseMask"), transform);
|
| | | // Load
|
| | | btnClickEmptyClose = goBtnESC.GetComponent<Button>();
|
| | | btnClickEmptyClose.AddListener(CloseWindow);
|
| | | btnClickEmptyClose.transform.SetAsFirstSibling();
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | 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 初始化方法
|
| | |
|
| | | // 获取必要的组件
|
| | | protected virtual void InitComponent()
|
| | | {
|
| | | // 获取或添加Canvas组件
|
| | | canvas = GetComponent<Canvas>();
|
| | | if (canvas == null)
|
| | | {
|
| | | canvas = gameObject.AddComponent<Canvas>();
|
| | | }
|
| | |
|
| | | // 设置Canvas属性
|
| | | canvas.overrideSorting = true;
|
| | |
|
| | | // 获取或添加CanvasGroup组件
|
| | | canvasGroup = GetComponent<CanvasGroup>();
|
| | | if (canvasGroup == null)
|
| | | {
|
| | | canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
| | | }
|
| | |
|
| | | // 添加GraphicRaycaster组件(如果没有)
|
| | | if (GetComponent<UnityEngine.UI.GraphicRaycaster>() == null)
|
| | | {
|
| | | gameObject.AddComponent<UnityEngine.UI.GraphicRaycaster>();
|
| | | }
|
| | |
|
| | | // 获取RectTransform组件
|
| | | rectTransform = GetComponent<RectTransform>();
|
| | | }
|
| | |
|
| | | #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 void HandleOpen()
|
| | | {
|
| | | OnPreOpen();
|
| | | // 如果正在播放动画,先停止
|
| | | StopCurrentAnimation();
|
| | |
|
| | | // 重置关闭标记
|
| | | isClosing = false;
|
| | | |
| | | gameObject.SetActive(true);
|
| | | isActive = true;
|
| | |
|
| | | // 根据动画类型播放打开动画
|
| | | PlayOpenAnimation();
|
| | |
|
| | | OnOpen();
|
| | | }
|
| | |
|
| | | // 关闭UI - 修改后的方法
|
| | | public 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, 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 async UniTask<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;
|
| | | 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)
|
| | | {
|
| | | // 无动画,直接启用交互
|
| | | 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();
|
| | | }
|
| | |
|
| | | public bool IsActive()
|
| | | {
|
| | | return isActive;
|
| | | }
|
| | |
|
| | | #endregion
|
| | |
|
| | | #region 回调方法
|
| | |
|
| | | // UI打开时的回调
|
| | | protected virtual void OnOpen()
|
| | | {
|
| | | // 子类可以重写此方法实现打开UI时的逻辑
|
| | | }
|
| | |
|
| | | // UI关闭时的回调
|
| | | protected virtual void OnClose()
|
| | | {
|
| | | // 子类可以重写此方法实现关闭UI时的逻辑
|
| | | }
|
| | |
|
| | | #endregion
|
| | | 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, // 适合做 二级窗口 二级界面 |
| | | 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 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 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<UIBase> childrenUI = new List<UIBase>(); |
| | | |
| | | // 打开遮罩 |
| | | [SerializeField] public bool openMask = false; |
| | | |
| | | // 点击空白区域关闭界面 |
| | | [SerializeField] public bool clickEmptySpaceClose = false; |
| | | |
| | | private GameObject screenMask = null; |
| | | |
| | | private Button btnClickEmptyClose; |
| | | |
| | | protected int functionOrder = 0; |
| | | |
| | | // 内部状态 |
| | | 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; |
| | | } |
| | | |
| | | 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() |
| | | { |
| | | await UniTask.DelayFrame(5); |
| | | |
| | | if (null != transform) |
| | | { |
| | | if (clickEmptySpaceClose) |
| | | { |
| | | GameObject goBtnESC = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/ClickEmptyCloseMask"), transform); |
| | | // Load |
| | | btnClickEmptyClose = goBtnESC.GetComponent<Button>(); |
| | | btnClickEmptyClose.AddListener(CloseWindow); |
| | | btnClickEmptyClose.transform.SetAsFirstSibling(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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 初始化方法 |
| | | |
| | | // 获取必要的组件 |
| | | protected virtual void InitComponent() |
| | | { |
| | | // 获取或添加Canvas组件 |
| | | canvas = GetComponent<Canvas>(); |
| | | if (canvas == null) |
| | | { |
| | | canvas = gameObject.AddComponent<Canvas>(); |
| | | } |
| | | |
| | | // 设置Canvas属性 |
| | | canvas.overrideSorting = true; |
| | | |
| | | // 获取或添加CanvasGroup组件 |
| | | canvasGroup = GetComponent<CanvasGroup>(); |
| | | if (canvasGroup == null) |
| | | { |
| | | canvasGroup = gameObject.AddComponent<CanvasGroup>(); |
| | | } |
| | | |
| | | // 添加GraphicRaycaster组件(如果没有) |
| | | if (GetComponent<UnityEngine.UI.GraphicRaycaster>() == null) |
| | | { |
| | | gameObject.AddComponent<UnityEngine.UI.GraphicRaycaster>(); |
| | | } |
| | | |
| | | // 获取RectTransform组件 |
| | | rectTransform = GetComponent<RectTransform>(); |
| | | } |
| | | |
| | | #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 void HandleOpen() |
| | | { |
| | | OnPreOpen(); |
| | | // 如果正在播放动画,先停止 |
| | | StopCurrentAnimation(); |
| | | |
| | | // 重置关闭标记 |
| | | isClosing = false; |
| | | |
| | | gameObject.SetActive(true); |
| | | isActive = true; |
| | | |
| | | // 根据动画类型播放打开动画 |
| | | PlayOpenAnimation(); |
| | | |
| | | OnOpen(); |
| | | } |
| | | |
| | | // 关闭UI - 修改后的方法 |
| | | public 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, 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 async UniTask<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; |
| | | 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) |
| | | { |
| | | // 无动画,直接启用交互 |
| | | 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(); |
| | | } |
| | | |
| | | public bool IsActive() |
| | | { |
| | | return isActive; |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 回调方法 |
| | | |
| | | // UI打开时的回调 |
| | | protected virtual void OnOpen() |
| | | { |
| | | // 子类可以重写此方法实现打开UI时的逻辑 |
| | | } |
| | | |
| | | // UI关闭时的回调 |
| | | protected virtual void OnClose() |
| | | { |
| | | // 子类可以重写此方法实现关闭UI时的逻辑 |
| | | } |
| | | |
| | | #endregion |
| | | } |