bit-ui:基于 FairyGUI 的窗口管理,装饰器一顿标注就能用

它解决什么问题

用 FairyGUI 做 UI 的项目,早晚都要自己写一套窗口管理:打开窗口要不要先关掉上一个、UI 资源什么时候加载什么时候释放、多个窗口叠在一起谁在最上层、切场景的时候一堆没关掉的窗口怎么清理干净。这些逻辑不是难,是琐碎,而且每个项目都要重新写一遍,还容易漏处理某个边界情况(比如窗口没显示完就被销毁,资源引用计数对不上)。

bit-ui 把这套窗口生命周期管理做成了现成的框架,你只要按装饰器的规则写窗口类,剩下资源加载、层级、多窗口组的事交给它。

安装

bit-core@gongxh/fairygui-cc 是 peer 依赖,要一起装,保证全项目只有一份实例:

npm install @gongxh/bit-ui @gongxh/bit-core @gongxh/fairygui-cc

核心用法

定义一个窗口

窗口类继承 Window,用装饰器注册:

import { Window, _uidecorator } from '@gongxh/bit-ui';
const { uiclass, uiprop, uiclick } = _uidecorator;

@uiclass("Home", "MainWindow", "MainWindow")
export class MainWindow extends Window {
    @uiprop private btnStart: GButton;
    @uiprop private txtTitle: GTextField;

    protected onShow(userdata?: any): void {
        this.txtTitle.text = userdata?.title ?? '默认标题';
    }

    @uiclick
    private onBtnStart(): void {
        console.log('开始游戏');
    }
}

uiclass 的三个参数分别是窗口组名称、FairyGUI 包名、组件名——组件名必须和类名完全一致,这是它能自动关联 FairyGUI 资源的关键,写错了会找不到对应组件。

打开/关闭窗口

import { WindowManager } from '@gongxh/bit-ui';

// 参数是窗口类本身,不是字符串名称
await WindowManager.showWindow(MainWindow, { title: '欢迎回来' });

WindowManager.closeWindow(MainWindow);

showWindow 是异步的,因为它内部要先把这个窗口对应的 FairyGUI 包加载进来,加载完了才创建、显示。如果打开窗口前还需要额外请求服务器数据(比如打开背包前先拉一次背包列表接口),可以用 beforeLoad,这段逻辑会在 UI 包加载前先执行完:

await WindowManager.showWindow(BagWindow, userdata, {
    beforeLoad: async () => {
        userdata.bagList = await requestBagList();
    },
});

beforeLoad 执行期间会复用你配置的通用等待窗,不用自己再单独弹一个 loading。

窗口之间的关系

打开新窗口时,WindowType 决定怎么处理原来在屏幕上的窗口:

  • Normal —— 不做任何处理,叠在上面
  • CloseOne / HideOne —— 关闭/隐藏上一个窗口
  • CloseAll / HideAll —— 关闭/隐藏所有窗口

比如从主界面进副本界面,通常想把主界面隐藏而不是关闭(回来的时候能直接恢复状态),这时候用 HideOne,副本界面关闭后主界面会自动走 onShowFromHide() 生命周期恢复回来。

Header 复用

如果好几个窗口顶部都是同一条资源栏(金币、钻石、体力),没必要每个窗口都重复放一份,用 Header 基类单独定义一次,跨窗口复用:

import { Header, _uidecorator } from '@gongxh/bit-ui';
const { uiheader } = _uidecorator;

@uiheader("Common", "TopHeader")
export class TopHeader extends Header {
    protected onShow(userdata?: any): void {
        // 刷新金币、钻石显示
    }
}

适配类型

窗口用 AdapterType 决定怎么适配屏幕:

  • Full —— 全屏适配(默认,大部分窗口用这个)
  • Bang —— 按 Screen(来自 bit-core)算出的安全区避让,窗口定位在安全区中心,适合异形屏上不想被摄像头/挖孔挡住的窗口
  • Fixed —— 固定尺寸不适配,适合弹窗、提示框这类

避坑提醒

  • uiclass 第三个参数(组件名)必须和 TypeScript 类名一致,这是隐性约定,不遵守的话运行时会提示找不到组件,排查起来容易懵。
  • showWindow 传的是窗口类本身(构造函数),不是窗口名字符串,跟很多其他 UI 框架的习惯不一样,容易写错。
  • HideOne/HideAll 隐藏的窗口不会自动释放资源,只有 closeWindow 才会走完整的生命周期和资源回收,长期隐藏不用的窗口记得该关就关。
  • 配套的可视化编辑器 kunpo-fgui 是付费插件,能一键导出 FairyGUI 配置省去手写绑定代码,不是必须,但界面多的项目用它能省不少事。

项目信息

需要红点、功能解锁提示这类跟 UI 联动的条件显示逻辑,可以配合 bit-condition 一起用。