前言 本教程是在B站UP“一唐老狮一 ”的视频教程 《Unity的程序基础框架》 中,UI管理模块 的改进。
因为UP这个教程变成了付费课程,如果想看代码是怎么写的,可以去支持付费课程
或者这篇文章,是别人的笔记: 《Unity3D程序基础框架(下)》 中的“UI管理模块 ”部分
主要功能 统一管理当前面板下的控件(按钮、图片、文本框等) UI分层 反射型事件监听 其中对原版的改进部分主要是反射型事件监听(我瞎起的名,不知道有没有专业名词),我不太确定这么做会不会有性能问题,还请大佬指教(在视频评论)
这个东西的作用是你在使用按钮等控件时,可以直接在对应的面板类里写方法
比如我有一个面板,上面有几个控件,分别是名叫btnTest
和btn2
的两个按钮和名叫Test
的复选框
我只需要写一个类,继承BasePanel
类,其他任何复杂的东西都不需要
就可以直接在这个面板类里写控件名+OnClick
和控件名+OnValueChanged
即可分别监听对应名称的按钮点击事件和复选框切换事件
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;public class LoginPanel : BasePanel { public void btnTestOnClick () { Debug.Log("Click!!!" ); } public void btn2OnClick () { Debug.Log("btn2 Click!!!" ); } public void TestOnValueChanged (bool value ) { Debug.Log(value ); } }
效果演示:
如果有其他事件监听,可以添加更多
教程 除了反射型事件监听这个功能,其实整个UI部分还包括很多,我们一个个来看
CanvasManager 画布管理器,因为UI的所有东西都需要Canvas
和EventSystem
,你可以把这两个东西拿出来,分别作为预制体
在Canvas
里创建几个空物体,用于实现分层
Bot、Mid、Top、Sys分别是底层、中层、顶层、系统层
UI分层的意义很好理解,举个例子。我有一个装备栏把它放在底层,鼠标放在装备上弹出装备信息,我需要显示在物品栏上层,点击物品信息的分解装备按钮弹出“是否确认分解”提示框,需要放在更上层
为了更方便的使用,我把这些统一放在了CanvasManager
中
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 using System;using System.Collections;using UnityEngine;public enum UI_Layer{ BOT, MID, TOP, SYS } public class CanvasManager : SingletonMonoBase <CanvasManager >{ private Transform _canvas; private Transform _bot; private Transform _mid; private Transform _top; private Transform _sys; public void Awake () { DontDestroyOnLoad(gameObject); GameObject obj = AssetBundleManager.Instance.Load<GameObject>("ui" , "Canvas" ); _canvas = obj.transform; _bot = _canvas.Find("Bot" ); _mid = _canvas.Find("Mid" ); _top = _canvas.Find("Top" ); _sys = _canvas.Find("Sys" ); obj = AssetBundleManager.Instance.Load<GameObject>("ui" , "EventSystem" ); } public Transform GetCanvas () { return _canvas; } public Transform GetLayer (UI_Layer layer ) { switch (layer) { case UI_Layer.BOT: return _bot; case UI_Layer.MID: return _mid; case UI_Layer.TOP: return _top; case UI_Layer.SYS: return _sys; } return null ; } public void SetLayer (GameObject obj, UI_Layer layer ) { obj.transform.SetParent(GetLayer(layer)); } public void SetLayer (Transform obj, UI_Layer layer ) { obj.SetParent(GetLayer(layer)); } }
BasePanel 这是面板基类,其他面板需要继承这个类
作用是找到面板下的控件并储存起来、给控件添加事件监听
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 using System;using System.Text.RegularExpressions;using System.Reflection;using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;using UnityEngine.EventSystems;public abstract class BasePanel : MonoBehaviour { private Dictionary<string , Dictionary<int , UIBehaviour>> _ui = new Dictionary<string , Dictionary<int , UIBehaviour>>(); private void Awake () { FindChildrenControl<Button>(); FindChildrenControl<Image>(); FindChildrenControl<Text>(); FindChildrenControl<Toggle>(); FindChildrenControl<Slider>(); FindChildrenControl<ScrollRect>(); } private void FindChildrenControl <T >() where T : UIBehaviour { string dictName = typeof (T).ToString(); T[] controls = this .GetComponentsInChildren<T>(); for (int i = 0 ; i < controls.Length; i++) { string name = controls[i].gameObject.name; if (controls[i] is Button) { Button btn = controls[i] as Button; btn.onClick.AddListener(() => { RegisterButtonOnClick(name); }); } else if (controls[i] is Toggle) { Toggle tog = controls[i] as Toggle; tog.onValueChanged.AddListener((value ) => { RegisterToggleOnValueChanged(name, value ); }); } if (_ui.ContainsKey(name)) { _ui[name].Add(controls[i].GetInstanceID(), controls[i]); return ; } _ui.Add(name, new Dictionary<int , UIBehaviour>() { { controls[i].GetInstanceID(), controls[i] } }); } } protected void RegisterButtonOnClick (string name ) { ExecutionEvent(name, null , UIEventType.BUTTON_ON_CLICK); } protected void RegisterToggleOnValueChanged (string name, bool value ) { ExecutionEvent(name, new List<object > { value }, UIEventType.TOOGLE_ON_VALUE_CHANGED); } protected void ExecutionEvent (string name, List<object > parametors, UIEventType eventType ) { if (parametors == null ) parametors = new List<object >(); Match match = Regex.Match(name, @"(.*)_(\d+)$" ); string num = match.Groups[2 ].ToString(); string eventName = null ; switch (eventType) { case UIEventType.BUTTON_ON_CLICK: eventName = "OnClick" ; break ; case UIEventType.TOOGLE_ON_VALUE_CHANGED: eventName = "OnValueChanged" ; break ; } Type panelClassType = this .GetType(); string methodName = name + eventName; if (num != "" ) { methodName = match.Groups[1 ].ToString() + eventName + "N" ; parametors.Add(int .Parse(num)); } MethodInfo method = panelClassType.GetMethod(methodName); if (method != null ) method.Invoke(this , parametors.ToArray()); } }
UIManager 这里是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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Events;public class UIManager <T > : SingletonBase <UIManager <T >> where T : BasePanel <T >{ private Dictionary<string , T> _panel = new Dictionary<string , T>(); public void ShowPanel (UI_Layer layer, UnityAction<T> callback ) { string panelName = typeof (T).ToString(); if (_panel.ContainsKey(panelName)) { _panel[panelName].gameObject.SetActive(true ); if (callback != null ) callback(_panel[panelName]); return ; } AssetBundleManager.Instance.LoadAsync<GameObject>("ui" , panelName, (obj) => { Transform parent = CanvasManager.Instance.GetLayer(UI_Layer.BOT); switch (layer) { case UI_Layer.MID: parent = CanvasManager.Instance.GetLayer(UI_Layer.MID); break ; case UI_Layer.TOP: parent = CanvasManager.Instance.GetLayer(UI_Layer.TOP); break ; case UI_Layer.SYS: parent = CanvasManager.Instance.GetLayer(UI_Layer.SYS); break ; } obj.transform.SetParent(parent); obj.transform.localPosition = Vector3.zero; obj.transform.localScale = Vector3.one; RectTransform objRect = obj.transform as RectTransform; objRect.offsetMax = Vector2.zero; objRect.offsetMin = Vector2.zero; T panel = obj.GetComponent<T>(); if (callback != null ) { callback(panel); } _panel.Add(panelName, panel); }); } public void HidePanel () { string panelName = typeof (T).ToString(); if (_panel.ContainsKey(panelName)) { _panel[panelName].gameObject.SetActive(false ); return ; } } public T GetPanel () { string panelName = typeof (T).ToString(); if (_panel.ContainsKey(panelName)) { return _panel[panelName]; } return null ; } }
最后新建各种面板类,继承面板基类,上面那个LoginPanel
就是其中一个面板
后言 至于SingletonBase
和SingletonMonoBase
是什么,可以去唐老狮那个基础视频中第三节看(免费的)
作用是单例模式和继承MonoBehaviour
的单例模式