Unity--UI framework

First explain that the function of the UI framework is to control the mutual jump between UI panels. After using the UI framework, the biggest use is to avoid complicated operations when switching pages. Using the UI framework can better manage UI pages , the display and closing of the control page are also controlled by only one function, which greatly optimizes the code

First look at a UI frame diagram

 Combined with the above picture, start to make UI framework step by step

1. First make each panel of the UI separately, and then put it in the Resources file as a prefab (the purpose of this is to get all the panels directly when loading (the specific implementation is through json parsing in the dictionary In), the specific implementation is below)

2. Create an enumeration type UIPanelType to save the corresponding panel

Create a json file of UIPanelType to save the panel and its corresponding path. Note that the value of the panelType here must be consistent with the value in the enumeration type, so that the parsing can be successful later (it needs to be loaded through the Resources file, so put it in In the Resources file (you can create another Resources file))

 3. Create the UIManager class, which has three functions,

 The first function: parsing and saving panel information (by parsing the json file, and then storing it in the dictionary, the key is the UIType of the enumeration, and the value is the path) (note that when parsing the json, a data class is needed to receive it, so create a UIPanelInfo class to receive json analysis data) Note that the UIPanelInfo class here is the corresponding [ {} , {}], but it can be seen that this structure is parsed as an array or list, but unity parsing can only parse out the class ( Starting from version 0.9.0, LitJSON supports the serialization of List objects into JSON strings, without the need to create more classes later ), so this structure needs to be changed to { [ {}, {}, {} ] }, and the array Put it in {}, that is, another class UIPanelTypeJson is needed to represent the outermost {}. Note that when parsing json, you should use string to enumeration type (because it cannot be directly parsed into enumeration). For details, see String and Enumeration conversion (currently function iteration only uses uimanager), after reading the first function, see step 4

The content of the Json file also changes to this

The second function: Create and save an instance of the panel (through the dictionary, the key is the enumeration of the panel, and the value is the BasePanel component of the panel (the parent class can be obtained through the subclass object, and BasePanel is used to represent the page)), and the creation method ( GetPanel ( ) ) Obtain the parent class of the panel through panel enumeration (there is a method to extend the dictionary when obtaining it, please refer to the extension of Dictionary)

The third function: manage and save all displayed panels (the displayed pages are saved by means of a stack, and all displayed pages are displayed in the stack, the top of the stack is the page that can be operated, that is, the page that can be clicked, and the bottom of the stack The page is only displayed and cannot be operated), so there are operations of creating a stack, popping and pushing (these two are the most important, because the display and closing of all pages rely on this popping and pushing) operations (and the following steps 5 status added)

4. Create a common base class of panels, BasePanel, so that all panels inherit from BasePanel, because the script of each panel inherits from BasePanel (the panel class here is created by itself), so that after instantiating each panel , you can directly instantiate the object through the panel. GetComponent<BasePanel>(), so you can add the second function of the UIManager class

These are the panels, created according to your own project, and attach the panel class code at the bottom:

5. There are four states for each page: OnEnter (the page is displayed), OnPause (the page is paused because other pages popped up), OnResume (the page continues, other pages covered on this page are removed, and the page is restored. Interaction), OnExit (remove the page, remove the display of the page) for details, see the flow chart. Note that because each page has these four states, these four states are defined in BasePanel, through the virtual method, and then let Each subclass rewrite, which can refer to the backpack panel, involves these four states (if you want to add display and exit animations, then add them in OnEnter and OnExit)

6. Add click events for each button on each panel (in the script inherited from BasePanel). For example, I now have a main menu, the script is MainPanel, and there are tasks, backpacks, and other buttons on the main menu. At this time The UI framework comes into play. You can directly display the page through the push method in UIManager in the button click event, and close the page with the pop method. Remember that you also need to pass the enumeration type of the panel (the click event cannot pass the enumeration type here. parameters of the enumeration type, so the step of converting the string to the enumeration type is still required), like this

7. It is the GameRoot part, which is used to start the UI framework

 The code part is attached below, in the order introduced above

UIPanelType class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum UIPanelType
{
    ItemMessage,
    Knapsack,
    MainMenu,
    Shop,
    Skill,
    System,
    Task

}

The json file of UIPanelType:

{

  "infoList": [
 
    {
      "panelTypeString": "ItemMessage",
      "path": "UIPanel/ItemMessagePanel"
    },
    {
      "panelTypeString": "Knapsack",
      "path": "UIPanel/KnapsackPanel"
    },
    {
      "panelTypeString": "MainMenu",
      "path": "UIPanel/MainMenuPanel"
    },
    {
      "panelTypeString": "Shop",
      "path": "UIPanel/ShopPanel"
    },
    {
      "panelTypeString": "Skill",
      "path": "UIPanel/SkillPanel"
    },
    {
      "panelTypeString": "System",
      "path": "UIPanel/SystemPanel"
    },
    {
      "panelTypeString": "Task",
      "path": "UIPanel/TaskPanel"
    }
    ]
}

UIManager class (the core content of the framework)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager 
{
    #region 第一部分功能
    private Dictionary<UIPanelType, string> panelPathDict;//面板信息字典,用来存放面板所对应的Prefab路径
    private static UIManager _instance;
    public static UIManager Instance//单例模式不是这章重点,不做讲解
    {
        get 
        {
            if(_instance == null)
            {
                _instance = new UIManager();
            }
            return _instance; 
        }

    }

    private UIManager()
    {
        ParseUIPanelTypeJson();
    }

    [Serializable]
    class UIPanelTypeJson
    {
        public List<UIPanelInfo> infoList;
    }
    private void ParseUIPanelTypeJson()//解析json文件
    {
        panelPathDict=new Dictionary<UIPanelType, string>();
        TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//json文件在unity中是属于TextAsset类,因此用这个类来接收
        //这里应该也可以用txt文件保存json数据,然后用读取流的形式读取
        UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//注意,如果这样的话就会报错,原因在解析json的时候,并不能解析成枚举类型,因为在UIPanelInfo类的panelType成员为枚举类型,因此就会报解析错误
        //将解析的json信息,填入panelPathDict字典中,键对应面板枚举值,值对应面板路径
        foreach (UIPanelInfo info in jsonObject.infoList)
        {
            panelPathDict.Add(info.panelType, info.path);

        }
    }
    #endregion

    #region 第二部分功能
    private Dictionary<UIPanelType, BasePanel> panelDict;//保存所有实例化面板的游戏物体身上的脚本组件(继承自BasePanel的)
    private Transform canvasTransform;//获取面板
    public Transform CanvasTransform
    {
        get
        {
            if(canvasTransform == null)
            {
                canvasTransform = GameObject.Find("Canvas").transform;
            }
            return canvasTransform;
        }
    }
    private BasePanel GetPanel(UIPanelType panelType)//根据面板的枚举类型,得到实例化的面板
    {
        if (panelDict == null)//判断字典是否为空
        {
            panelDict = new Dictionary<UIPanelType, BasePanel>();//如果为空就创建字典
        }
        // BasePanel panel;
        //panelDict.TryGetValue(panelType, out panel);
        BasePanel panel = panelDict.TryGet(panelType);//字典扩展方法
        if (panel == null)//判断是否得到该面板
        {
            //如果为空就根据panelPathDict字典中路径实例化面板,并保存在panelDict中和放在Canvas画布下
            //string path;
            //  panelPathDict.TryGetValue(panelType, out path);
            string path = panelPathDict.TryGet(panelType);

            //GameObject instPanelPrefab = Resources.Load<GameObject>(path);//这是通过动态加载的形式获取到了面板预制件的引用
            //GameObject instPanel=GameObject.Instantiate(instPanelPrefab);//因为没有继承自MonoBehaviour,所有需要加GameObject
            GameObject instPanel = GameObject.Instantiate(Resources.Load<GameObject>(path));//这是简洁写法
            panelDict.Add(panelType, instPanel.GetComponent<BasePanel>());
            instPanel.transform.SetParent(CanvasTransform, false);//放在画布下
            return instPanel.GetComponent<BasePanel>();
        }
        else
        {
            return panel;
        }
    }
    #endregion

    #region 第三部分功能
    private Stack<BasePanel> panelStack;
    //都是根据页面的枚举类型来获取到页面,才能方便出栈入栈操作
    public void PushPanel(UIPanelType panelType )//把页面入栈,把某个页面显示在界面上
    {
        if(panelStack == null)
        {
            panelStack = new Stack<BasePanel>();
        }
        //判断栈里面是否有页面
        if(panelStack.Count > 0 )
        {
            BasePanel topPanel=panelStack.Peek();//这是获取到栈顶元素Pop是出栈
            topPanel.OnPause();//将栈顶元素暂停
        }
        BasePanel panel=GetPanel(panelType);//这里就用到了第二部分功能中的获取面板的方法
        panelStack.Push(panel);//入栈
        panel.OnEnter();//这是新的栈顶元素显示
        //注意这里都只是统一调用BasePanel中的方法,因为BasePanel中的方法是虚方法没有具体实现,需要在子类中具体实现后,就能有具体的功能
    }
    public void PopPanel()//把页面出栈, 把页面从界面上移除
    {

        if (panelStack == null)
        {
            panelStack = new Stack<BasePanel>();
        }
        if (panelStack.Count <= 0)
        {
            return;
        }
        BasePanel topPanel=panelStack.Pop();//栈顶出栈
        topPanel.OnExit();
        if (panelStack.Count <= 0)//这是如果关闭页面后下方还有页面就需要将栈顶页面恢复
        {
            return;
        }
        BasePanel topPanel2 = panelStack.Peek();
        topPanel2.OnResume();//这里同理,具体实现都是在子类脚本中重新,这里只负责调用
    }
    #endregion
}

 UIPanelInfo class:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class UIPanelInfo:ISerializationCallbackReceiver
{
    [NonSerialized]//因为json解析成不了枚举类型,因此就不解析成它
    public UIPanelType panelType;//注意,因为这个类时用来接收解析的json的,因此变量成员名一定要和json中的名字要相同

    public string panelTypeString;//json解析成字符串

    public string path;

    public void OnAfterDeserialize()//反序列化成功之后调用
    {
        // throw new NotImplementedException();
        //这里就是将字符串转化为枚举类型的过程,放在反序列化成功之后执行
        UIPanelType Type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
        panelType = Type;
    }

    public void OnBeforeSerialize()//反序列化成功之前调用
    {
      //  throw new NotImplementedException();
    }
}

DictionaryExtension class: dictionary extension class

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class DictionaryExtension //字典扩展类,对字典使用方法进行扩展
{
   
    public static Tvalue TryGet<Tkey,Tvalue>(this Dictionary<Tkey,Tvalue> dict,Tkey key)
    {
        Tvalue value;
        dict.TryGetValue(key, out value);
        return value;
    }
}

BasePanel class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasePanel : MonoBehaviour
{
    /// <summary>
    /// 界面被显示出来
    /// </summary>
    public virtual void OnEnter()
    {

    }

    /// <summary>
    /// 界面暂停
    /// </summary>
    public virtual void OnPause()
    {

    }

    /// <summary>
    /// 界面继续
    /// </summary>
    public virtual void OnResume()
    {

    }

    /// <summary>
    /// 界面不显示,退出这个界面,界面被关系
    /// </summary>
    public virtual void OnExit()
    {

    }
}

GameRoot class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameRoot : MonoBehaviour
{
    private void Start()
    {
        UIManager.Instance.PushPanel(UIPanelType.MainMenu);
    }
}

A class that displays a part of the project panel (inherited from BasePanel), not part of the framework

The main menu class (mounted on the main menu page), the click event method, such as clicking the task button in the main menu to display the task page, you need to write your own function and bind it to the button, here is the OnClickPushPanel() function

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainMenuPanel : BasePanel
{
    private CanvasGroup canvasGroup;
    private void Start()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }
    //这里是重新的MainMenuPanel页面枚举类型对应的BasePanel中的OnPause方法,因为在UIManager中的panelDict保存了枚举类型对应的BasePanel
    //因此这里的调用流程就是先调用下方的PushPanel,在PushPanel函数中根据panelType获取到BasePanel,然后执行 panel.OnPause();等方法,又因为
    //获取到的BasePanel就是 MainMenuPanel的父类这个 BasePanel,因此调用的panel.OnPause();方法就是执行的下方的内容
    public override void OnPause()//这里具体的暂停方法就是取消掉主页面中的所有可交互的东西,因此可以用CanvasGroup控制,详细使用方法见手册
    {
       canvasGroup.blocksRaycasts = false;//暂停该页面,让鼠标不再和该页面交互
    }
    public override void OnResume()
    {
        canvasGroup.blocksRaycasts = true;
    }
    public void OnClickPushPanel(string panelString )//定义点击事件,将要显示的页面添加进栈中,这里是因为要复用才通过字符串形式转换
    {
        UIPanelType panelType=(UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelString);
        UIManager.Instance.PushPanel( panelType );
    }
}

Another example is the backpack class:

using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class KnapsackPanel : BasePanel
{
    private CanvasGroup canvasGroup;
    private void Start()
    {
        if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();
    }
    public override void OnEnter()
    {
        if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();//这里是防止面板在实例化出来后,立马调用了OnEnter()方法,导致 canvasGroup还没赋值而报空
        canvasGroup.alpha = 1;
        canvasGroup.blocksRaycasts = true;
        // gameObject.SetActive(true); //这里也可以直接可以设置页面的active来表示进入和退出

        Vector3 temp = transform.localPosition;
        temp.x = 1300;
        transform.localPosition = temp;
        transform.DOLocalMoveX(0, 0.5f);
    }
    public override void OnExit()//关闭该页面的具体实现内容
    {
        //canvasGroup.alpha = 0;
        canvasGroup.blocksRaycasts = false;
        transform.DOLocalMoveX(1800, 0.5f).OnComplete(() => { canvasGroup.alpha = 0; });
        // gameObject.SetActive(false);
    }
    public override void OnPause()
    {
        canvasGroup.blocksRaycasts = false;
    }
    public override void OnResume()
    {
        canvasGroup.blocksRaycasts = true;
    }
    public void OnClosePanel()
    {
        UIManager.Instance.PopPanel();
    }
    public void OnItemButtonClick()//点击显示物品按钮
    {
        UIManager.Instance.PushPanel(UIPanelType.ItemMessage);
    }
}

So to sum up, there are only 7 classes belonging to the UI framework

Other classes like KnapsackPanel change according to project changes

Guess you like

Origin blog.csdn.net/qq_62947569/article/details/130773177