这个框架简单易懂,上手就可以直接拿来用,主要是单例管理类,界面和界面之间的互相交流通过单例去实现,个人感觉不是很好,但是我特别喜欢他的管理层级非常分明。
之后会发一个广播机制,结合上这套UI框架,但是本文主要是讲这个框架,所以后话就后话说吧,话不多说上代码
(一)UImanager:
以panel为编程单位,储存管理panel和下级控件,向外提供接口方法,接收界面和控件主动往管理类里注册,排查是否重复注册以及最后销毁等
注意:我在使用的时候发现还是有很多需要注意的问题的
1、UIManager必须游戏启动第一时间启动,整个游戏进程中不能销毁的,可以给个空物体,然后dontdestory,再动态addcompnent加入
2、整个框架主体分为三个脚本,执行顺序为 UIManager > UIBase > UIBehaviour,我在第一使用的时候发现,只要一加载界面就会报空,百思不其解,然后层层断点发现执行顺序的问题,UIBehaivour先执行了,导致找不到UIManager往里注册,有点坑
3、因为字典存的是界面name,控件name,控件对象,在RegistGameObject函数中会进行一次筛选,key不能相同,也就意味着界面名字不能重复,同界面的控件名字不能重复,我在做交易列表时被这个搞得头疼,一定要注意,加载完换个名字
1 using System.Collections.Generic; 2 using UnityEngine; 3 4 public class UIManager : MonoBehaviour 5 { 6 Dictionary<string, Dictionary<string, GameObject>> allChild; 7 public static UIManager Instance; 8 9 private GameObject mainCanvas; 10 public GameObject MainCanvas 11 { 12 get 13 { 14 if (mainCanvas == null) 15 { 16 mainCanvas = GameObject.FindGameObjectWithTag("MainCanvas"); 17 } 18 return mainCanvas; 19 } 20 } 21 22 public GameObject InstantiateUIPanelGameObject(string path, string endWithName = "", Transform parent = null) 23 { 24 if (parent == null) 25 { 26 parent = MainCanvas.transform; 27 } 28 GameObject tmpGameObj = Instantiate(Resources.Load<GameObject>(path)); 29 tmpGameObj.name = tmpGameObj.name.Replace("(Clone)", endWithName); 30 tmpGameObj.transform.SetParent(parent,false); 31 return tmpGameObj; 32 } 33 34 void Awake() 35 { 36 allChild = new Dictionary<string, Dictionary<string, GameObject>>(); 37 Instance = this; 38 } 39 40 /// <summary> 41 /// 可以拿到字典中已经注册过的panel下的控件 42 /// </summary> 43 /// <param name="panelName"></param> 44 /// <param name="widgeNmae"></param> 45 /// <returns></returns> 46 public GameObject GetGameObject(string panelName, string widgeNmae) 47 { 48 if (allChild.ContainsKey(panelName)) 49 { 50 return allChild[panelName][widgeNmae]; 51 } 52 return null; 53 } 54 55 /// <summary> 56 /// 反注册,当你确定不再需要这个界面的时候,没必要浪费空间 57 /// </summary> 58 /// <param name="panelName"></param> 59 /// <param name="widgeName"></param> 60 public void UnRegistGameObject(string panelName, string widgeName) 61 { 62 if (allChild.ContainsKey(panelName)) 63 { 64 if (allChild.ContainsKey(widgeName)) 65 { 66 allChild[panelName].Remove(widgeName); 67 } 68 } 69 } 70 71 /// <summary> 72 /// 销毁某个panel上所有控件 73 /// </summary> 74 /// <param name="panelName"></param> 75 public void DestroyAllWidgeFormPanel(string panelName) 76 { 77 if (allChild.ContainsKey(panelName)) 78 { 79 if (allChild[panelName] != null) 80 { 81 allChild[panelName].Clear(); 82 } 83 } 84 } 85 86 /// <summary> 87 /// 供UIBehaviour调用,UIBehaviour每个控件都会动态挂载,并且在awake里面调用,注册自己 88 /// </summary> 89 /// <param name="panelName"></param> 90 /// <param name="widgeName"></param> 91 /// <param name="widge"></param> 92 public void RegistGameObject(string panelName, string widgeName, GameObject widge) 93 { 94 if (!allChild.ContainsKey(panelName)) 95 { 96 allChild.Add(panelName, new Dictionary<string, GameObject>()); 97 } 98 99 if (!allChild[panelName].ContainsKey(widgeName)) 100 { 101 allChild[panelName].Add(widgeName, widge); 102 } 103 else 104 { 105 Debug.LogError(" has been registed => " + widgeName); 106 } 107 } 108 109 public void CleanDic() 110 { 111 allChild.Clear(); 112 } 113 }
(二)UIBase:
这是一个供界面脚本继承的基类,前面也说次框架是以panel为编程单位的,这也就是说只要你想用这套框架,只需要在panel上挂载自己写的脚本去控制底下的所有控件即可,但是这个脚本必须继承UIBase
这里又存在一个硬性条件,那就是panel下,你所要写功能的控件,对象名的结尾必须_N,这是为了动态挂脚本UIBehaivour,我想这是为了让预设体或者AB尽量减少依赖吧
在UIBase里存着panel下所有的控件,不用再去find了,避免低效,同时在UIBase里对各种控件的回调进行了二次封装,直接调用传参就行,这点我很喜欢,简单粗暴
如果有需要,也可以自己拓展封装,底层回调在UIBehaivour封装,UIBase进行二次封装
1 using UnityEngine; 2 using UnityEngine.Events; 3 using UnityEngine.EventSystems; 4 using UnityEngine.UI; 5 6 public class UIBase : MonoBehaviour 7 { 8 public Transform[] allChild; 9 10 private void Awake() 11 { 12 // 将所有的子控件 获取到。 13 allChild = gameObject.GetComponentsInChildren<Transform>(); 14 for (int i = 0; i < allChild.Length; i++) 15 { 16 //以名字区别控件,动态挂载脚本,所以必须按规范命名 17 if (allChild[i].name.EndsWith("_N")) 18 { 19 allChild[i].gameObject.AddComponent<UIBehaviour>(); 20 } 21 } 22 } 23 24 public virtual void OnDestroy() 25 { 26 UIManagerLen.Instance.DestroyAllWidgeFormPanel(transform.name); 27 } 28 29 /// <summary> 30 /// 从UIManager里面拿子控件 31 /// </summary> 32 /// <param name="widgaeName"></param> 33 /// <returns></returns> 34 public GameObject GetGameObject(string widgaeName) 35 { 36 return UIManagerLen.Instance.GetGameObject(transform.name, widgaeName); 37 } 38 39 /// <summary> 40 /// 为了拿到底层封装 41 /// </summary> 42 /// <param name="widgaeName"></param> 43 /// <returns></returns> 44 public UIBehaviour GetBehavour(string widgaeName) 45 { 46 // 找对象 47 GameObject tmpBtn = GetGameObject(widgaeName); 48 if (tmpBtn) 49 { 50 // 拿到 UIBehavour 51 UIBehaviour behavour = tmpBtn.GetComponent<UIBehaviour>(); 52 return behavour; 53 } 54 return null; 55 } 56 57 /// <summary> 58 /// button 59 /// </summary> 60 /// <param name="widageName"></param> 61 /// <param name="action"></param> 62 public void AddButtonListen(string widageName, UnityAction action) 63 { 64 UIBehaviour behavour = GetBehavour(widageName); 65 if (behavour) 66 { 67 //绑定事件 68 behavour.AddButtonListener(action); 69 } 70 } 71 72 /// <summary> 73 /// slider 74 /// </summary> 75 /// <param name="widageName"></param> 76 /// <param name="action"></param> 77 public void AddSliderListen(string widageName, UnityAction<float> action) 78 { 79 UIBehaviour behavour = GetBehavour(widageName); 80 if (behavour) 81 { 82 //绑定事件 83 behavour.AddSliderListen(action); 84 } 85 } 86 87 88 /// <summary> 89 /// scroll view 90 /// </summary> 91 /// <param name="cellName"></param> 92 /// <param name="widageName"></param> 93 /// <param name="action"></param> 94 public void AddScrollListen(string widageName, UnityAction<Vector2> action) 95 { 96 UIBehaviour behavour = GetBehavour(widageName); 97 if (behavour) 98 { 99 //绑定事件 100 behavour.AddScrollListen(action); 101 } 102 } 103 104 /// <summary> 105 /// dropdown 106 /// </summary> 107 /// <param name="widageName"></param> 108 /// <param name="action"></param> 109 public void AddDropDownListen(string widageName, UnityAction<int> action) 110 { 111 UIBehaviour behavour = GetBehavour(widageName); 112 if (behavour) 113 { 114 //绑定事件 115 behavour.AddDropDownListen(action); 116 } 117 } 118 119 /// <summary> 120 /// toggle 121 /// </summary> 122 /// <param name="widageName"></param> 123 /// <param name="action"></param> 124 public void AddToggleListen(string widageName, UnityAction<bool> action) 125 { 126 UIBehaviour behavour = GetBehavour(widageName); 127 if (behavour) 128 { 129 //绑定事件 130 behavour.AddToggleListener(action); 131 } 132 } 133 134 135 /// <summary> 136 /// inputfiled 137 /// </summary> 138 /// <param name="widageName"></param> 139 /// <param name="action"></param> 140 public void AddInputListen(string widageName, UnityAction<string> action) 141 { 142 UIBehaviour behavour = GetBehavour(widageName); 143 if (behavour) 144 { 145 //绑定事件 146 behavour.AddInputEndListen(action); 147 } 148 } 149 150 151 /// <summary> 152 /// 文本赋值 153 /// </summary> 154 /// <param name="widageName"></param> 155 /// <param name="value"></param> 156 public void ChangeText(string widageName, string value) 157 { 158 UIBehaviour behavour = GetBehavour(widageName); 159 if (behavour) 160 { 161 //绑定事件 162 behavour.ChangeText(value); 163 } 164 } 165 166 /// <summary> 167 /// 图片点击 168 /// </summary> 169 /// <param name="widageName"></param> 170 /// <param name="action"></param> 171 public void AddPointClick(string widageName, UnityAction<BaseEventData> action) 172 { 173 UIBehaviour behavour = GetBehavour(widageName); 174 if (behavour) 175 { 176 behavour.AddInterfaceLisenter(EventTriggerType.PointerClick, action); 177 } 178 } 179 180 181 /// <summary> 182 /// 图片拖拽 开始,正在拖,结束 183 /// </summary> 184 /// <param name="widageName"></param> 185 /// <param name="action"></param> 186 public void AddBeginDrag(string widageName, UnityAction<BaseEventData> action) 187 { 188 UIBehaviour behavour = GetBehavour(widageName); 189 if (behavour) 190 { 191 behavour.AddInterfaceLisenter(EventTriggerType.BeginDrag, action); 192 } 193 } 194 public void AddOnDrag(string widageName, UnityAction<BaseEventData> action) 195 { 196 UIBehaviour behavour = GetBehavour(widageName); 197 if (behavour) 198 { 199 behavour.AddInterfaceLisenter(EventTriggerType.Drag, action); 200 } 201 } 202 public void AddEndDrag(string widageName, UnityAction<BaseEventData> action) 203 { 204 UIBehaviour behavour = GetBehavour(widageName); 205 if (behavour) 206 { 207 behavour.AddInterfaceLisenter(EventTriggerType.EndDrag, action); 208 } 209 } 210 211 212 /// <summary> 213 /// 更换图片 214 /// </summary> 215 /// <param name="widageName"></param> 216 /// <param name="value"></param> 217 public void SetImage(string widageName, Sprite value) 218 { 219 UIBehaviour subManager = GetBehavour(widageName); 220 if (subManager) 221 { 222 subManager.GetComponent<Image>().sprite = value; 223 } 224 } 225 226 /// <summary> 227 /// 获取图片 228 /// </summary> 229 /// <param name="widageName"></param> 230 /// <returns></returns> 231 public Image GetImage(string widageName) 232 { 233 UIBehaviour behavour = GetBehavour(widageName); 234 if (behavour) 235 { 236 return behavour.GetImage(); 237 } 238 return null; 239 } 240 241 /// <summary> 242 /// 拿到text组件的值 243 /// </summary> 244 /// <param name="widageName"></param> 245 /// <returns></returns> 246 public string GetText(string widageName) 247 { 248 UIBehaviour behavour = GetBehavour(widageName); 249 if (behavour) 250 { 251 return behavour.GetText(); 252 } 253 return null; 254 } 255 }
(三)UIBehaivour:
这个脚本就是动态挂载各个控件上的脚本,作用只有两个
1、向UIManager注册,接受管理
2、底层回调封装,供UIBase调用从而进行二次封装后直接使用 如果需要拓展也是从这里开始
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEngine.UI; 5 using UnityEngine.Events; 6 using UnityEngine.EventSystems; 7 8 9 public class UIBehaviour : MonoBehaviour 10 { 11 UIBase tmpBase; 12 private void Awake() 13 { 14 tmpBase = gameObject.GetComponentInParent<UIBase>();//找到父类,也就是panel 15 16 UIManagerLen.Instance.RegistGameObject(tmpBase.name, transform.name, gameObject);//向管理类注册自己 17 } 18 19 private void OnDestroy() 20 { 21 UIManagerLen.Instance.UnRegistGameObject(tmpBase.name, gameObject.name); 22 } 23 24 #region UGUI各种控件的回调封装 25 public void AddButtonListener(UnityAction action)//按钮回调封装 26 { 27 Button tmpBtn = gameObject.GetComponent<Button>(); 28 if (tmpBtn) tmpBtn.onClick.AddListener(action); 29 } 30 public void AddSliderListen(UnityAction<float> action)//滚动条回调封装 31 { 32 Slider tmpBtn = gameObject.GetComponent<Slider>(); 33 if (tmpBtn) tmpBtn.onValueChanged.AddListener(action); 34 } 35 public void AddDropDownListen(UnityAction<int> action)//下拉框回调封装 36 { 37 Dropdown tmpBtn = gameObject.GetComponent<Dropdown>(); 38 if (tmpBtn) tmpBtn.onValueChanged.AddListener(action); 39 } 40 public void AddScrollListen(UnityAction<Vector2> action)//滚动框回调封装 41 { 42 ScrollRect tmpBtn = gameObject.GetComponent<ScrollRect>(); 43 if (tmpBtn) tmpBtn.onValueChanged.AddListener(action); 44 } 45 public void AddToggleListener(UnityAction<bool> action) 46 { 47 Toggle tmpToggle = gameObject.GetComponent<Toggle>(); 48 if (tmpToggle) tmpToggle.onValueChanged.AddListener(action); 49 } 50 public void AddInputEndListen(UnityAction<string> action)//输入框回调封装 51 { 52 InputField tmpBtn = GetComponent<InputField>(); 53 if (tmpBtn) tmpBtn.onEndEdit.AddListener(action); 54 } 55 public void ChangeText(string value)//改变文本的接口 56 { 57 Text tmpBtn = gameObject.GetComponent<Text>(); 58 if (tmpBtn) tmpBtn.text = value; 59 } 60 public Image GetImage()//如果控件是图片,提供获取图片的接口 61 { 62 Image tmpBtn = gameObject.GetComponent<Image>(); 63 return tmpBtn; 64 } 65 public string GetText()//如果控件是文本,提供获取文本的接口 66 { 67 Text tmpBtn = gameObject.GetComponent<Text>(); 68 return tmpBtn.text; 69 } 70 #endregion 71 72 /// <summary> 73 /// 用代码动态添加接口事件 74 /// </summary> 75 /// <param name="action"></param> 76 public void AddInterfaceLisenter(EventTriggerType triger, UnityAction<BaseEventData> action) 77 { 78 79 EventTrigger trigger = gameObject.GetComponent<EventTrigger>(); 80 81 if (trigger == null) 82 trigger = gameObject.AddComponent<EventTrigger>(); 83 84 EventTrigger.Entry entry = new EventTrigger.Entry(); 85 //绑定哪个接口 86 entry.eventID = triger; 87 88 entry.callback = new EventTrigger.TriggerEvent(); 89 90 entry.callback.AddListener(action); 91 92 trigger.triggers.Add(entry); 93 } 94 }
(四)使用:
继承,调用就行了,简单示例一下
总结:
这个框架有优点,也有缺点
优点是层级管理分明,分工明确,封装之后调用简单方便
缺点界面和界面的交流,以及模块和模块之间的交流,耦合性还是不能很好的解决
有时间会写一套广播机制,配合着使用,已达到尽可能解耦的效果