UI系统UIManager

背景

  本项目的UI系统采用了经典的 MVC(Model-View-Controller)架构,目的是将逻辑控制与界面展示进行分离,方便维护和拓展。每个具体的窗口界面由控制器controller、视图view、和模型model组成,界面的MVC使用和规范详见UI界面MVC模板。UIManager负责统一管理和调度界面,确保多个界面间的层次关系、打开顺序、栈管理等功能能够顺利执行。
  此外,框架还引入并配置好了XLua框架,C#层通过UIManager与Lua层交互,允许开发者使用Lua进行UI界面开发,配合AssetManager进行热更新操作,基于模块化开发原则,XLua框架也可以很方便地做到即插即用。

功能介绍

  1. UI层级管理
    UI界面分为多个层级,框架预设了三个主要层级:
    • SceneLayer: 场景相关的UI层,通常用于场景内特定UI。
    • NormalLayer: 普通界面层,一般游戏内操作界面如菜单、背包等。
    • TopLayer: 顶层UI层,通常用于提示框、弹窗等。
  2. UI栈管理
    每个层级对应一个 栈stack,以确保层级内的界面按照栈的顺序进行打开和关闭。举一个简单的例子,背包界面中打开了道具合成界面,栈底是背包界面,栈底往上是道具合成界面,当直接关闭背包界面时,需要将道具合成界面也一起关闭,逻辑上就是一直弹栈直到弹出背包界面。
  3. 缓存与重用
    UIManager 会缓存所有已创建的界面控制器,避免重复创建。同时也支持在需要时销毁特定的界面,通过 DestroyWindow() 释放内存。

使用

UI文件管理

所有界面的预制体都存放于aa资源包中,通过AssetManager进行加载:

UI界面的MVC代码,存放于文件夹中,以界面名命名。
UI代码文件夹路径:项目路径\Assets\Scripts\UI\UILogic

UI界面预制体

  每一个UI界面都是一个Canvas,所以配置表中需要声明 Canvas 的层级SortOrder,需要与层级layer区分开,层级负责管理界面栈,sortOrder负责控制实际前后遮挡,原则上不同层级layer应该控制一段范围的SortOrder,以符合基本认知。

UI配置表

  在 UI.xlsx 中配置每一个界面的预制体的资源文件路径、界面名id、层级、sortOrder等必要参数:

打开界面

  此方法将首先检查该界面是否已打开,如果未打开则创建并显示该界面,并将其压入对应层级的栈中。框架目前只封装了同步打开界面的方法,可能会卡顿,后续会实现异步打开界面

1
2
3
// 允许打开界面时传入可变参数,可变参数将会转交给界面对应的controller.OpenRoot()和.OnInit()
// UIManager.Instance.OpenWindow(界面名id, 可变参数params object[] param);
UIManager.Instance.OpenWindow("HomeView", 10); // 打开UI.xlsx中的HomeView数据项

此方法不会将界面压入对应层级的栈中,关闭界面时需要与CloseWindowWithStack() 一起使用。

1
UIManager.Instance.OpenWindowWithoutStack("HomeView");

关闭界面

关闭压栈的界面,此方法会弹出此界面之后打开的同层级的界面,即一直弹栈 直到弹出此界面。

1
2
// UIManager.Instance.CloseWindow(界面名id);
UIManager.Instance.CloseWindow("HomeView");

关闭压栈的界面,此方法仅将此界面弹出栈,不影响栈内其他界面。

1
UIManager.Instance.CloseWindowOnly("HomeView");

关闭非栈内的独立界面,即通过OpenWindowWithoutStack() 打开的界面。

1
UIManager.Instance.CloseWindowWithoutStack("HomeView");

获取界面控制器

此方法获取controller时,若未打开次界面时会自动打开界面

1
2
// UIManager.Instance.GetCtrl<界面controller类T>(界面名id,可变参数);
var homeCtrl = UIManager.Instance.GetCtrl<PauseCtrl>("HomeView", 10);

此方法获取controller时,若未打开此界面,则返回null

1
2
// UIManager.Instance.GetCtrlWithoutCreate<界面controller类T>(界面名id);
var homeCtrl = UIManager.Instance.GetCtrlWithoutCreate<PauseCtrl>("HomeView");

其他API

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UIManager
{
// 获取UI界面根节点,root -> layer -> 具体UI界面
public Transform GetUIRoot();
// windowName界面是否处于打开状态
public bool CheckViewActiveInHierarchy(string windowName);
// 关闭指定层级的栈内的界面
public void CloseLayerWindows(string layerName)
// 关闭所有层级的栈内的所有界面
public void CloseAllWindows()
// 销毁界面
public void DestroyWindow(string windowName)
}

注意事项

  • OpenWindowWithoutStack() 与 CloseWindowWithStack() 要成对使用,由于这样打开的界面不交由栈进行管理,容易出现找不到界面的问题,通常情况下也不应该使用这种打开方式。主要用于一些常驻的界面,如时间显示界面、状态显示界面等。
  • 动静界面需要区分开,动态界面变化一次都会引起一次DrawCall,需要将静态界面单独作为一个canvas,一个canvas一个DrawCall,确保静态界面不会引起DrawCall,优化UI开销。