| | |
| | | SlideFromTop, // 从顶部滑入 |
| | | SlideFromBottom, // 从底部滑入 |
| | | SlideFromLeft, // 从左侧滑入 |
| | | SlideFromRight // 从右侧滑入 |
| | | SlideFromRight, // 从右侧滑入 |
| | | ScaleOverInOut,// 缩放(超过)之后再返回 |
| | | |
| | | } |
| | | |
| | | [RequireComponent(typeof(Canvas))] |
| | |
| | | // 动画相关 |
| | | [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>(); |
| | |
| | | |
| | | private Button btnClickEmptyClose; |
| | | |
| | | protected int functionOrder = 0; |
| | | // 跟OneLevelWin联动 实际上是需要继承自OneLevelWin才能生效的值 使用需要注意 |
| | | int m_FunctionOrder = 0; |
| | | public int functionOrder |
| | | { |
| | | get { return m_FunctionOrder; } |
| | | set { m_FunctionOrder = value; } |
| | | } |
| | | |
| | | // 内部状态 |
| | | protected bool isActive = false; |
| | |
| | | // 组件引用 |
| | | protected Canvas canvas; |
| | | protected CanvasGroup canvasGroup; |
| | | protected RectTransform _rectTransform; |
| | | |
| | | // 动画相关 |
| | | protected Vector3 originalPosition; |
| | |
| | | |
| | | protected virtual void Awake() |
| | | { |
| | | CreateRootNode(); |
| | | // 防止有人不写base.InitComponent引发错误 所以拆分 |
| | | InitComponentInternal(); |
| | | // 在Awake中进行基本初始化 |
| | |
| | | } |
| | | } |
| | | |
| | | private void CreateRootNode() |
| | | { |
| | | List<Transform> children = new List<Transform>(); |
| | | foreach (Transform child in transform) |
| | | { |
| | | children.Add(child); |
| | | } |
| | | |
| | | rootNode = new GameObject("WindowRoot"); |
| | | rootNode.transform.SetParent(transform, false); |
| | | rootNode.layer = LayerMask.NameToLayer("UI"); |
| | | _rectTransform = rootNode.AddMissingComponent<RectTransform>(); |
| | | _rectTransform.anchorMin = new Vector2(0.5f, 0.5f); |
| | | _rectTransform.anchorMax = new Vector2(0.5f, 0.5f); |
| | | _rectTransform.pivot = new Vector2(0.5f, 0.5f); |
| | | _rectTransform.sizeDelta = new Vector2(750, 1334); // 默认大小,可以根据需要调整 |
| | | _rectTransform.anchoredPosition = Vector2.zero; |
| | | _rectTransform.localScale = Vector3.one; |
| | | |
| | | foreach (Transform child in children) |
| | | { |
| | | child.SetParent(rootNode.transform, false); |
| | | } |
| | | } |
| | | |
| | | protected virtual void Start() |
| | | { |
| | | // 子类可以重写此方法进行额外初始化 |
| | |
| | | |
| | | protected async UniTask ApplySettings() |
| | | { |
| | | await UniTask.DelayFrame(5); |
| | | |
| | | if (null != transform) |
| | | if (clickEmptySpaceClose) |
| | | { |
| | | if (clickEmptySpaceClose) |
| | | { |
| | | GameObject goBtnESC = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/ClickEmptyCloseMask"), transform); |
| | | // Load |
| | | btnClickEmptyClose = goBtnESC.GetComponent<Button>(); |
| | | btnClickEmptyClose.AddListener(CloseWindow); |
| | | btnClickEmptyClose.transform.SetAsFirstSibling(); |
| | | } |
| | | //延迟创建会导致层级在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) |
| | | { |
| | |
| | | } |
| | | |
| | | canvasScaler = GetComponent<CanvasScaler>(); |
| | | canvasScaler.referenceResolution = Constants.DESIGN_RESOLUTION; |
| | | canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; |
| | | canvasScaler.matchWidthOrHeight = 0; |
| | | } |
| | | |
| | | // 获取必要的组件 |
| | |
| | | // 打开UI |
| | | public void HandleOpen() |
| | | { |
| | | if (_rectTransform == null) |
| | | { |
| | | Debug.LogError($"界面: {uiName} 需要设置根节点_rectTransform "); |
| | | return; |
| | | } |
| | | |
| | | OnPreOpen(); |
| | | // 如果正在播放动画,先停止 |
| | | StopCurrentAnimation(); |
| | |
| | | PlayOpenAnimation(); |
| | | |
| | | OnOpen(); |
| | | |
| | | ExecuteNextFrame(NextFrameAfterOpen); |
| | | } |
| | | |
| | | protected virtual void NextFrameAfterOpen() |
| | | { |
| | | |
| | | } |
| | | |
| | | // 关闭UI - 修改后的方法 |
| | |
| | | // 禁用交互但保持可见 |
| | | if (canvasGroup != null) |
| | | { |
| | | canvasGroup.interactable = false; |
| | | canvasGroup.blocksRaycasts = false; |
| | | } |
| | | |
| | |
| | | /// <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) |
| | | public EffectPlayer PlayUIEffect(int id, Transform parent = null, bool autoDestroy = true, float destroyDelay = 5f) |
| | | { |
| | | // 使用默认值 |
| | | if (parent == null) parent = transform; |
| | | |
| | | EffectConfig effectCfg = EffectConfig.Get(id); |
| | | EffectPlayer player = parent.gameObject.AddComponent<EffectPlayer>(); |
| | | |
| | | if (null == effectCfg) |
| | | { |
| | | return null; |
| | | } |
| | | player.effectId = id; |
| | | player.autoDestroy = autoDestroy; |
| | | player.destroyDelay = destroyDelay; |
| | | player.canvas = canvas; |
| | | |
| | | // 加载特效资源 |
| | | 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; |
| | | return player; |
| | | } |
| | | |
| | | /// <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 |
| | |
| | | if (canvasGroup != null) |
| | | { |
| | | canvasGroup.alpha = 0f; |
| | | canvasGroup.interactable = false; |
| | | canvasGroup.blocksRaycasts = false; |
| | | } |
| | | if (canvasScaler != null) |
| | |
| | | if (canvasGroup != null) |
| | | { |
| | | canvasGroup.alpha = 1f; |
| | | canvasGroup.interactable = false; |
| | | canvasGroup.blocksRaycasts = false; |
| | | } |
| | | if (canvasScaler != null) |
| | |
| | | _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.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.interactable = true; |
| | | canvasGroup.blocksRaycasts = true; |
| | | } |
| | | |
| | | }); |
| | | |
| | | // currentAnimation.ingoreTimeScale = true; |
| | | |
| | | currentAnimation.Play(); |
| | | } |
| | |
| | | if (canvasGroup != null) |
| | | { |
| | | canvasGroup.alpha = 1f; |
| | | canvasGroup.interactable = true; |
| | | canvasGroup.blocksRaycasts = true; |
| | | } |
| | | isAnimating = false; |
| | |
| | | if (canvasGroup != null) |
| | | { |
| | | canvasGroup.alpha = 1f; |
| | | canvasGroup.interactable = false; |
| | | canvasGroup.blocksRaycasts = false; |
| | | canvasGroup.blocksRaycasts = true; |
| | | } |
| | | if (canvasScaler != null) |
| | | { |
| | |
| | | break; |
| | | |
| | | case UIAnimationType.ScaleInOut: |
| | | case UIAnimationType.ScaleOverInOut: |
| | | if (_rectTransform != null) |
| | | { |
| | | currentAnimation.Append(DOVirtual.Float(1f, 0.3f, animeDuration, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); |