Unity — фреймворк пользовательского интерфейса

Сначала объясните, что функция платформы пользовательского интерфейса заключается в управлении взаимным переходом между панелями пользовательского интерфейса.После использования платформы пользовательского интерфейса самое большое использование заключается в том, чтобы избежать сложных операций при переключении страниц.Использование структуры пользовательского интерфейса может лучше управлять страницами пользовательского интерфейса, отображением и закрытие страницы управления также контролируется только одной функцией, что значительно оптимизирует код

Первый взгляд на диаграмму фрейма пользовательского интерфейса

 В сочетании с приведенным выше рисунком начните шаг за шагом создавать структуру пользовательского интерфейса.

1. Сначала сделать каждую панель UI отдельно, а потом положить в файл Resources как префаб (цель этого - получить все панели сразу при загрузке (конкретная реализация - через парсинг json в словаре In), конкретная реализация ниже)

2. Создайте тип перечисления UIPanelType, чтобы сохранить соответствующую панель

Создайте json-файл UIPanelType для сохранения панели и соответствующего пути.Обратите внимание, что значение panelType здесь должно совпадать со значением в типе перечисления, чтобы парсинг мог быть успешным позже (его нужно загрузить через файл ресурсов, поэтому поместите его в файл ресурсов (можно создать другой файл ресурсов))

 3. Создайте класс UIManager, который имеет три функции:

 Первая функция: парсинг и сохранение информации о панели (путем парсинга json-файла, а затем сохранения его в словаре, ключом является UIType перечисления, а значением — путь) (обратите внимание, что при парсинге json данные класс необходим для его получения, поэтому создайте класс UIPanelInfo для получения данных анализа json) Обратите внимание, что класс UIPanelInfo здесь является соответствующим [ {} , {}], но видно, что эта структура анализируется как массив или список , но синтаксический анализ единства может только разобрать класс ( начиная с версии 0.9.0, LitJSON поддерживает сериализацию объектов List в строки JSON без необходимости создания дополнительных классов позже ), поэтому эту структуру необходимо изменить на { [ { }, {}, {} ] }, а массив Поместите его в {}, то есть для представления самого внешнего {} нужен другой класс UIPanelTypeJson . Обратите внимание, что при разборе json следует использовать строку для типа перечисления (потому что это не может быть напрямую преобразован в перечисление).Подробности см. в разделе Преобразование строк и перечислений (в настоящее время итерация функций использует только uimanager), после прочтения первой функции см. шаг 4

Содержимое файла Json также меняется на это

Вторая функция: Создать и сохранить экземпляр панели (через словарь, ключ - перечисление панели, а значение - компонент BasePanel панели (родительский класс можно получить через объект подкласса, а BasePanel используется для представления страницы)), и метод создания ( GetPanel ( ) ) Получить родительский класс панели через перечисление панелей (есть метод расширения словаря при его получении, пожалуйста, обратитесь к расширению Dictionary)

Третья функция: управление и сохранение всех отображаемых панелей (отображаемые страницы сохраняются с помощью стека, а все отображаемые страницы отображаются в стеке, вершиной стека является страница, с которой можно работать, т. е. страница что можно нажать, а дно стека Страница только отображается и не может управляться), поэтому есть операции создания стека, выталкивания и выталкивания (эти две самые важные, т.к. отображение и закрытие всех страниц полагаться на эти операции выталкивания и нажатия) (и добавлены следующие шаги 5 статуса)

4. Создайте общий базовый класс панелей BasePanel, чтобы все панели наследовались от BasePanel, потому что скрипт каждой панели наследуется от BasePanel (класс панели здесь создается сам), чтобы после инстанцирования каждой панели можно было напрямую создать экземпляр объекта через панель.GetComponent<BasePanel>(), чтобы можно было добавить вторую функцию класса UIManager

Это панели, созданные по вашему собственному проекту, внизу прикрепите код класса панели:

5. Для каждой страницы есть четыре состояния: OnEnter (страница отображается), OnPause (страница приостановлена, потому что появились другие страницы), OnResume (страница продолжается, другие страницы, затронутые на этой странице, удаляются, а страница восстановлена. Взаимодействие), OnExit (удалить страницу, удалить отображение страницы) для получения подробной информации см. блок-схему. Обратите внимание, что, поскольку каждая страница имеет эти четыре состояния, эти четыре состояния определяются в BasePanel с помощью виртуального метода и затем пусть каждый подкласс rewrite, который может ссылаться на панель рюкзака, включает в себя эти четыре состояния (если вы хотите добавить анимации отображения и выхода, то добавьте их в OnEnter и OnExit)

6. Добавить события клика для каждой кнопки на каждой панели (в скрипте унаследованном от BasePanel) Например, у меня сейчас главное меню, скрипт MainPanel, а в главном меню есть задачи, рюкзаки и другие кнопки. В это время в игру вступает инфраструктура пользовательского интерфейса. Вы можете напрямую отображать страницу с помощью метода push в UIManager в событии нажатия кнопки и закрывать страницу с помощью метода pop. Помните, что вам также необходимо передать тип перечисления панели (событие клика не может передать здесь тип перечисления. параметры типа перечисления, поэтому шаг преобразования строки в тип перечисления все же требуется), вот так

7. Это часть GameRoot, которая используется для запуска фреймворка пользовательского интерфейса.

 Часть кода прикреплена ниже, в порядке, указанном выше

Класс UIPanelType:

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

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

}

JSON-файл 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 (основное содержимое фреймворка)

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:

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: класс расширения словаря

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:

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:

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

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

Класс, отображающий часть панели проекта (унаследованную от BasePanel), а не часть фреймворка

Класс главного меню (монтируется на странице главного меню), метод события клика, например нажатие кнопки задачи в главном меню для отображения страницы задачи, нужно написать свою функцию и привязать ее к кнопке, вот функция OnClickPushPanel()

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

Другой пример — класс рюкзака:

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

Подводя итог, можно сказать, что фреймворку пользовательского интерфейса принадлежит всего 7 классов.

Другие классы, такие как KnapsackPanel, изменяются в соответствии с изменениями проекта.

рекомендация

отblog.csdn.net/qq_62947569/article/details/130773177