Unity实现基于UGUI的简易UI框架
什么是UI框架?
UI框架中的 UIManager
管理场景中所有的面板, 控制面板之间的跳转.
本Demo实现以下功能:
- 关闭当前页面
- 显示新的页面
- 叠加显示
- 关闭当前页面并显示
- 关闭所有页面并显示
- 安卓返回键响应
- 返回上一个页面
- 退出程序
设计UI页面
先搭建好所有的UI界面, 并保存为prefab
通过Json和枚举保存所有面板的信息
json: json用于描述资料结构,有两种结构存在:
- 对象 (object):一个对象包含一系列非排序的名称/值对(pair),一个对象以
{
开始,并以}
结束。每个名称/值对之间使用:
分区。 - 数组 (array):一个数组是一个值(value)的集合,一个数组以
[
开始,并以]
结束。数组成员之间使用,
分区。数组成员具体的格式如下: - 名称/值(pair):名称和值之间使用
:
隔开,一般的形式是:{name:value}
- 对象 (object):一个对象包含一系列非排序的名称/值对(pair),一个对象以
UIPanelType.json
: 保存这个工程所有的UI面板类型及其相应的prefab的路径, 此时主要该文件和prefab文件都需要位于Resources
文件夹路径下{ "infoList": [ {"panelTypeString":"ALERT_PANEL", "path":"AlertPanel"}, {"panelTypeString":"CONNECT_PANEL", "path":"ConnectPanel"}, {"panelTypeString":"HOME_PANEL", "path":"HomePanel"}, {"panelTypeString":"WELCOME_PANEL", "path":"WelcomePanel"} ] }
UIPanelType.cs
: 保存这个工程所有的UI面板类型public enum UIPanelType { ALERT_PANEL, CONNECT_PANEL, HOME_PANEL, WELCOME_PANEL }
开发UIManager解析面板信息Json
JsonUtility
的使用:
FromJson | Create an object from its JSON representation. |
---|---|
FromJsonOverwrite | Overwrite data in an object by reading from its JSON representation. |
ToJson | Generate a JSON representation of the public fields of an object. |
注意json中的key值和所对应的unity类中的字段名要保持一致
[Serializable]
public class UIPanelInformation : ISerializationCallbackReceiver {
[NonSerialized]
public UIPanelType panelType;
public string panelTypeString;
public string path;
public void OnAfterDeserialize() {
//反序列化之后, 将一个或多个枚举字符串表示(panelTypeString)转换成等效的枚举对象(UIPanelType)。
UIPanelType type = (UIPanelType)Enum.Parse(typeof(UIPanelType), panelTypeString);
panelType = type;
}
public void OnBeforeSerialize() {
//啥都不用做
}
}
[Serializable]
public class UIPanelTypeJson {
//用来储存json对象所对应的类的列表
public List<UIPanelInformation> infoList;
}
在UIManager.cs
中 :
private void ParseUIPanelTypeJson() {
mPanelPathDictionary = new Dictionary<UIPanelType, string>();
TextAsset textAsset = Resources.Load<TextAsset>("Demo1/UIPanelType");
//将json对象转化为UIPanelTypeJson类
UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(textAsset.text);
foreach (UIPanelInformation info in jsonObject.infoList) {
mPanelPathDictionary.Add(info.panelType, info.path);
}
}
开发BasePanel面板基类
为什么使用virtual
关键字而不是abstract
关键字来修饰?
- 使得子类可以选择是否需要重写方法
- //todo
控制UI面板Prefab的实例化创建和管理
mPanelPathDictionary
: 路径字典, 解析保存所有的面板信息(枚举类型和prefab路径)mPanelPool
: 实例对象池, 保存所有已实例化的面板mPanelStack
: 页面栈, 管理各个页面切换任务
控制面板之间的跳转
主要方法:
PushPanel(UIPanelType panelType)
: 新增一个页面, 此时当前页面需要被暂停PushPanel(UIPanelType panelType, bool isPopCurrentPanel)
: 新增一个页面, 此时当前页面或所有页面需要被关闭BackToLastPanel
: 如果mPanelStack.Count
为1, 则提示退出程序; 如果不为1, 则弹出当前页面, 并显示此时mPanelStack
栈顶页面
其它问题
如何添加新的页面?
- 制作prefab, 继承BasePanel
- 重写虚函数
- 添加枚举类型
- 添加json信息
如何控制一个面板不可交互 / 忽略点击事件?
- 了解一下:
CanvasGroup
组件, 属性有:
alpha | Set the alpha of the group.设置该组的透明度。 |
---|---|
blocksRaycasts | Does this group block raycasting (allow collision).该组是否忽略投射(允许碰撞)。 |
ignoreParentGroups | Should the group ignore parent groups?是否忽略父物体组? |
interactable | Is the group interactable (are the elements beneath the group enabled).是否打开交互(在该组之下启用该元素)。 |
CanvasGroup
的典型用途是:- 通过在GameObject上添加一个
CanvasGroup
并控制其Alpha
属性来实现整体淡入或淡出。 - 通过将父物体的
CanvasGroup
组件中Interactable属性设置为false,从而使子物体一组控件不可交互(“变灰”)。 - 通过将
CanvasGroup
组件放置在元素或其父项之一上并将其BlockBlockcast
属性设置为false,使一个或多个UI元素不会阻挡鼠标事件(即射线可穿透它)。
- 通过在GameObject上添加一个
参考解决方案:
组件要求:
- 相关代码为:
[RequireComponent(typeof(CanvasGroup))] public class BasePanel : MonoBehaviour { protected CanvasGroup canvasGroup; protected virtual void Awake() { canvasGroup = gameObject.GetComponent<CanvasGroup>(); //其它代码 } private void SetPanelInteractable(bool isInteractable) { if (isInteractable ^ canvasGroup.interactable) canvasGroup.interactable = isInteractable; } //其它代码 }
在游戏运行中如何使得新创建的面板显示在最上面?如何修改UGUI的显示层级?
了解一下: UGUI物体的渲染顺序
SetAsFirstSibling
:移动到所有兄弟节点的第一个位置(Hierarchy同级最上面,先渲染,显示在最下面)SetAsLastSibling
:移动到所有兄弟节点的最后一个位置(Hierarchy同级最下面,后渲染,显示在最上面)GetSiblingIndex
:获得该元素在当前兄弟节点层级的位置SetSiblingIndex
:设置该元素在当前兄弟节点层级的位置
参考解决方案:
gameObject.transform.SetAsLastSibling();
留意Hierarchy窗口
页面重新出现的时候, Awake
和Start
函数内的数据刷新不了?
了解一下Unity脚本生命周期:
测试一下, 在
Awake
,OnEnable
,Start
,OnDisable
,OnEnter
,OnExit
函数里面加上Debug.Log
输出, 结果如图:
AlertPanel.cs
的函数调用顺序为:
可见, Awake
, Start
函数只调用一次, 因此, 每次显示和隐藏页面时的数据操作,可以通过OnEnter
, OnExit
这两个方法来实现
参考解决方案:
- 在
OnEnter
,OnExit
函数里面进行关于即时数据的操作; - 在
Awake
,Start
里面进行各种初始化操作
- 在
注意:
- prefab的面板要设置可见, 不然会有奇怪的事情发生!
如果需要切换场景呢?
切换时当前场景内的物体都会被清空, 但我们可以通过DontDestroyOnLoad
来保留UIManager
单例, 同时将 mPanelPool
和mPanelStack
清空就好了
/// <summary>
/// 实例化UIManager
/// </summary>
/// <returns></returns>
private static UIManager GetInstance() {
if (sInstanceUiManager == null) {
var go = new GameObject("UIManager");
sInstanceUiManager = go.AddComponent<UIManager>();
//如果有场景切换
DontDestroyOnLoad(go);
}
return sInstanceUiManager;
}
/// <summary>
/// 切换场景前,调用该方法来清空当前场景的数据
/// </summary>
public void RefreshDataOnSwitchScene() {
mPanelPathDictionary.Clear();
mPanelStack.Clear();
}
如何切换场景?
UIManager.Instance.RefreshDataOnSwitchScene();
SceneManager.LoadScene("NewScene");