V7投稿 | CocosCreator开源框架oops-framework 之 界面管理(四)

引擎: CocosCreator 3.8.0

环境: Mac

Gitee: oops-game-kit


引言


oops-framework是由作者dgflash编写,基于CocosCreator 3.x而实现的开源框架。

该框架以插件形式存在,主要目的是为了降低与项目的耦合,并且通过插件内部的命令快速的获取最新版本。

该框架的特性有:

  • 提供游戏常用的功能库,提高开发效率
  • 提供业务模块代码模版,降低程序设计难度
  • 内置模块低耦合,可根据需要自行删减,以适应不同的类型
  • 提供了常用的插件工具,支持Excel表转Json、支持热更新、AB包
  • 增加了ECS、MVVM框架相关,以及常用的屏幕适配,UI管理,多语言等等

为了方便大家更好的学习和使用该框架,作者很贴心的准备了一些学习资料:

dgflash-哔哩视频

dgflash CSDN博客

dgflash-cocos论坛

Gitee dgflash项目仓库

注:oops-framework框架QQ群: 628575875

在CocosCreator官方商店,可以通过 oops 搜索更多的框架项目Demo进行学习。

1_2


界面管理


框架的UI界面主要在场景的gui节点中,该节点被LayerManager模块管理。框架初始化的主要逻辑有:

// ../extensions/oops-plugin-framework/assets/core/Roots.ts
export class Root extends Component {
  @property({type: Node, tooltip: "界面层"})
  gui: Node = null!;
  
  protected init() {
    oops.gui = new LayerManager(this.gui);
    this.initGui();
  }
}

// assets/script/Main.ts
import { UIConfigData } from './game/common/config/GameUIConfig';
export class Main extends Root {
  protected initGui() {
    // 初始化UI界面配置数据进行初始化
    oops.gui.init(UIConfigData);
  }
}

在实际的应用中,对于页面的打开、隐藏我们只要编写:

// 打开新页面
oops.gui.open(UIID.GAME_LOGIN);
// 关闭指定页面
oops.gui.remove(UIID.Loading);

oops.gui的调用入口在 Oop.ts

export class oops {
    static gui: LayerManager;
}

UIID配置信息在GameUIConfig.ts中,该文件主要用于配置UI页面的数据相关

// GameUIConfig.ts
export enum UIID {
    Loading = 1,
    Window,
    Netinstable,
}

export var UIConfigData: { [key: number]: UIConfig } = {
  [UIID.Loading]: { layer: LayerType.UI, prefab: "common/prefab/loading"},
  [UIID.Netinstable]: { layer: LayerType.PopUp, prefab: "common/prefab/netinstable" },
  [UIID.Window]: { layer: LayerType.Dialog, prefab: "common/prefab/window" },
}

// LayerManager.ts
export interface UIConfig {
    bundle?: string;		/** 远程包名 */
    layer: LayerType;		/** 窗口层级 */
    prefab: string;			/** 预制资源相对路径 */
}

简单的理解页面的构建就是:

  1. 构建预制体页面UI
  2. 打开GameUIConfig.ts 文件,配置页面枚举类型,配置页面类型,资源路径
  3. 调用oops.gui.open(UIID)

LayerManager


LayerManager 主要用于管理不同的UI页面,构造函数实现如下:

constructor(root: Node) {
  this.root = root;
  this.camera = this.root.getComponentInChildren(Camera)!;
  // 不同界面类型构建节点,然后顺序添加到根节点中
  this.game = this.create_node(LayerType.Game);
  this.ui = new LayerUI(LayerType.UI);
  this.popup = new LayerPopUp(LayerType.PopUp);
  this.dialog = new LayerDialog(LayerType.Dialog);
  this.system = new LayerDialog(LayerType.System);
  this.notify = new LayerNotify(LayerType.Notify);
  this.guide = this.create_node(LayerType.Guide);
  // 注意下层级, LayerType.Game的最低,LayerType.Guide的最高
  root.addChild(this.game);
  root.addChild(this.ui);
  root.addChild(this.popup);
  root.addChild(this.dialog);
  root.addChild(this.system);
  root.addChild(this.notify);
  root.addChild(this.guide);
}

private create_node(name: string) {
  var node = new Node(name);
  node.layer = Layers.Enum.UI_2D;
  // 添加widget组件,设置上下左右对齐和对齐模式
  var w: Widget = node.addComponent(Widget);
  w.isAlignLeft = w.isAlignRight = w.isAlignTop = w.isAlignBottom = true;
  w.left = w.right = w.top = w.bottom = 0;
  w.alignMode = 2;
  w.enabled = true;
  return node;
}

主要页面的类型有:

类型 说明
Game 游戏层,比如地图逻辑处理
UI 主界面层,比如地图上方的菜单页面
PopUp 弹窗层, 窗口显示后,支持非窗口区域点击,可显示多个不同配置的弹窗
Dialog 模式窗口层,窗口显示后,非窗口区域不可透点
System 系统窗口层,与Dialog类似,可用于显示系统信息的弹窗错误提示
Notify 提示信息层, Tip信息显示,显示以后会上移消失
Guide 新手引导层,用于新手的强制引导

页面的继承结构:

4_2

常用的参数或接口:

参数或接口 说明
root 获取界面根节点
camera 获取界面摄像机
game 获取游戏界面根节点
guide 获取新手引导
uiMap 获取界面地图
setUIMap() 界面地图配置数据
toast() Tip提示显示,支持是否显示多语言
open() 根据uiId,同步打开某个页面
openAsync() 根据uiId,异步打开某个页面
has() 根据uiId, 检测是否存在某个页面
remove() 根据uiId,移除某个页面或窗口
removeByNode() 根据this框架添加的节点,移除某个页面或窗口
clear() 清除所有窗口

通过 oops.gui.open 打开某个窗口,看下框架的逻辑实现:

/*
@func: 同步打开一个窗口
@param: uiId 窗口唯一标识符ID
@param: uiArgs 页面参数,可以通过回调对象的onAdded或onRemoved回调获取
@param: callbacks 回调对象
*/
open(uiId: number, uiArgs: any = null, callbacks?: UICallbacks): void {
  var config = this.configs[uiId];
  if (config == null) {
    warn(`打开编号为【${uiId}】的界面失败,配置信息不存在`);
    return;
  }

  // 根据不同的界面类型打开不同窗口的显示逻辑
  switch (config.layer) {
    case LayerType.UI:
      this.ui.add(config, uiArgs, callbacks);
      break;
    case LayerType.PopUp:
      this.popup.add(config, uiArgs, callbacks);
      break;
    case LayerType.Dialog:
      this.dialog.add(config, uiArgs, callbacks);
      break;
    case LayerType.System:
      this.system.add(config, uiArgs, callbacks);
      break;
  }
}

页面的打开是支持页面参数传递的,新页面可以通过 UICallbacks 的回调调用

// ../oops-plugin-framework/assets/core/gui/layer/Defines.ts
export interface UICallbacks {
  // 节点添加到层级以后的回调,参数为当前页面节点,传递参数
  onAdded?: (node: Node, params: any) => void,
  // 窗口节点destroy之后回调,参数为当前页面节点,传递参数
  onRemoved?: (node: Node | null, params: any) => void,
  // 页面在移除的时候,进行的调用,可用于隐藏动画的显示,参数为当前页面节点,回调
  // 注意:如果调用`this.node.destroy()`,该回调将直接忽略
  onBeforeRemove?: (node: Node, next: Function) => void
}

注:新页面的打开传递参数可调用onAdded方法


常用示例


打开关闭页面

oops.gui.open(UIID.UI_MAIN);

// 关闭方式1: 通过UIID移除窗口,默认释放
oops.gui.remove(UIID.Loading);

// 方式2: 通过this.node释放窗口,默认保留对象
oops.gui.open(UIID.UI_MAIN);
oops.gui.removeByNode(this.node, true);

打开新页面传递参数

let params = {
  data: "oops"
}
oops.gui.open(UIID.MAIN, param);

// MainLayer.ts
export class MainLayer extends Component {
  onAdded(params?: any) {
    if (!params) {
      return;
    }
    let data = params.data;
    // todo
  }
}

弹窗页面显示

弹窗的类型虽有PopUp,Dialog,System的几种类型,但他们是类似的:

  • PopUp 打开以后,支持非窗口区域透点,支持打开多个不同配置的弹窗
  • Dialog 仅支持显示一个,非窗口区域不可透点
  • SystemDialog类似,作者dgflash增加这个处理的原因主要是为了区别窗口提示的不同类型,比如客户端自身和服务器的提示,方便问题的定位。

增加一个窗口的UI预制体,如下图所示:
4_1

GameUIConfig.ts中增加配置后, 添加示例:

public openWindow(event, customData: string) {
  let params = {
    title: "窗口标题",
    content: "这是一段描述",
  }
  let callBack: UICallbacks = {
    onAdded: (node: Node, params: any) => {
      console.log("onAdded获取传递的参数:", params)
    },
    onRemoved:(node: Node | null, params: any) => {
      console.log("onRemoved获取传递的参数:", params)     
    }
  }
  oops.gui.open(UIID.UI_POPUP, params, callBack);
}

一般弹窗的出现是需要有显示或隐藏动画的,我们可以通过回调方法:

  • onAdded 增加显示动画
  • onBeforeRemove 增加隐藏动画

动画的显示可以通过tween缓动系统或CocosCreator的Animation组件进行添加:

public clickSystem(event, customData: string) {
    console.log(customData);
    let params = {
      title: `系统窗口`,
      content: "数据异常",
    }
    oops.gui.open(UIID.UI_SYSTEM, params, this.getPopCommonEffect());
}

// 弹窗动画
private getPopCommonEffect(callbacks?: PopViewParams) {
	let newCallbacks: PopViewParams = {
		// 节点添加动画
    onAdded: (node, params) => {
      node.setScale(0.1, 0.1, 0.1);
      tween(node)
        .to(0.2, { scale: new Vec3(1, 1, 1) })
        .start();
    },
    // 节点删除动画
    onBeforeRemove: (node, next) => {
      tween(node)
        .to(0.2, { scale: new Vec3(0.1, 0.1, 0.1) })
        .call(next)
        .start();
    },
	}

	if (callbacks) {
    if (callbacks && callbacks.onAdded) {
      let onAdded = callbacks.onAdded;
      callbacks.onAdded = (node: Node, params: any) => {
        onAdded(node, params);
        newCallbacks.onAdded(node, params);
      };
    }

    if (callbacks && callbacks.onBeforeRemove) {
      let onBeforeRemove = callbacks.onBeforeRemove;
      callbacks.onBeforeRemove = (node, params) => {
        onBeforeRemove(node, params);
        newCallbacks.onBeforeRemove(node, params);
      };
    }
    return callbacks;
  }
  return newCallbacks;
}

注:作者在oops-frameworkTipsManager.ts中增加了更多的窗口示例,推荐查看学习

Toast示例

提示内容的显示就相对简单了,主要代码如下:

private _tipIndex: number = 0;

public clickTip(event, customData: string) {
  this._tipIndex++;
  // 参数:内容,是否使用多语言默认false
  oops.gui.toast(`这是第${this._tipIndex}个提示`);
}

最后,祝大家学习生活工作愉快!