Unity | Unity中UI框架的实现与使用

0 前言

作为初学者常用的UI设计方式以及与UI与游戏对象交互的方式是使用简单的对象拖拽,但是这样的方式耦合性很高,而且对于协作项目来说不太友好,所以使用一个单一的UI框架管理Resources中的UIPanel预制体,在需要时进行创建和销毁是很有必要的,其一可以降低UI与游戏程序之间的耦合性,提高UI的复用能力,其二可以优化游戏性能,在必要的时候才创建所需的UI组件,减少在开始时大量创建UI的性能开销。

1 程序结构

UIManager

  • UIManager.cs : Class
  • BasePanel.cs : Class
  • UIPanelInfo.cs : Class
  • UIPanelType.cs : Enum
  • UIPanel.json : Json

2 工具类:UIPanelInfo.cs

该类用于对Json文件进行反序列化,将Json文本中的路径和类型字段转换为对应的对象,利用了C#中对Json进行序列化操作的原生接口ISerializationCallbackReceiver的回调方法处理了Json对象。

using System;
using UnityEngine;

// 反序列化json文本,读取其中存储的Panel路径
[Serializable]
public class UIPanelInfo : ISerializationCallbackReceiver
{
    [NonSerialized] 
    public UIPanelType panelType;

    public string panelTypeString;
    public string path;

    //序列化
    public void OnBeforeSerialize()
    {
        
    }

    // 反序列化,从文本到对象
    public void OnAfterDeserialize()
    {
        var type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
        panelType = type;
    }
}

3 PanelType枚举类以及BasePanel类

PanelType类用于为单个Panel赋予唯一Key用来生成和调用Panel。

public enum UIPanelType
{
    PanelType,
    ...
}

BasePanel类是对所有Panel的声明周期进行了定义的基类,在其中我对GetOverUI和FindUIElem方法进行了封装,用来获得所需要的对象,其余方法是对Panel生命周期的虚函数声明,包括OnEnter(进入时)、OnPause(暂停时)、OnResume(继续时)、OnExit(退出时)、OnClose(关闭时)这五个生命周期,在继承其的基类中,可以override这几个虚函数,用来对不同生命周期中的Panel进行操作,同时强推一下CanvasGroup这个组件,对于Panel的效果实现有很大帮助。

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class BasePanel : MonoBehaviour
{
    private CanvasGroup canvasGroup;

    public CanvasGroup CanvasGroupValue
    {
        get
        {
            return canvasGroup;
        }
        set
        {
            canvasGroup = value;
        }
    }

    private void Awake()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }
    
    /// <summary>
    /// 显示UI时调用
    /// </summary>
    public virtual void OnEnter()
    {
        canvasGroup.alpha = 1;
    }

    public virtual void OnPause()
    {
        canvasGroup.blocksRaycasts = false;
    }

    public virtual void OnResume()
    {
        canvasGroup.blocksRaycasts = true;
    }

    public virtual void OnExit()
    {
        canvasGroup.alpha = 0;
    }

    public virtual void OnClose()
    {
        UIManager.Instance.PopPanel();
    }
    
    protected GameObject GetOverUI(GameObject canvas)
    {
        PointerEventData pointerEventData = new PointerEventData(EventSystem.current);
        pointerEventData.position = Input.mousePosition;
        GraphicRaycaster gr = canvas.GetComponent<GraphicRaycaster>();
        List<RaycastResult> results = new List<RaycastResult>();
        gr.Raycast(pointerEventData, results);
        if (results.Count != 0)
        {
            return results[0].gameObject;
        }
        return null;
    }
    
    protected GameObject FindUIElement(string elementName)
    {
        GameObject returnGameObject = null;
        foreach (var child in gameObject.GetComponentsInChildren<Transform>(true))
        {
            if (child.name.Equals(elementName))
            {
                returnGameObject = child.gameObject;
            }
        }
        return returnGameObject;
    }
}

4 UIManager类

UIManager类是该UI框架的主要逻辑类,这个类的数据结构由存放Json中path字段的字典、每个BasePanel组件的字典、以及一个存放了Panel对象的栈组成,同时因为UIManager的特性,在类设计上使用了单例模式。
构成该类的方法主要是对Panel栈的操作方法,包括PushPanel(入栈)和PopPanel(出栈)两个主要方法。
最后为该类设计了一个内部类用来获取Json中的path和panelType,这个类使用了JsonUtility来获得Json对象的信息。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.Video;

public class UIManager : MonoBehaviour
{
    private static UIManager _instance;

    public static UIManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new UIManager();
            }

            return _instance;
        }
    }

    private Transform _canvasTransform;

    private Transform CanvasTransform
    {
        get
        {
            if (_canvasTransform == null)
            {
                _canvasTransform = GameObject.Find("MainCanvas").transform;
            }

            return _canvasTransform;
        }
    }

    private Dictionary<UIPanelType, string> panelPathDictionary;
    private Dictionary<UIPanelType, BasePanel> panelDictionary;

    private Stack<BasePanel> panelStack;

    private UIManager()
    {
        ParseUIPanelTypeJson();
    }

    public void PushPanel(UIPanelType panelType)
    {
        if (panelStack == null)
        {
            panelStack = new Stack<BasePanel>();
        }

        if (panelStack.Count > 0)
        {
            BasePanel topPanel = panelStack.Peek();
            topPanel.OnPause();
        }

        BasePanel panel = GetPanel(panelType);
        panel.OnEnter();
        panelStack.Push(panel);
    }

    public void PopPanel()
    {
        if (panelStack == null)
        {
            panelStack = new Stack<BasePanel>();
        }

        if (panelStack.Count <= 0)
        {
            return;
        }

        BasePanel basePanel = panelStack.Pop();
        basePanel.OnExit();

        if (panelStack.Count <= 0) return;
        BasePanel topPanel = panelStack.Peek();
        topPanel.OnResume();
    }

    private BasePanel GetPanel(UIPanelType panelType)
    {
        if (panelDictionary == null)
        {
            panelDictionary = new Dictionary<UIPanelType, BasePanel>();
        }

        panelDictionary.TryGetValue(panelType, out var panel);

        if (panel == null)
        {
            panelPathDictionary.TryGetValue(panelType, out var path);
            
            GameObject initPanel = Instantiate(Resources.Load(path)) as GameObject;
            initPanel.transform.SetParent(CanvasTransform, false);
            
            panelDictionary.Add(panelType, initPanel.GetComponent<BasePanel>());
            return initPanel.GetComponent<BasePanel>();
        }
        else
        {
            return panel;
        }
    }

    [SerializeField]
    class UIPanelTypeJson
    {
        public List<UIPanelInfo> infoList;
    }

    private void ParseUIPanelTypeJson()
    {
        panelPathDictionary = new Dictionary<UIPanelType, string>();

        TextAsset textAsset = Resources.Load<TextAsset>("Json/UIPanels");

        UIPanelTypeJson jsObject = JsonUtility.FromJson<UIPanelTypeJson>(textAsset.text);

        foreach (UIPanelInfo info in jsObject.infoList)
        {
            panelPathDictionary.Add(info.panelType, info.path);
        }
    }
}

5 UIPanelJson文件

用来存放Panel预制体路径和对应的panelType

{
  "infoList": [
    {
      "panelTypeString" : "MainMenuPanel",
      "path" : "UIPanel/MainMenuPanel"
    }
  ]
}

5 使用方法

  1. 在json文件中写入对应的Panel预制体路径和panelType
{
  "infoList": [
    {
      "panelTypeString" : "PanelType",
      "path" : "UIPanel/XXXPanel"
    }
  ]
}
  1. 在PanelType枚举类中写入Json文件中创建的PanelType
public enum UIPanelType
{
    PanelType
}
  1. 在方法中调用PushPanel方法创建panelTyep对应的Panel对象
UIManager.Instance.PushPanel(panelType);
  1. 修改XXXPanel类中的生命周期方法来实现面板效果(示例:一个简单的UI界面类)
using System;
using System.Collections;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;

public class UserInterfacePanel : BasePanel
{
    private int Health;

    private int sub = 0;

    private int ableBullet = 0;

    void Awake()
    {
        EventCenter.AddListener<int>(EventType.BulletRefresh, RefreshBullet);
        EventCenter.AddListener<int>(EventType.HealthRefresh, RefreshHealth);
    }

    private void Start()
    {
        StartCoroutine(CheckTheNumericValue());
    }

    void RefreshHealth(int health)
    {
        var text = transform.Find("Health").transform.Find("OnTimeHealth").GetComponent<Text>();
        text.text = health.ToString();
    }

    void RefreshBullet(int bulletContain)
    {
        ableBullet = bulletContain;
    }

    public override void OnEnter()
    {
        UIManager.Instance.PushPanel(UIPanelType.TipPanel);
        UIManager.Instance.PushPanel(UIPanelType.PackagePanel);
    }

    IEnumerator CheckTheNumericValue()
    {
        while (true)
        {
            yield return new WaitForSeconds(0.1f);
            var text = FindUIElement("BulletContain").GetComponent<Text>();
            if (Package.Instance.GetItem(ItemType._9mmAmmo) != null)
            {
                text.text = $"{ableBullet} / {Package.Instance.GetItem(ItemType._9mmAmmo).count}";
            }
        }
    }
}

6 结篇

框架对程序结构以及性能的优化是十分强大的,可以多用。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>