目前我们的游戏暂停界面并不是弹窗的形式,而是覆盖掉了之前的GameUI,这是不合理的。
在这里我们引入UI分层的概念,定义好每个UI的层级关系,让每种UI更合理地显示在屏幕上。
UILayer枚举
在Enums中定义三个UI层级
1 2 3 4 5 6 7
| public enum UILayer { BASE_UI, MIDDLE_UI, TOP_UI, COUNT }
|
- 每个UI层级内的UI界面互相覆盖
- 不同UI层级的UI界面可以同时显示
这里只定义了三个层级,一般够用了。
UILayerMgr
我们定义好三个层级,需要在游戏启动时根据三个层级创建UI父物体,然后新建的UI根据自己的层级自动挂载到对应的UI父物体上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| using System.Collections.Generic; using UnityEngine;
public class UILayerMgr : NormalSingleton<UILayerMgr>,IInit { private Dictionary<UILayer, RectTransform> _uiParentDic; public void Init() { _uiParentDic = new(); var canvas = GameObject.FindObjectOfType<Canvas>(); if(canvas == null ) { Debug.LogError("当前场景中未发现Canvas"); return; } for (UILayer i = UILayer.BASE_UI; i < UILayer.COUNT; i++) { GameObject child = new(i.ToString()); child.transform.SetParent(canvas.transform); var rect = child.AddComponent<RectTransform>(); rect.anchoredPosition = Vector2.zero; _uiParentDic.Add(i, rect); } }
public void SetParent(string path, Transform trans) { var layer = GetLayer(path); var parent = _uiParentDic[layer]; trans.SetParent(parent); } public UILayer GetLayer(string path) { if( UILayerConfig.Layers.TryGetValue(path, out UILayer layer) ) { return layer; } else { Debug.LogError("当前prefab未指定层级配置:" + path); return UILayer.BASE_UI; }
} }
|
UIMgr
修改UIMgr的UI显示逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
public IView Show(string path) { if(uiStack.Count > 0) { string pre = uiStack.Peek(); if (GetLayer(pre) >= GetLayer(path)) { HideAll(viewsDic[pre]); } } } private UILayer GetLayer(string path) { return UILayerMgr.Instance.GetLayer(path); } private IView InitView(string path) { if (viewsDic.ContainsKey(path)) { return viewsDic[path]; } else { GameObject go = LoadMgr.Instance.LoadPrefabAndInstantiate(path,Canvas.transform); if (go != null) { InitLayer(path, go.transform); AddTypeComponent(go, path); } else Debug.LogError("没有找到对应的Prefab:" + path); return null; } } private void InitLayer(string path, Transform viewTrans) { UILayerMgr.Instance.SetParent(path, viewTrans); }
|
注意,在实际的UI层级管理中,关闭掉一个MIDDLE_UI,一定会连带关闭掉它之前的BASE_UI,在这里并没有实现。在Show方法中,我们只判断了if (GetLayer(pre) >= GetLayer(path)),然后关闭掉了之前的UI。所以在设计层面,我们打开Middle UI时一定不能在Middle UI中打开一个Base UI。
UILayerConfig
每个UI的Prefab都需要指定自己所属的层级,我们把它写在一个Config当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| using System.Collections.Generic;
public class UILayerConfig { public static readonly Dictionary<string, UILayer> Layers = new() { {Pathes.START_VIEW, UILayer.BASE_UI }, {Pathes.SELECT_HERO_VIEW, UILayer.BASE_UI }, {Pathes.STRENGTHEN_VIEW, UILayer.BASE_UI }, {Pathes.LOADING_VIEW, UILayer.BASE_UI }, {Pathes.GAMEUI_VIEW, UILayer.BASE_UI }, {Pathes.LEVELS_VIEW, UILayer.BASE_UI }, {Pathes.PAUSE_VIEW, UILayer.MIDDLE_UI }, {Pathes.DIALOG_VIEW, UILayer.TOP_UI } }; }
|
LifeCycleAddConfig
在生命周期初始化部分添加UILayerMgr
1 2 3 4 5 6 7 8
| private void Add() { Objects.Add(UILayerMgr.Instance); Objects.Add(ConfigMgr.Instance); Objects.Add(new InitCustomAttributes()); }
|
剔除LoadingUI
当前游戏从主界面进入游戏界面时,我们的UI入栈顺序为:
…LEVELS——>LOADING——>GAMEUI。
所以当我们想从GameUI界面返回主界面时,会回退到LOADING界面,这是不合理的。我们应该让Loading界面不参与入栈。
修改UIMgr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private readonly HashSet<string> _skipViews = new() { Pathes.LOADING_VIEW }; public IView Show(string path) { if(uiStack.Count > 0) { } IView view = InitView(path); if (view != null) { ShowAll(view); if(!_skipViews.Contains(path)) uiStack.Push(path); viewsDic[path] = view; } return view; }
public void Hide(string name) { HideAll(viewsDic[name]); }
|
修改Show方法,让UIMgr显示UI时跳过skipViews的UI,不让它加入uiStack。
添加Hide方法,Loading界面没有在栈中,所以不能调用Back方法,必须主动调用UIMgr的Hide方法才能让自己关闭。
修改LoadingController
修改UpdateFun方法,添加跳转目标场景的逻辑判断,来决定下一个要显示的UI是什么。然后主动调用一下UIMgr.Hide方法
1 2 3 4 5 6 7 8 9 10 11 12
| public override void UpdateFun() { base.UpdateFun(); if(SceneMgr.Instance.Process() == 1f) { if (GameStateModel.Instance.TargetScene == SceneName.Game) UIMgr.Instance.Show(Pathes.GAMEUI_VIEW); else if (GameStateModel.Instance.TargetScene == SceneName.StartScene) UIMgr.Instance.Back(); UIMgr.Instance.Hide(Pathes.LOADING_VIEW); } }
|