UI框架bug修复版
打工人小棋
编辑于 2023年09月01日 06:32
收录于文集
共3篇

上一次分享完UI框架之后,就经常有同学来问我问题,其中出现问题最多的就是打开某个界面时,显示:界面已打开。

为了方便大家使用,本着严谨的态度,在这里对原因进行分析,并且给出修改版的代码。

UGUI的界面是用Canvas(画布)进行布局的,对于不同Canvas,至少有两种办法可以调整其渲染的先后顺序:

  1. 使用Canvas属性中的Sort Order,数字越大,渲染优先级越高。

  2. Canvas在Hierarchy中的顺序,越下方的渲染优先级越高。

1. Sort Order

2. 先后顺序

按照这个思路,后打开的界面一定是叠加在之前界面的上层。

所以,当我打开:UserPanel,一切正常,他叠加在 MainMenuPanel 之上:

紧接着,我再打开:NewUserPanel,就不对劲了。

UserPanel不见了

把MainMenuPanel隐藏掉,发现UserPanel跑到下方去了,很明显,此处UGUI的渲染层级并没有按照第二条规则进行:

2. Canvas在Hierarchy中的顺序,越下方的渲染优先级越高。

具体原因嘛,我还没找到,如果有同学知道,可以分享下,不是Unity出bug就是我出bug了。

解决方案:

既然第二种方法排序错乱,那么我们使用第一种方法不就好了:

1. 使用Canvas属性中的Sort Order,数字越大,渲染优先级越高。

核心代码就是在UIManager中记录当前最大的sortingLayer,

代码块
C#
自动换行
复制代码
public class BaseUIManager
{
    public int sortingOrder = 0;
    public BasePanel OpenPanel(string name)
    {
        BasePanel panel = null;
        // 检查是否已打开
        // ...

        // 检查路径是否配置
        // ...

        // 使用缓存预制件
        // ...
      
        // 打开界面
        // ...
        Canvas panelCavas = panelObject.GetComponent<Canvas>();
        panelCavas.sortingOrder = sortingOrder;
        sortingOrder += 1;
        return panel;
    }
}
复制成功

这样就确保每次新打开的界面永远在最上层了。


当然这只是提出一个思路,对于更复杂的项目还要考虑如下问题: 1. sortingOrder会不会超出int的最大值

int的最大值为:21474836473,差不多打开过两百多亿个界面就超过了,嗯,相信你可以的!!!

实在不放心的话,可以在切场景的时候,重置sortingOrder为0就好了。

2. 有些界面我希望他始终位于最上层怎么办:

这里提供下思路,可以对UI再分几个大层,比如:

Canvas1.sortingOrder = 1

Canvas2.sortingOrder = 2

Canvas3.sortingOrder = 2

然后默认打开的界面都放在Canvas1下,需要处于比较高层级的就放在Canvas2下。

比如,这样:

具体代码我就不写了,反正思路是这么个思路,欢迎交流探讨~


源码:

代码块
C#
自动换行
复制代码
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
public class BaseUIManager
{
    private static BaseUIManager _instance;
    private Transform _uiRoot;
    // 路径配置字典
    private Dictionary<string, string> pathDict;
    // 预制件缓存字典
    private Dictionary<string, GameObject> prefabDict;
    // 已打开界面的缓存字典
    public Dictionary<string, BasePanel> panelDict;
    public int sortingOrder = 0;

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

    public Transform UIRoot
    {
        get
        {
            if (_uiRoot == null)
            {
                if (GameObject.Find("Canvas"))
                {
                    _uiRoot = GameObject.Find("Canvas").transform;
                }
                else
                {
                    _uiRoot = new GameObject("Canvas").transform;
                }
            };
            return _uiRoot;
        }
    }

    private BaseUIManager()
    {
        InitDicts();
    }

    private void InitDicts()
    {
        prefabDict = new Dictionary<string, GameObject>();
        panelDict = new Dictionary<string, BasePanel>();

        pathDict = new Dictionary<string, string>()
        {
            {UIConst.MainMenuPanel, "Menu/MainMenuPanel"},
            {UIConst.UserPanel, "Menu/UserPanel"},
            {UIConst.NewUserPanel, "Menu/NewUserPanel"},
        };
    }

    public BasePanel OpenPanel(string name)
    {
        BasePanel panel = null;
        // 检查是否已打开
        if (panelDict.TryGetValue(name, out panel))
        {
            Debug.Log("界面已打开: " + name);
            return null;
        }

        // 检查路径是否配置
        string path = "";
        if (!pathDict.TryGetValue(name, out path))
        {
            Debug.Log("界面名称错误,或未配置路径: " + name);
            return null;
        }

        // 使用缓存预制件
        GameObject panelPrefab = null;
        if (!prefabDict.TryGetValue(name, out panelPrefab))
        {
            string realPath = "Prefab/Panel/" + path;
            panelPrefab = Resources.Load<GameObject>(realPath) as GameObject;
            prefabDict.Add(name, panelPrefab);
        }

        // 打开界面
        GameObject panelObject = GameObject.Instantiate(panelPrefab, UIRoot, false);
        panel = panelObject.GetComponent<BasePanel>();
        panelDict.Add(name, panel);
        panel.OpenPanel(name);
        Canvas panelCavas = panelObject.GetComponent<Canvas>();
        panelCavas.sortingOrder = sortingOrder;
        sortingOrder += 1;
        return panel;
    }

    public bool ClosePanel(string name)
    {
        BasePanel panel = null;
        if (!panelDict.TryGetValue(name, out panel))
        {
            Debug.Log("界面未打开: " + name);
            return false;
        }

        panel.ClosePanel();
        // panelDict.Remove(name);
        return true;
    }

}

public class UIConst
{
    // menu panels
    public const string MainMenuPanel = "MainMenuPanel";
    public const string UserPanel = "UserPanel";
    public const string NewUserPanel = "NewUserPanel";
}
复制成功

代码块
C#
自动换行
复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasePanel : MonoBehaviour
{
    protected bool isRemove = false;
    protected new string name;

    protected virtual void Awake()
    {
    }

    public virtual void SetActive(bool active)
    {
        gameObject.SetActive(active);
    }

    public virtual void OpenPanel(string name)
    {
        this.name = name;
        SetActive(true);
    }

    public virtual void ClosePanel()
    {
        isRemove = true;
        SetActive(false);
        Destroy(gameObject);
        if (BaseUIManager.Instance.panelDict.ContainsKey(name))
        {
            BaseUIManager.Instance.panelDict.Remove(name);
        }
    }
}

复制成功