游戏开发实现简易实用的ui框架

游戏开发实现简易实用的ui框架

本文使用cocos引擎实现,框架代码本质上不依赖某一个引擎,稍作修改也能作为其他引擎的实现

1.1 UI管理框架的核心需求剖析

  1. 分层与类型管理
    • 对不同类型UI需要进行分层管理。
    • 不同层级的UI需要有不同的父节点,保证渲染顺序和交互优先级。
  2. 资源管理与预加载
    • 支持动态加载UI资源,并缓存已加载的资源。
    • 提供预加载功能,提前加载高频使用的UI。
  3. 实例化与复用
    • 管理UI实例的创建和销毁,避免重复创建。
    • 通过缓存机制复用UI实例,减少性能开销。
  4. 显示与隐藏管理
    • 提供便捷的API控制UI的显示与隐藏。
    • 能自动控制UI层级。
  5. 事件与参数传递
    • 支持通过参数初始化UI,方便UI组件根据业务逻辑动态变化。

1.2 探讨UI层级的问题

  在做UI管理的时候层级关系是一个常见的问题,特别是在动态加载时由于设计不当或忽视一些细节,从而导致层级混乱、显示错误或者UI交互问题。

  毛毛虫认为解决这个问题的一个方式就是进行UI分类管理,只要根据UI的类型对其层级加以区分,不同类型的UI展示具有不同的特性。

​   在本次的设计中将UI区分为四个UI层(全屏视图、弹框、常驻UI、提示视图)。

  • 全屏视图是优先级最低的UI,显示在所有视图之下,同一时间只会存在一个全屏视图。

  • 常驻UI通常是底部或顶部的UI多用于全屏视图的切换,优先级只比全屏视图高。

  • 弹框是游戏中最多的UI,具有较高的优先级,同时可以出现多个,越晚出现的弹框层级越高。

  • 提示视图是优先级最高的UI,提示视图不是指提示框,提示框应该被归类为弹框,常见的提示视图有加载界面、过场动画、网络故障提示等。多数情况提示视图不会同时出现多个,如果存在多个越晚出现的层级应当越高。

1.3 UI基类的设计和自定义生命周期的封装

基类模板:

export default abstract class BaseView extends cc.Component implements IBaseView{UIType: UI_TYPE;private _params: any;get params(){return this._params;}set params(v) {this._params = v;}onLoad(){//#region 公共事件//#endregionthis.OnMountEnd();}/**挂载后事件,作OnLoad使用 */protected abstract OnMountEnd();protected onEnable(): void {this.ShowUI(this.params);}abstract ShowUI(params: any);HideUI() {//#region 公共事件//#endregionthis.node.active = false;}
}
export abstract class FullView extends BaseView{UIType: UI_TYPE = UI_TYPE.FULL;
}
export abstract class PopView extends BaseView{UIType: UI_TYPE = UI_TYPE.POP;
}
export abstract class TipView extends BaseView{UIType: UI_TYPE = UI_TYPE.FULL;
}
export abstract class ResidentView extends BaseView{UIType: UI_TYPE = UI_TYPE.RESIDENT;
}
export enum UI_TYPE{/**全屏视图 */FULL,/**弹出视图 */POP,/**提示视图 */TIP,/**常驻视图 */RESIDENT 
}
interface IBaseView{UIType: UI_TYPE;/**显示UI事件 */ShowUI(params: any);
}

基类设计的特点与目的:

  1. 通用性
    • BaseView 提供了一个统一的结构,所有具体的 UI 类都继承自此类,IBaseView 给予 UI 类一些行为约束。
  2. 生命周期的封装
    • 使用 OnMountEnd 替代了 onLoad,将公共逻辑与具体实现分离。确保了在 onLoad 执行一些通用逻辑(如资源加载、事件注册)后,具体的子类可以通过 OnMountEnd 来完成特定的初始化工作。
    • ShowUI:UI 的初始化和展示逻辑,在 UI 被展示时调用,可以根据传入的参数动态初始化视图内容提升灵活性,可以理解为可以传参的激活事件。
    • HideUI:为视图的销毁提供一个统一的入口,避免直接调用 node.active = false 导致遗漏清理逻辑,为后续的扩展(如内存回收、动画关闭等)提供了统一的触发点;
  3. UI 类型的标记
    • 基于 UI_TYPE 枚举定义了四种 UI 类型,并通过抽象类(FullViewPopView 等)进行进一步的封装。
    • 子类通过继承对应的抽象类,自动绑定 UI 类型,减少重复代码。

1.4 UI管理器的封装

UI管理器模板:

import BaseView, { UI_TYPE } from "./BaseView";
const UIPath: string = "prefab/UI/";
export default class UIManager {private isInited: boolean = false;private static instance: UIManager = null;private FullParentNode: cc.Node = null;private PopParentNode: cc.Node = null;private TipParentNode: cc.Node = null;private ResidentParentNode: cc.Node = null;public static get inst() {if (this.instance == null) {this.instance = new UIManager();}return this.instance;}public initUIManager(fullParentNode: cc.Node,popParentNode: cc.Node,tipParentNode: cc.Node = null,residentParentNode: cc.Node = null) {this.FullParentNode = fullParentNode;this.PopParentNode = popParentNode;this.TipParentNode = tipParentNode;this.ResidentParentNode = residentParentNode;}private UIcache: { [key: string]: BaseView } = {};private onView: BaseView = null;public ShowUI(ViewClass: string, params?) {let view: BaseView = this.UIcache[ViewClass];if (view == null) {this.instacePrefab(ViewClass, true, params)return;}if (view.node.active) {console.error("请勿重复加载");return;}view.params = params;this.Mount(view);view.node.active = true;}public HideUI(ViewClass: string) {let view: BaseView = this.UIcache[ViewClass];view && view.HideUI();}public preloadUI(uiViews: string[]) {if (this.isInited) {return;}this.isInited = true;uiViews.forEach(uiView => this.instacePrefab(uiView));}private instacePrefab(UIName: string, isShow: boolean = false, params?: any) {if (this.UIcache[UIName]) {return;}cc.resources.load(UIPath + UIName, cc.Prefab, (err, assert) => {if (err) {console.error(`没有${UIName}页面,路径信息:${UIPath + UIName}`);return;}let uiNode: cc.Node = cc.instantiate(assert as cc.Prefab);let uiView: BaseView = uiNode.getComponent(BaseView);if (uiView == null) {console.error(`${UIName}没有挂载脚本,路径信息:${UIPath + UIName}`);return;}this.UIcache[assert.name] = uiView;uiView.params = params;this.Mount(uiView);uiNode.active = isShow;})}private Mount(view: BaseView) {switch (view.UIType) {case UI_TYPE.FULL:this.onView && this.onView.HideUI();this.onView = view;view.node.parent = this.FullParentNode;break;case UI_TYPE.POP:view.node.setSiblingIndex(999);view.node.parent = this.PopParentNode;break;case UI_TYPE.TIP:view.node.setSiblingIndex(999);view.node.parent = this.TipParentNode;break;case UI_TYPE.RESIDENT:view.node.parent = this.ResidentParentNode;break;}}public hideResidentNode() {this.ResidentParentNode.children.forEach(nod => nod.active = false);}public HideAll() {Object.keys(this.UIcache).forEach(key => {let view: BaseView = this.UIcache[key];if (view.node.active) {view.HideUI();}})}public getView(ViewClass: new () => BaseView): BaseView {let view: BaseView = this.UIcache[ViewClass.name];if (view == null) {console.error(`页面${ViewClass.name}不存在`);return null;}return view;}
}

UIManager 是整个项目的 UI 管理框架核心模块,主要用于加载、显示、隐藏以及管理 UI 层级,通过分层管理和缓存机制使 UI 管理更为结构化、高效化;

  1. 单例模式

    • get inst通过单例模式实现全局唯一实例,避免多个实例造成管理混乱。但大型项目最好是实现一个 IOC 容器管理,避免实例滥用;
  2. UI显示管理

    • ShowUI方法用于控制 UI 的激活,记录 UI 视图的初始化参数,
    • 节点生命周期onLoad的运行时机在第一次挂载时,而节点实例如果默认是激活状态会出现自动执行onEnable,从而导致参数未记录就执行 UI 视图的ShowUI事件,故参数记录应当在挂载之前,当然也可以通过手动将节点设置为非激活状态;
    • 如果有需求在节点挂载前执行一些逻辑也可以添加在下面代码挂载前事件标记的位置;
    //#region 挂载前事件
    //#endregion
    view.params = params;
    this.Mount(view);
    view.node.active = true;
    
  3. UI挂载管理

    • Mount方法控制节点的挂载,使不同类型的 UI 视图判断自己的逻辑,如全屏视图需要关闭上一个全屏视图并记录,弹出视图需要修改节点索引;
  4. 预加载

    • preloadUI方法实现 UI 的预加载,可以将常使用的视图提前加载优化 UI 速度;
  5. 其他

    • 通常情况下不建议使用getView获取并持有视图脚本,一切行为应该趋向于中心管理;
    • UI 的隐藏事件尽可能的使用 UI 类的HideUI方法,而非 UIManager 的HideUI方法;
    • UI 视图名称可以使用枚举记录而并未字符串,根据不同的语言特性部分语言可以直接使用class类,因为 cocos 构建后类名会被编译成简单的字母不能和预制体统一名称,故该案例使用的字符串;

1.5 使用示例

  1. 实现示例
@ccclass
export default class AMoudleView extends PopView {protected OnMountEnd() {//进行节点获取、适配、事件注册等操作}ShowUI(params: {`具体属性`}) {//UI初始化逻辑}HideUI(): void {//在此实现私有隐藏事件,非特殊需求尽量不要写在 super.HideUI() 之后super.HideUI();}
}
  1. UI 调用逻辑
//通过 UI 的中心管理器 UIManager 显示视图
UIManager.inst.ShowUI(ViewName.AModuleView, { `具体参数` });

2.1 结语

对于微型项目毛毛虫不建议专门搭建一个框架,一方面框架的搭建无可避免的容易增加代码量,另一方面框架的设计需要对生命周期有较深理解否则容易出现时序性的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/886933.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用可视化工具kafkatool连接docker的kafka集群,查看消息内容和offset

1、下载kafkatool 下载地址Offset Explorer,下载对应系统的offset explorer 下载完,傻瓜安装即可(建议放D盘),在开始菜单输入offset找到该应用打开 打开 2、连接kafka 点击File > add new connection Bootstrap…

关于Java使用ueditor上传图片的一些总结

1.如何配置ueditor让上传的图片到项目之外? 因为图片上传到web项目中,重新部署项目可能会丢失图片。 解决方法:下载ueditor.1.1.2.jar. 地址:ueditor-1.1.2项目源码及jar包.zip 链接: https://pan.baidu.com/s/1Bhumfw8OX16n0MTO9ur73g 提…

React可以做全栈开发吗

React可以做全栈开发吗? 答案是肯定的,而且还比较完美 React可以用于全栈开发,以下是具体的介绍: 前端部分 构建用户界面 React是一个用于构建用户界面的JavaScript库,它通过组件化的方式让开发者能够高效地创建交互式的UI。例…

【前端学习笔记】Javascript学习二(运算符、数组、函数)

一、运算符 运算符(operator)也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。 JavaScript中常用的运算符有: 算数运算符、递增和递减运算符、比较运算符、逻辑运算符、赋值运算符 算数运算符: 、-…

Redis五大基本类型——List列表命令详解(命令用法详解+思维导图详解)

目录 一、List列表类型介绍 二、常见命令 1、LPUSH 2、LPUSHX 3、RPUSH 4、RPUSHX 5、LRANGE 6、LPOP 7、RPOP 8、LREM 9、LSET 10、LINDEX 11、LINSERT 12、LLEN 13、阻塞版本命令 BLPOP BRPOP 三、命令小结 相关内容: Redis五大基本类型——Ha…

快速入门消息队列MQ、RabbitMQ

目录 一、MQ简介 1.同步调用 2.异步调用 3.技术选型 二、RabbitMQ 1.安装 2.控制台的使用说明 2.1交换机 2.2队列​编辑 2.3绑定关系 3.AMQP 3.1快速入门 3.2WorkQueues模型 3.3交换机 3.3.1 Fanout交换机 3.3.2 Direct交换机 3.3.3 Topic交换机 3.4 声明交换机…

CRM系统安全性排名:数据保护能力评估

安全性对CRM系统厂商和企业来说至关重要,因为它直接关系到企业的客户数据、商业机密和品牌信誉。CRM系统通常存储了大量敏感信息,包括客户联系详情、交易记录和个人身份信息,一旦发生数据泄露或安全事件,不仅可能导致客户信任丧失…

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…

《现代网络技术》读书笔记:NFV功能

本文部分内容来源于《现代网络技术:SDN,NFV,QoE、物联网和云计算:SDN,NFV,QoE,IoT,andcloud》 NFV基础设施 NFV体系结构的核心是资源与功能集合,也为称为NFV基础设施(NFVI)。NFVI包括以下三个域: 计算域:提供商用的大…

MySQL数据库2——SQL语句

一.SQL基础 1.SQL通用语法 1.SQL语句可以单行或多行书写,以分号结尾。2.SOL语句可以使用空格/缩进来增强语句的可读性。3.MySQL数据库的SQL语句不区分大小写,关键字建议使用大写 注释: 单行注释:-- 注释内容或#注释内容(MySQL…

会员等级经验问题

问题描述 会员从一级完成任务升级到二级以后,一级显示还差经验,这里差的其实是二级到三级的经验,如下图所示 修复方法 1、前端需要修改: 路径:/pages/users/user_vip/index.vue 方便复制: v-if"i…

【Apache Paimon】-- 6 -- 清理过期数据

目录 1、简要介绍 2、操作方式和步骤 2.1、调整快照文件过期时间 2.2、设置分区过期时间 2.2.1、举例1 2.2.2、举例2 2.3、清理废弃文件 3、参考 1、简要介绍 清理 paimon (表)过期数据可以释放存储空间,优化资源利用并提升系统运行效…

前端项目支持tailwindcss写样式

安装 npm install -D tailwindcss npx tailwindcss init配置 tailwind.config.js //根据个人需求填写,比如vue简单配置 /** type {import(tailwindcss).Config} */ module.exports {darkMode: "class",corePlugins: {preflight: false},content: [&quo…

Spring Boot整合Kafka,实现单条消费和批量消费,示例教程

如何安装Kafka&#xff0c;可以参考docker搭载Kafka集群&#xff0c;一个文件搞定&#xff0c;超简单&#xff0c;亲试可行-CSDN博客 1、在pom.xml中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sta…

ora.LISTENER.lsnr : Not All Endpoints Registered

进行了几次VIP转移 发现该问题无法解决 $ crsctl status res -t -------------------------------------------------------------------------------- NAME TARGET STATE SERVER STATE_DETAILS -----------------------------…

django基于Python的农产品销售系统的设计与实现

摘 要 随着现代人们的快速发展&#xff0c;农产品销售系统已成为农产品的需求。该平台采用Python技术和django搭建系统框架&#xff0c;后台使用MySQL数据库进行信息管理&#xff1b;通过个人中心、用户管理、商家管理、产品类型管理、农产品管理、系统管理、订单管理等功能&a…

项目-摄像

树莓派摄像头使用方法 Camera教程 https://www.raspi.cc/index.php?cread&id53&page1 nanopc-t4 ​https://www.raspi.cc/index.php?cread&id53&page1 摄像头型号 Raspberry Pi Camera Rev 1.3 检测故障 dmesg | grep -i mipi piNanoPC-T4:~$ dmesg | …

Facebook商城号封号的原因是什么?

Facebook商城作为一个重要的销售平台&#xff0c;不仅为商家提供了巨大的市场机会&#xff0c;也带来了一系列需要警惕的风险&#xff0c;其中包括账号被封的风险。本文将从环境异常、频繁操作和违规行为三个主要方面深入探讨&#xff0c;解析导致Facebook商城账号被封禁的具体…

聊一聊Elasticsearch的索引分片的恢复机制

1、什么是索引分片的恢复&#xff1f; 所谓索引分片的恢复指的是在某些条件下&#xff0c;索引分片丢失&#xff0c;ES会把某索引的分片复制一份来得到该分片副本的过程。 2、触发分片恢复的场景有哪些&#xff1f; 分片的分配 当集群中节点的数量发生变化&#xff0c;或者配…

字符串的基本操作(C语言版)

一、实验内容&#xff1a; 采用顺序结构存储串&#xff0c;编写一个函数substring(strl,str2)&#xff0c;用于判定str2是否为strl的子串&#xff1b;编写一个函数&#xff0c;实现在两个已知字符串中找出所有非空最长公共子串的长度和最长公共子串的个数&#xff1b; ①字符…