Unity笔记-UI框架

pi2Q1v6.png
这是一个普通的消息框架
其中,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
    29
    using 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
    代码:
    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()
    {

    }
    }
    然后回头修改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
    29
    30
    31
    32
    33
    34
    using 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;
    }
    }

    完成UIManager

    UIController

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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>();
    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
    74
    using 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。
    piRFVZq.png
    现在,我们有两层UI界面
    再添加一个Panel取名StateController,用来显示角色状态,将Panel的Image组件里的Background调为灰色。
    可以在角色状态面板里写任何东西,实现过程这里不写。我的StateController:
    piRkwNV.png
    最后,还要加上关闭按钮,直接复制即可(懒得传图了,关闭按钮不在图上)
    接着复制StateController,做出背包和技能的页面,无需实现细节,只要能看出谁是谁就行。

    逻辑

    将UIMagager赋给Canvas。
    创建MainController脚本。

    MainController

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    using 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!");
    }


    }
    挂载到MainController上。
    运行测试,成功报错!
    问题出在UIController的Awake方法里。这时UIManager的单例还没有加载好,单例为空。因此检测不到Instance,
    报出空异常。
    此时,需要让UIManager抢先运行。
    在unity中单击UIManager脚本,选择检查器上的Execution Order按钮,点一下+号,选UIManager,旁边的数值越小执行速度越快,这里填-99。
    (Tip:填完之后一定要apply(应用)!)
    之后还会有报错,显示添加了相同的键。修改UIController的Awake方法:
    1
    2
    3
    4
    //将当前的页面控制器添加到Manager中
    if(!UIManager.Instance.GetComponent<UIManager>().UIControllerDic.ContainsKey(transform.name)){
    UIManager.Instance.GetComponent<UIManager>().UIControllerDic.Add(transform.name,this);
    }
    为了便捷使用UIManager的单例,在UIController里创建Instance变量:
    1
    2
    3
    4
    public UIManager Instance;
    private void Awake(){
    Instance = UIManager.Instance.GetComponent<UIManager>();
    }
    现在,重新回到MainController:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    using 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);
    }


    }
    运行测试。把MainController关掉,把MenuController开启。
    创建MenuController脚本,挂载到MenuController上。
    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
    using 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);
    }
    }
    这样,基本的UI就完成了。

    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
## ItemController
```csharp
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;

public class ItemController : 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);
}
}

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);
}

}

还有一些逻辑,功能,这里就不去实现了。
运行,测试,结束。