UI模块封装

思考并回答以下问题:

  • 为什么模块的入口就用单例模式?
  • 坚持一个类不超过200行,慢慢下去就会有架构思想了。
  • 时序图和类图太重要了,时序图还重要一点,怎么理解?
  • 为什么要保证UIManager最先执行?怎么设置?

实现

消息交互无非两种方式:
1、引用
2、委托

思路

以Panel为单位。C层管理所有的子控件,控件之间互相交互通过C层(中介者模式)完成,降低耦合。C层怎么找到子控件?用GameObject.Find()是深度遍历,比较麻烦。增加一个UIManager,Panel与Panel之间通过UIManager进行通信。然后让子控件主动上报给UIManager,Panel可以从UIManager取得控件。

如何主动上报?

  • 1、给任何子控件挂一个万能的UIBehaviour。
  • 2、UIBehaviour将自己注册进UIManager。

UIManager:
1、怎么样保存注册到UIManager里面的子控件?

以Panel为单位划分,每个Panel下面又管理了很多子控件。

UIManager.cs 模块管理者

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour
{
// 单例,是模块的入口就用单例模式
public static UIManager Instance;

// widget 控件 各种控件
// 树形结构
// 第1个string表示panel,第2个string表示控件的名字
Dictionary<string, Dictionary<string, GameObject>> allWidget;

/// <summary>
/// 给外部提供的接口
/// 子控件注册到UIManager,存入allWidget字典
/// </summary>
/// <param name="panelName">panel名字</param>
/// <param name="widgetName">子控件名字</param>
/// <param name="obj"></param>
public void RegGameObject(string panelName, string widgetName, GameObject obj)
{
if (!allWidget.ContainsKey(panelName))
{
// new第2个Dictionary
allWidget[panelName] = new Dictionary<string, GameObject>();
}

allWidget[panelName].Add(widgetName, obj);
}

void Awake()
{
Instance = this;
allWidget = new Dictionary<string, Dictionary<string, GameObject>>();
}

/// <summary>
/// 提供得到某一个panel下的某个子控件对象的接口
/// </summary>
/// <param name="">panelName</param>
/// <param name="">widgetName</param>
/// <returns>GameObject </returns>
public GameObject GetGameObject(string panelName, string widgetName)
{
if (allWidget.ContainsKey(panelName))
{
return allWidget[panelName][widgetName];
}
return null;
}
}

UIBase.cs 所有Panel,C层基类,挂载到Panel上

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class UIBase : MonoBehaviour
{
void Awake()
{
// 找到所有子控件
Transform[] allChildren = transform.GetComponentsInChildren<Transform>();

// 给所有以_N结尾的子控件挂上UIBehaviour脚本
for (int i = 0; i < allChildren.Length; i++)
{
if (allChildren[i].name.EndsWith("_N"))
{
allChildren[i].gameObject.AddComponent<UIBehaviour>();
}
}
}

// 子控件的行为需要C层来做控制
// 根据子控件名字获取子控件对象
// 从UIManager拿
public GameObject GetWidget(string widgetName)
{
return UIManager.Instance.GetGameObject(transform.name, widgetName);
}

// 得到子控件的UIBehaviour脚本
public UIBehaviour GetBehaviour(string widgetName)
{
GameObject tmpObj = GetWidget(widgetName);

if (tmpObj != null)
{
return tmpObj.GetComponent<UIBehaviour>();
}
return null;
}

// Controller
// 1能获取这个组件
// 2能获取上面的UIBehaviour
// 3能用UIBehaviour
public void AddButtonListener(string widgetName, UnityAction action)
{
UIBehaviour tmpBehaviour = GetBehaviour(widgetName);

if (tmpBehaviour != null)
{
tmpBehaviour.AddButtonListener(action);
}
}

public void ChangeTextContent(string widgetName, string content)
{
UIBehaviour tmpBehaviour = GetBehaviour(widgetName);

if (tmpBehaviour != null)
{
tmpBehaviour.ChangeTextContent(content);
}
}
}

UIBehaviour.cs 挂载到需要注册的子控件上,比如一个Button,显示的text就不要挂载了

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;

public class UIBehaviour : MonoBehaviour
{
void Awake()
{
// 上来就注册
// 先寻找上一层的Panel
UIBase tmpBase = transform.GetComponentInParent<UIBase>();

UIManager.Instance.RegGameObject(tmpBase.name, transform.name, gameObject);
}

// 添加监听事件
public void AddButtonListener(UnityAction action)
{
Button tmpBtn = transform.GetComponent<Button>();

if(tmpBtn != null)
{
tmpBtn.onClick.AddListener(action);
}
}

public void AddSliderListener(UnityAction<float> action)
{
Slider tmpSlider = transform.GetComponent<Slider>();

if(tmpSlider != null)
{
tmpSlider.onValueChanged.AddListener(action);
}
}

public void AddInputFieldEndEditListener(UnityAction<string> action)
{
InputField tmpInputField = transform.GetComponent<InputField>();

if(tmpInputField != null)
{
tmpInputField.onEndEdit.AddListener(action);
}
}

public void AddInputFieldValueChangedListener(UnityAction<string> action)
{
InputField tmpInputField = transform.GetComponent<InputField>();

if(tmpInputField != null)
{
tmpInputField.onValueChanged.AddListener(action);
}
}

public void ChangeTextContent(string content)
{
Text tmpText = transform.GetComponent<Text>();

if(tmpText != null)
{
tmpText.text = content;
}
}

public void ChangeImage(Sprite image)
{
Image tmpImage = transform.GetComponent<Image>();

if(tmpImage != null)
{
tmpImage.sprite = image;
}
}
}

实例

先把UIManager设置为最先执行。

创建一个空对象UIManager,挂载UIManager.cs

创建一个LoadingPanel面板,挂载LoadingCtrl脚本

LoadingCtrl.cs 挂载到LoadingPanel上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LoadingCtrl : UIBase
{
public void OnClick()
{
Debug.Log("Loading On Click !");
}

void Start()
{
AddButtonListener("Button_N", OnClick);
ChangeTextContent("Text_N", "你好");
}
}

RegCtrl.cs 挂载到RegPanel上,以Panel为单位,所以下面可以有一个Button_N

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 模型层
public class RegModel
{
public string password {get;set;}
public string userName {get;set;}
}

// 逻辑层
public class RegLogic
{
RegCtrl regCtrl;

public void OnClick()
{
Debug.Log("Reg On Click !");
}
}

public class RegCtrl : UIBase
{
RegModel regModel;
RegLogic regLogic;

void Start()
{
regModel = new RegModel();
regLogic = new RegLogic();
AddButtonListener("Button_N", regLogic.OnClick);
}
}

注意事项:

  • 1、在同一个Panel里面,要使用的控件名字不能一样。
  • 2、不同的Panel的名字也不能一样。
  • 3、不同的Panel下面的子控件的名字可以一样。
0%