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


为了方便大家使用,本着严谨的态度,在这里对原因进行分析,并且给出修改版的代码。
UGUI的界面是用Canvas(画布)进行布局的,对于不同Canvas,至少有两种办法可以调整其渲染的先后顺序:
使用Canvas属性中的Sort Order,数字越大,渲染优先级越高。
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,
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下。
比如,这样:

具体代码我就不写了,反正思路是这么个思路,欢迎交流探讨~
源码:
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";
}
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);
}
}
}