【系列文章目录】
文章目录
前言
本篇我们来创建FGUI的窗口管理类
一、显示普通窗口
1.使用PuerTS创建一个FGUI窗口
FGUI文档中有关于窗口系统的描述【FGUI文档——窗口系统】
按照文档中的说法
我们在创建一个高层级的组件的时候
不应该直接将组件放在GRoot下(FGUI的根节点)
而是应该先创建窗口再放置这些组件
首先我们创建一个UI管理器,并为他添加以下方法
public static async ShowWnd(pkg: string,name: string) {
//异步加载FGUI包资源
await $promise(API.LoadFairyGUIPackage(pkg));
//创建窗口
let win: FairyGUI.Window = new FairyGUI.Window();
//创建窗口内容
win.contentPane = FairyGUI.UIPackage.CreateObject(pkg, name).asCom;
//全屏显示
win.MakeFullScreen();
//显示窗口
win.Show();
}
使用上面的方法我们就可以创建一个窗口并将组件作为窗口中的内容放在窗口中显示
这样我们通过窗口的API可以更容易的控制界面
2.拓展窗口基类
我们可以使用FGUI的窗口可以作为基类,进行拓展
export interface IGwndController extends IController {
//窗口的拓展接口
}
export abstract class AbstractGWndController extends FairyGUI.Window implements IGwndController{
//窗口的拓展实现
}
这样我们就拓展好了FGUI的窗口
3.创建拓展窗口
在游戏中,我希望每个窗口都有自己的类
而上面的抽象类只作为一个模板存在
export class WndLogin extends AbstractGWndController{
//具体窗口的实现
}
这样我们就拥有了实际窗口类,他们与FGUI编辑器中我们所设计的界面一一对应
所以我们需要将他们的对于关系存储起来
先思考一个我们需要一个什么样的结构
- key:用于创建窗口时索引窗口信息的值,唯一
- pkgName:组件所在包名
- itemId:组件名
- script:窗口类
- windName:窗口名
这样我们就先定义了一个接口类型
export interface IUIInfo {
pkgName: string
itemId: string
script: any
windName: string
}
然后我们建了一个类专门用于管理窗口信息与组件拓展信息
export class UITable {
public static UIWindow = {
WndGameMain: {
pkgName: "GameMain", itemId: "WndGameMain", script: WndGameMain, windName: "WndGameMain" },
WndLogin: {
pkgName: "Login", itemId: "WndLogin", script: WndLogin, windName: "WndLogin" },
}
}
这样我们在需要创建窗口的时候只需要填入窗口名,就可以把窗口创建出来了,修改以下窗口创建函数
public static async ShowWnd(windName: string) {
let info: IUIInfo = UITable.UIWindow[windName]
//判断是否存在窗口信息
if (info != null) {
let pkg = info.pkgName
let name = info.itemId
let scr = info.script
let wndName = info.windName
//异步加载FGUI包资源
await $promise(API.LoadFairyGUIPackage(pkg))
//创建窗口
let win: AbstractGWndController = new scr();
win.name = wndName;
//创建窗口内容
win.contentPane = FairyGUI.UIPackage.CreateObject(pkg, name).asCom;
win.MakeFullScreen();
//绑定子组件(这个方法自行拓展)
win.BindChild(win);
FairyGUI.GRoot.inst.ShowWindow(win);
}
else {
TSLog.Error("WindowName:【" + windName + "】 does not exist or is misspelled,Please check the spelling");
}
}
这样我们就可以通过我们自己的UI管理器创建出一个我们自己拓展的窗口了
二、显示弹出窗口
接下来我们来做弹出窗口即点击空白区域会自动关闭的窗口
首先,不要总想着自己一个人造车,先翻一下FGUI文档吧
于是我在FGUI的文档中找到了【FGUI文档——Popup】的内容
按照文档中的描述,十分的简单
显示普通窗口我们用的是
窗口类中Show()
的方法
而显示弹出窗口我们只需要把需要这个效果的窗口作为参数传入
FairyGUI.GRoot.inst.ShowPopup()
这个方法就可以了
由于创建窗口的部分与普通窗口时公用的,所以我将它分离出来
//有窗口返回窗口,无窗口创建窗口
private async GetWnd(windName: string): Promise<AbstractGWndController> {
let info: IUIInfo = UITable.UIWindow[windName]
if (info == null) {
TSLog.Error("WindowName:【" + windName + "】 does not exist or is misspelled,Please check the spelling");
return null;
}
try {
if (this.HasWnd(windName)) {
return this.Map_WndInstance.get(windName);
}
else {
let pkg = info.pkgName
let name = info.itemId
let scr = info.script
let wndName = info.windName
//异步加载FGUI包资源
await $promise(API.LoadFairyGUIPackage(pkg))
//创建窗口
let win: AbstractGWndController = new scr();
win.name = wndName;
//创建窗口内容
win.contentPane = FairyGUI.UIPackage.CreateObject(pkg, name).asCom;
win.MakeFullScreen();
//绑定代码
win.fui = win.contentPane;
win.BindAll(win);
//存储窗口对象
this.Map_WndInstance.set(windName, win);
return win;
}
}
catch {
TSLog.Error("WindowName:【" + windName + "】 throw error when create");
}
return null;
}
将显示普通窗口的方法修改一下
public async ShowWnd(windName: string, modal: boolean = true) {
let wnd: AbstractGWndController = await this.GetWnd(windName);
if (wnd == null) {
TSLog.Error("WindowName:【" + windName + "】 can not get this window");
}
wnd.modal = modal;
FairyGUI.GRoot.inst.ShowWindow(wnd);
}
然后接下来只需要写一个弹出窗口的方法就可以了
public async ShowPopWnd(windName: string, modal: boolean = true) {
let wnd: AbstractGWndController = await this.GetWnd(windName);
if (wnd == null) {
TSLog.Error("WindowName:【" + windName + "】 can not get this window");
}
LinxLog.Log("PopWnd")
wnd.modal = modal;
FairyGUI.GRoot.inst.ShowPopup(wnd);
}
似乎这样就可以了
然而在测试的时候我发现好像并不能点击空白处关闭窗口
我的组件时这样的
研究了一下发现,我需要把组件属性中的点击穿透开起来才可以
而在这过程中,我还发现了FGUI为我们做好了黑遮的效果
只需要在窗口中将modal这个属性设为true就可以了
这个功能一样要把组件中的点击穿透开启才会生效
这样才一个窗口内的空白区域,会被modal遮挡,阻止穿透到其他的窗口
三、管理窗口的创建、显示、隐藏、销毁
按照上述方式来创建显示窗口
我们会使用一个Map来存储已经存在的窗口
public Map_WndInstance: Map<string, AbstractGWndController> = new Map<string, AbstractGWndController>();
这样我们做隐藏和销毁就很容易了
//隐藏窗口
public HideWnd(windName: string) {
if (this.HasWnd(windName)) {
let wnd = this.Map_WndInstance.get(windName);
FairyGUI.GRoot.inst.HideWindow(wnd);
}
}
//隐藏弹窗
public HidePopWnd(windName: string) {
if (this.HasWnd(windName)) {
let wnd = this.Map_WndInstance.get(windName);
FairyGUI.GRoot.inst.HidePopup(wnd);
}
}
//销毁窗口
public DisposeWnd(windName: string) {
if (this.HasWnd(windName)) {
let wnd = this.Map_WndInstance.get(windName);
wnd.Dispose();
this.Map_WndInstance.delete(windName);
}
}
四、在WebGL与微信小游戏中
由于Unity导出的WebGL中是不支持异步的
所以如果有导出WebGL或微信小游戏的计划
那就不要使用异步方法,而是用回调的方式来做
可以参考下面的方式:
//第一步、外部调用显示窗口
public ShowWnd(windName: string, modal: boolean = true): void {
//获取配置
let info: IUIInfo = UITable.UIWindow[windName]
//查询不到配置抛出错误
if (info == null) {
TSLog.Error("WindowName:【" + windName + "】 does not exist or is misspelled,Please check the spelling");
return;
}
//构造窗口数据
let wnddata: UIWndData = {
windName: windName, cfg: info, modal: modal }
//通往第二步,获取窗口并指定最终步骤
this.GetWnd(wnddata, this.DoShowWnd.bind(this));
}
//内部调用,有窗口返回窗口,无窗口创建窗口
private GetWnd(info: UIWndData, call: Function): void {
try {
//判断是否已存在窗口
if (this.HasWnd(info.windName)) {
if (call != null) {
//窗口已存在通往最终步骤
call(info, this.Map_WndInstance.get(info.windName))
};
}
else {
let pkg = info.cfg.pkgName
//加载FGUI包资源,加载完成通完第三步
API.LoadFairyGUIPackage(pkg, this.LoadPackComplete.bind(this, info, call))
}
}
catch (error) {
TSLog.Error("WindowName:【" + info.windName + "】 throw error when create");
TSLog.Error("error:" + error);
}
}
//第三步、加载完成回调
private LoadPackComplete(info: UIWndData, call: Function): void {
let pkg = info.cfg.pkgName
let name = info.cfg.itemId
let scr = info.cfg.script
let wndName = info.windName
//创建窗口
let win: AbstractGWndController = new scr();
win.name = wndName;
//创建窗口内容
win.contentPane = FairyGUI.UIPackage.CreateObject(pkg, name).asCom;
win.MakeFullScreen();
//绑定代码
win.fui = win.contentPane;
win.BindAll(win);
//存储窗口对象
this.Map_WndInstance.set(info.windName, win);
if (call != null) {
//通往最终步骤
call(info, win)
};
}
//最终步骤、加载完成回调,显示窗口
private DoShowWnd(info: UIWndData, wnd: AbstractGWndController) {
let wndName = info.windName
let modal = info.modal
if (wnd == null) {
TSLog.Error("WindowName:【" + wndName + "】 can not get this window");
}
wnd.modal = modal;
FairyGUI.GRoot.inst.ShowWindow(wnd);
}
这样在微信小游戏中就可以正常的运行了
这里之所以不用匿名函数,是因为使用匿名函数会造成GC的问题
所以使用了命名函数,虽然写起来麻烦而且很长
对比之下,异步的方式,真是清爽