Unity笔记-UI框架
Unity笔记-UI框架
Wang ChenHan
这是一个普通的消息框架
其中,UIManager与其他分支不同。如NPCManager和Enemy只有一些功能,而UI在制作游戏时通常会有十几个功能(如血条,技能,背包,页面等)。且页面之间要相互切换。
所以,UI框架诞生了。
UI框架1
嵌套在消息框架里,为UIManager做简化。
UI框架可以让各个页面互相切换,简洁条理。
UI框架将每个页面(UI)制作成预设体(prefab),UIManager负责实例化页面。这是其中一种UI框架的实现。
步骤:创建Canvas画布,在里面创建UI页面。UI页面上可以有很多UI子物体。然后将UI页面(不包括Canvas)
拖到Project里(显示项目文件夹的地方),然后,用UIManager类在必要的时候实例化预制体。
- 制作UI管理器基类
- 创建UI页面列表(集合),存储每个预制体
- 定义一个带参数的方法,用于在必要条件时实例化某个UI页面预制体
这个过程很简单,用unity Resources,这里不多赘述。UI框架2
第二种方式:
将所有UI界面存储在Canvas下面(成为Canvas子物体),都取消显示(激活),不制作成预设体。
UIManager此时管理页面的激活,而不是实例化。
一个管理器下面有很多UI页面,每个页面下还有很多控件(Image,Text…)
所以,我们将这些分为三层。(UI管理器,UI页面,UI控件)
我们抽象出3个类: - UIManager UI管理器
- UIController UI页面控制器基类
- UIControl UI控件
UIManager
先导入消息框架(详见Unity笔记-消息框架)新建Base文件夹。把消息框架四个基类放入新建的Base文件夹。
新建一个UI文件夹,在里面创建UIManager,UIController,UIControl脚本。
先写UIManager停!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
29using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
public class UIManager : ManagerBase//继承管理基类
{
public Dictionary<string,UIController> UIControllerDic = new Dictionary<string,UIController>();//UI页面字典
public void SetActive(string controllerName,bool active){
transform.Find(controllerName).gameObject.SetActive(active);
//激活UI页面
}
//获取页面上的子控件
//获取某个页面的某个控件
public UIControl GetUIControl(string controllerName,string controlName)
{
if(UIControllerDic.ContainsKey(controllerName)){//如果字典里包含此页面
//寻找页面子控件
}
}
public override byte GetMessageType()
{
return MessageType.Type_UI;
}
}
先不管报错,去修改UIController类。
UIController
代码:然后回头修改UIManager类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 System.Collections.Specialized;
using UnityEngine;
public class UIController : MonoBehaviour
{
// Start is called before the first frame update
//控件字典
public Dictionary<string,UIControl> UIControlDic = new Dictionary<string, UIControl>();
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}完成UIManager1
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
34using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
public class UIManager: ManagerBase//继承管理基类
{
public Dictionary<string,UIController> UIControllerDic = new Dictionary<string,UIController>();//UI页面字典
public void SetActive(string controllerName,bool active){
transform.Find(controllerName).gameObject.SetActive(active);
//激活UI页面
}
//获取页面上的子控件
//获取某个页面的某个控件
public UIControl GetUIControl(string controllerName,string controlName)
{
if(UIControllerDic.ContainsKey(controllerName)){//如果字典里包含此页面
//寻找页面子控件
//当前参数对应的页面里面的控件字典里是否包含参数对应控件
if(UIControllerDic[controllerName].UIControlDic.ContainsKey(controlName)){
return UIControllerDic[controllerName].UIControlDic[controlName];
}
}
return null;
}
public override byte GetMessageType()
{
return MessageType.Type_UI;
}
}UIController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;
public class UIController : MonoBehaviour
{
// Start is called before the first frame update
//控件字典
public Dictionary<string,UIControl> UIControlDic = new Dictionary<string, UIControl>();
private void Awake()
{
//将当前的页面控制器添加到Manager中
UIManager.Instance.GetComponent<UIManager>().UIControllerDic.Add(transform.name,this);
//给子控件添加UIControl
foreach(Transform tran in transform){//遍历自身和子物体
if(tran.gameObject.GetComponent<UIControl>() == null){
tran.gameObject.AddComponent<UIControl>();
}
}
}
}UIControl
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
74using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class UIControl : MonoBehaviour
{
public UIController controller;//副控制器(附属于哪个控制器)
private void Awake()
{
//向上层检查,赋值副控制器
if(transform.parent != null){//有父物体(控制器)
controller = transform.GetComponentInParent<UIController>();
//将自身添加到副控制器的字典中
if(controller != null){
controller.UIControlDic.Add(transform.name,this);
}
}
}
//更改文本,将常用方法封装在UIControl里
public void ChangeText(string str){
if(this.GetComponent<Text>() != null ){
this.GetComponent<Text>().text = str;
}
}
public void ChangeIntText(int change){
if(this.GetComponent<Text>() != null ){
this.GetComponent<Text>().text = change.ToString();
}
}
public void ChangeIntModeText(int change,string mode){
if(this.GetComponent<Text>() != null ){
this.GetComponent<Text>().text = change.ToString(mode);
}
}
//更改图片
public void ChangeImage(Sprite sprite){
if(this.GetComponent<Image>()!=null){
this.GetComponent<Image>().sprite = sprite;
}
}
//事件-点击按钮
public void AddButtonClickEvent(UnityAction action){
//Tip:UnityAction是一个委托(Delegate)类型
//委托:简单说可以说成是一个方法的别名,委托可以指向一个与其具有相同标签的方法
//如:定义一个委托 public delegate int MyDelegate (string s);
//这个委托可以引用一个具有相同参数(任何一个方法参数是一个string的方法)的方法
//声明委托:定义一个带有一个string参数的名为PrintName的方法,打印字符串。然后声明委托:
// MyDelegate my = new MyDelegate(PrintName);
//现在,使用my("Hello");=使用PrintName("Hello");
//这个action可以说是一个没有参数的委托声明(不严谨)
//有关UnityAction委托,可以自行上网
Button control = this.GetComponent<Button>();
if(control != null){
control.onClick.AddListener(action);//添加事件
}
}
//Slider
public void SliderEvent(UnityAction<float> action){
Slider control = this.GetComponent<Slider>();
if(control != null){
control.onValueChanged.AddListener(action);
}
}
//InputField(输入框)
public void AddInputFieldEvent(UnityAction<string> action){
InputField control = this.GetComponent<InputField>();
if(control != null){
control.onValueChanged.AddListener(action);
}
}
//...
}Unity
先创建Canvas,然后创建Button(旧版),取名MenuButton。将MenuButton的字调大,内容为菜单。
然后创建一个Panel,取名MainController。将MainController的大小调节到和Menu一样
然后创建一个Panel,取名MenuController。它的大小约为整张画布的六分之一(最后有图)
在MenuController下创建一些横条Button填满panel(如果填不满把Panel的Image的勾去掉),先创建StateButton(角色状态),
ItemButton(背包),SkillButton(技能),CloseButton(关闭页面)名字的Button。
现在,我们有两层UI界面
再添加一个Panel取名StateController,用来显示角色状态,将Panel的Image组件里的Background调为灰色。
可以在角色状态面板里写任何东西,实现过程这里不写。我的StateController:
最后,还要加上关闭按钮,直接复制即可(懒得传图了,关闭按钮不在图上)
接着复制StateController,做出背包和技能的页面,无需实现细节,只要能看出谁是谁就行。逻辑
将UIMagager赋给Canvas。
创建MainController脚本。MainController
挂载到MainController上。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainController : UIController
{
// Start is called before the first frame update
void Start()
{
//给菜单按钮添加事件
UIManager.Instance.GetComponent<UIManager>().GetUIControl(transform.name,"MenuButton").AddButtonClickEvent(ShowMenu);
}
//显示菜单
public void ShowMenu(){
//打印测试
Debug.Log("HELLO,UNITY!");
}
}
运行测试,成功报错!
问题出在UIController的Awake方法里。这时UIManager的单例还没有加载好,单例为空。因此检测不到Instance,
报出空异常。
此时,需要让UIManager抢先运行。
在unity中单击UIManager脚本,选择检查器上的Execution Order按钮,点一下+号,选UIManager,旁边的数值越小执行速度越快,这里填-99。
(Tip:填完之后一定要apply(应用)!)
之后还会有报错,显示添加了相同的键。修改UIController的Awake方法:为了便捷使用UIManager的单例,在UIController里创建Instance变量:1
2
3
4//将当前的页面控制器添加到Manager中
if(!UIManager.Instance.GetComponent<UIManager>().UIControllerDic.ContainsKey(transform.name)){
UIManager.Instance.GetComponent<UIManager>().UIControllerDic.Add(transform.name,this);
}现在,重新回到MainController:1
2
3
4public UIManager Instance;
private void Awake(){
Instance = UIManager.Instance.GetComponent<UIManager>();
}运行测试。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainController : UIController
{
// Start is called before the first frame update
void Start()
{
//给菜单按钮添加事件
UIManager.Instance.GetComponent<UIManager>().GetUIControl(transform.name,"MenuButton").AddButtonClickEvent(ShowMenu);
}
//显示菜单
public void ShowMenu(){
//关闭自己,显示菜单
Instance.SetActive("MenuController",true);
this.gameObject.SetActive(false);
}
}MenuController
把MainController关掉,把MenuController开启。
创建MenuController脚本,挂载到MenuController上。这样,基本的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
40using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MenuController : UIController
{
void Start()
{
//状态按钮事件
Instance.GetUIControl(transform.name,"StateButton").AddButtonClickEvent(ShowStateController);
//其他事件
Instance.GetUIControl(transform.name,"ItemButton").AddButtonClickEvent(ShowItemController);
Instance.GetUIControl(transform.name,"SkillButton").AddButtonClickEvent(ShowSkillController);
Instance.GetUIControl(transform.name,"CloseButton").AddButtonClickEvent(CloseController);
}
void Update()
{
}
//状态页面方法
void ShowStateController(){
Instance.SetActive("StateController",true);
gameObject.SetActive(false);
}
void ShowItemController(){
Instance.SetActive("ItemController",true);
gameObject.SetActive(false);
}
void ShowSkillController(){
Instance.SetActive("SkillController",true);
gameObject.SetActive(false);
}
void CloseController(){
Instance.SetActive("MainController",true);
gameObject.SetActive(false);
}
}StateController
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
public class StateController : UIController
{
// Start is called before the first frame update
void Start()
{
Instance.GetUIControl(transform.name,”CloseButton”).AddButtonClickEvent(CloseController);
}
// Update is called once per frame
void Update()
{
}
void CloseController(){
Instance.SetActive("MenuController",true);
gameObject.SetActive(false);
}
}
1 | ## ItemController |
SkillController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkillController : UIController
{
// Start is called before the first frame update
void Start()
{
Instance.GetUIControl(transform.name,”CloseButton”).AddButtonClickEvent(CloseController);
}
// Update is called once per frame
void Update()
{
}
void CloseController(){
Instance.SetActive("MenuController",true);
gameObject.SetActive(false);
}
}
还有一些逻辑,功能,这里就不去实现了。
运行,测试,结束。