| | |
| | | public enum UILayer |
| | | { |
| | | Static, // 静态UI 适合做 战斗 主界面 |
| | | Bottom, // 主界面 |
| | | Mid, // 功能窗口 |
| | | System, // 网络弹窗/其他重要弹窗 |
| | | Bottom, // 部分界面特殊处理层级用 |
| | | Mid, // 大部分功能窗口都放这层,便于跳转上下层管理(一个界面可以同时存在多个) |
| | | System, // 网络弹窗,信息提示等,其他重要弹窗 |
| | | Loading, // 加载界面 |
| | | |
| | | } |
| | |
| | | SlideFromBottom, // 从底部滑入 |
| | | SlideFromLeft, // 从左侧滑入 |
| | | SlideFromRight, // 从右侧滑入 |
| | | ScaleOverInOut,// 缩放(超过)之后再返回 |
| | | ScaleOverInOut,// 缩放根据曲线 |
| | | |
| | | } |
| | | |
| | |
| | | [SerializeField] public UILayer uiLayer = UILayer.Mid; |
| | | [SerializeField][HideInInspector] public string uiName; |
| | | [SerializeField] public bool isMainUI = false; |
| | | [SerializeField] public bool supportParentChildRelation = true; // 新增:是否支持父子关系 |
| | | |
| | | // 新增:是否支持父子关系 |
| | | // 父子关系:UI拥有的上下级链式关系 |
| | | // 在非特定情况下 都要拥有父子关系 (一般来说功能都要有父子关系 例外的是例如系统弹窗 |
| | | // 主界面这种不需要 在制作UI伤 目前的需求是 功能全部做在Middle层) |
| | | // 拥有父子关系 在关闭父界面的时候 子界面会连同一起关闭 子界面打开时 父界面的“回合数”会刷新 回合数详见持久化相关的注释 |
| | | // |
| | | [SerializeField] public bool supportParentChildRelation = true; |
| | | |
| | | // 持久化相关 |
| | | [SerializeField] public bool isPersistent = false; |
| | |
| | | // 动画相关 |
| | | [SerializeField] public UIAnimationType openAnimationType = UIAnimationType.None; |
| | | [SerializeField] public UIAnimationType closeAnimationType = UIAnimationType.None; |
| | | [SerializeField] protected RectTransform _rectTransform; //界面默认添加根节点用于表现界面开启关闭动画 |
| | | [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; |
| | | public Action btnClickEmptyCloseEvent = null; //提供点击空白区域关闭界面的回调 |
| | | |
| | | // 跟OneLevelWin联动 实际上是需要继承自OneLevelWin才能生效的值 使用需要注意 |
| | | int m_FunctionOrder = 0; |
| | |
| | | // 保存原始值用于动画 |
| | | if (_rectTransform != null) |
| | | { |
| | | originalPosition = _rectTransform.anchoredPosition; |
| | | originalPosition = _rectTransform.anchoredPosition;; |
| | | |
| | | } |
| | | |
| | | ApplySettings(); |
| | |
| | | //延迟创建会导致层级在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); |
| | | btnClickEmptyClose.AddListener(()=> |
| | | { |
| | | if (btnClickEmptyCloseEvent != null) |
| | | { |
| | | btnClickEmptyCloseEvent(); |
| | | } |
| | | else |
| | | { |
| | | CloseWindow(); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // 设置Canvas属性 |
| | | canvas.overrideSorting = true; |
| | | |
| | | canvas.worldCamera = CameraManager.uiCamera; |
| | | |
| | | canvas.sortingLayerID = SortingLayer.NameToID("UI"); // 确保使用正确的排序层 |
| | | |
| | | // 获取或添加CanvasGroup组件 |
| | | canvasGroup = GetComponent<CanvasGroup>(); |
| | |
| | | } |
| | | |
| | | 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(); |
| | | |
| | | // // 如果后续需要统一处理刘海或者小游戏的界面适配问题 |
| | | // _rectTransform.offsetMin = new Vector2(0, 10); //下方 |
| | | // _rectTransform.offsetMax = new Vector2(0, -50); //上方 |
| | | |
| | | OnOpen(); |
| | | |
| | |
| | | /// <summary> |
| | | /// 播放UI特效 |
| | | /// </summary> |
| | | /// <param name="effectName">特效资源名称</param> |
| | | /// <param name="id">特效资源名称</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) |
| | | public UIEffectPlayer PlayUIEffect(int id, Transform parent = null) |
| | | { |
| | | // 使用默认值 |
| | | 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; |
| | | return UIEffectPlayer.CreateEffect(id, parent, false); |
| | | } |
| | | |
| | | /// <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 |
| | |
| | | canvasGroup.alpha = 0f; |
| | | canvasGroup.blocksRaycasts = false; |
| | | } |
| | | if (canvasScaler != null) |
| | | if (_rectTransform != null) |
| | | { |
| | | canvasScaler.scaleFactor = 1f; |
| | | _rectTransform.localScale = Vector3.one; |
| | | } |
| | | break; |
| | | |
| | |
| | | canvasGroup.alpha = 1f; |
| | | canvasGroup.blocksRaycasts = false; |
| | | } |
| | | if (canvasScaler != null) |
| | | if (_rectTransform != null) |
| | | { |
| | | canvasScaler.scaleFactor = 0.3f; |
| | | _rectTransform.localScale = Vector3.one * 0.3f; |
| | | } |
| | | break; |
| | | |
| | |
| | | canvasGroup.alpha = 1f; |
| | | canvasGroup.blocksRaycasts = false; |
| | | } |
| | | if (canvasScaler != null) |
| | | if (_rectTransform != null) |
| | | { |
| | | canvasScaler.scaleFactor = 0.3f; |
| | | _rectTransform.localScale = Vector3.one * 0.3f; |
| | | } |
| | | break; |
| | | } |
| | |
| | | case UIAnimationType.ScaleInOut: |
| | | if (_rectTransform != null) |
| | | { |
| | | currentAnimation.Append(DOVirtual.Float(0.3f, 1f, animeDuration, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); |
| | | currentAnimation.Append(DOVirtual.Float(0.3f, 1f, animeDuration, (value) => {_rectTransform.localScale = Vector3.one * value;}).SetEase(animationEase)); |
| | | } |
| | | break; |
| | | |
| | |
| | | case UIAnimationType.ScaleOverInOut: |
| | | if (_rectTransform != null) |
| | | { |
| | | currentAnimation.Append(DOVirtual.Float(0.3f, 1.2f, animeDuration, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); |
| | | currentAnimation.Append(DOVirtual.Float(1.2f, 1f, 0.1f, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); |
| | | float startScale = scaleOverInOutCurve.curve.Evaluate(0f); |
| | | _rectTransform.localScale = Vector3.one * startScale; |
| | | currentAnimation.Append( |
| | | DOTween.To( |
| | | () => _rectTransform.localScale.x, |
| | | (value) => _rectTransform.localScale = Vector3.one * value, |
| | | 1f, |
| | | animeDuration |
| | | ) |
| | | .SetEase(scaleOverInOutCurve.curve) |
| | | .OnComplete(() => _rectTransform.localScale = Vector3.one) // 确保最终值1正确 |
| | | ); |
| | | |
| | | } |
| | | break; |
| | | } |
| | |
| | | currentAnimation.OnComplete(() => |
| | | { |
| | | isAnimating = false; |
| | | |
| | | _ResetToBegin(); |
| | | OnOpenAnimationComplete(); |
| | | |
| | | // 启用交互 |
| | |
| | | canvasGroup.alpha = 1f; |
| | | canvasGroup.blocksRaycasts = true; |
| | | } |
| | | if (canvasScaler != null) |
| | | if (_rectTransform != null) |
| | | { |
| | | canvasScaler.scaleFactor = 1f; |
| | | _rectTransform.localScale = Vector3.one; |
| | | } |
| | | |
| | | if (_rectTransform != null) |
| | |
| | | case UIAnimationType.ScaleOverInOut: |
| | | if (_rectTransform != null) |
| | | { |
| | | currentAnimation.Append(DOVirtual.Float(1f, 0.3f, animeDuration, (value) => {canvasScaler.scaleFactor = value;}).SetEase(animationEase)); |
| | | currentAnimation.Append(DOVirtual.Float(1f, 0.3f, animeDuration, (value) => {_rectTransform.localScale = Vector3.one * value;}).SetEase(animationEase)); |
| | | } |
| | | break; |
| | | |