当前位置: 首页 > news >正文

游戏开发实现简易实用的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 结语

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

相关文章:

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

游戏开发实现简易实用的ui框架 本文使用cocos引擎实现,框架代码本质上不依赖某一个引擎,稍作修改也能作为其他引擎的实现 1.1 UI管理框架的核心需求剖析 分层与类型管理 对不同类型UI需要进行分层管理。不同层级的UI需要有不同的父节点,保证渲…...

vue3的attr透传属性详解和使用法方式。以及在css样式的伪元素中实现

在 Vue 3 和 TypeScript 中,属性透传(attr pass-through)是指将组件的属性传递到其根元素或某个子元素中。这个概念在开发可复用的组件时非常有用,尤其是当你希望将父组件的属性动态地传递给子组件的某个 DOM 元素时。 在 Vue 3 …...

【仿真建模-MESA】框架简介

1. 简介 Mesa是一个基于Python3的开源项目,旨在提供一个现代、易用的多智能体仿真环境。它借鉴了NetLogo、Repast和MASON等多智能体仿真框架的优点,并结合Python语言的强大功能,为用户提供了丰富的建模和仿真工具。 《官方文档》 2. 核心组件…...

Linux环境基础开发工具的使用(yum、vim、gcc、g++、gdb、make/Makefile)

目录 Linux软件包管理器 - yum Linux下安装软件包的方式 认识yum 查找软件包 安装软件 如何实现本地机器和云服务器之间的文件互传 卸载软件 Linux编辑器 - vim vim的基本概念 vim下各模式的切换 批量化注释 vim的简单配置 Linux编译器 - gcc/g gcc/g的作用 gcc/g语…...

VSCode 间距太小

setting->font family 使用:Consolas, Courier New, monospace 字体...

【K8S系列】imagePullSecrets配置正确,但docker pull仍然失败,进一步排查详细步骤

如果 imagePullSecrets 配置正确,但在执行 docker pull 命令时仍然失败,可能存在以下几种原因。以下是详细的排查步骤和解决方案。 1. 检查 Docker 登录凭证 确保你使用的是与 imagePullSecrets 中相同的凭证进行 Docker 登录: 1.1 直接登录 在命令行中,执行以下命令: …...

【ARM Coresight OpenOCD 系列 5.1 -- OpenOCD 无法识别CPUID 问题: xxx is unrecognized】

请阅读【嵌入式开发学习必备专栏】 文章目录 OpenOCD 无法识别CPUID 问题ARM CPUIDCPUID 特性CPUID 寄存器字段OpenOCD 无法识别CPUID 问题 在使用OpenOCD 进行CPU debug的过程中有时会报出 无法识别CPUID的问题,本文将会介绍如何解决这个问题。首先我们来学习下什么是CPUID,…...

如何实现点击目录跳转到指定位置?【vue】

需求&#xff1a;实现目录点击跳转到指定位置&#xff0c;点击后直接定位到指定模块 效果&#xff1a; 实现方法&#xff1a; &#xff08;1&#xff09;a标签跳转 普通使用&#xff1a; <!DOCTYPE html> <html><head><title>a-Demo</title>&l…...

SQL 通配符

SQL 通配符 在SQL中&#xff0c;通配符是一种特殊字符&#xff0c;用于在LIKE子句中搜索数据。它们主要用于模式匹配&#xff0c;允许你搜索符合特定模式的值。SQL中的通配符通常用于SELECT、UPDATE和DELETE语句中&#xff0c;以增加查询的灵活性。本文将详细介绍SQL中常用的通…...

ubuntu显示管理器_显示导航栏

ubuntu文件管理器_显示导航栏 一、原始状态&#xff1a; 二、显示导航栏状态&#xff1a; 三、原始状态--->导航栏状态: 1、打开dconf编辑器&#xff0c;直接在搜索栏搜索 dconf-editor ------如果没有安装&#xff0c;直接按流程安装即可。 2、进入目录&#xff1a;org …...

黑芝麻嵌入式面试题及参考答案

请详细描述二叉树的深度优先搜索(dfs)流程。 深度优先搜索是一种用于遍历二叉树的重要算法,主要有先序遍历、中序遍历和后序遍历三种方式。 先序遍历的流程是,首先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。这就好比是在探索一个家族树,先拜访家族中的长辈…...

使用 PyTorch-BigGraph 构建和部署大规模图嵌入的完整教程

当涉及到图数据时&#xff0c;复杂性是不可避免的。无论是社交网络中的庞大互联关系、像 Freebase 这样的知识图谱&#xff0c;还是推荐引擎中海量的数据量&#xff0c;处理如此规模的图数据都充满挑战。 尤其是当目标是生成能够准确捕捉这些关系本质的嵌入表示时&#xff0c;…...

系统性能优化方法论详解:从理解系统到验证迭代

在当今的企业级和云计算环境中&#xff0c;系统性能优化已成为提升竞争力的关键因素。本文将对系统优化的步骤进行深入解析&#xff0c;帮助读者系统化地进行性能优化&#xff0c;从而显著提升系统的整体表现。 流程概述: 系统性能优化的流程可以分为以下几个关键步骤&#x…...

使用Tengine 对负载均衡进行状态检查(day028)

本篇文章对于在服务器已经安装了nginx,但却希望使用Tengine 的状态检查或其他功能时使用&#xff0c;不需要卸载服务器上的nginx,思路是使用干净服务器&#xff08;未安装过nginx&#xff09;通过编译安装Tengine&#xff0c;通过对./configure的配置&#xff0c;保证安装Tengi…...

网站推广实战案例:杭州翔胜科技有限公司如何为中小企业打开市场大门

以下是以杭州翔胜科技有限公司为例&#xff0c;解析其如何通过网站推广为中小企业打开市场大门的实战案例&#xff1a; 一、一站式网站推广方案 杭州翔胜科技有限公司提供一站式网站推广方案&#xff0c;该方案整合了多种推广手段&#xff0c;如搜索引擎优化&#xff08;SEO&a…...

视频修复技术和实时在线处理

什么是视频修复&#xff1f; 视频修复技术的目标是填补视频中的缺失部分&#xff0c;使视频内容连贯合理。这项技术在对象移除、视频修复和视频补全等领域有着广泛的应用。传统方法通常需要处理整个视频&#xff0c;导致处理速度慢&#xff0c;难以满足实时处理的需求。 技术发…...

文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题

一、在图 25-2 所示的带权重的有向图上运行 Floyd-Warshall 算法&#xff0c;给出外层循环的每一次迭代所生成的矩阵 D ( k ) D^{(k)} D(k) 。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 好的&#xff0c;让我们一步步分析在带权重的有向图上运行 Floyd-Wa…...

如何使用本地大模型做数据分析

工具&#xff1a;interpreter --local 样本数据&#xff1a; 1、启动分析工具 2、显示数据文件内容 输入&#xff1a; 显示/Users/wxl/work/example_label.csv 输出&#xff1a;(每次输出的结果可能会不一样&#xff09; 3、相关性分析 输入&#xff1a; 分析客户类型与成…...

【Nginx从入门到精通】04-安装部署-使用XShell给虚拟机配置静态ip

文章目录 总结1、XShell &#xff1a;方便管理多台机器2、配置ip文件&#xff1a;区分大小写 一、查看上网模式二、Centos 7 设置静态ipStage 1 &#xff1a;登录root账号Stage 2 &#xff1a;设置静态ip : 修改配置文件 <font colororange>ifcfg-ens33Stage 2-1&#xf…...

C# 面向对象的接口

接口&#xff0c;多态性&#xff0c;密封类 C# 接口 遥控器是观众和电视之间的接口。 它是此电子设备的接口。 外交礼仪指导外交领域的所有活动。 道路规则是驾车者&#xff0c;骑自行车者和行人必须遵守的规则。 编程中的接口类似于前面的示例。 接口是&#xff1a; APIsC…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...

flow_controllers

关键点&#xff1a; 流控制器类型&#xff1a; 同步&#xff08;Sync&#xff09;&#xff1a;发布操作会阻塞&#xff0c;直到数据被确认发送。异步&#xff08;Async&#xff09;&#xff1a;发布操作非阻塞&#xff0c;数据发送由后台线程处理。纯同步&#xff08;PureSync…...

JS红宝书笔记 - 3.3 变量

要定义变量&#xff0c;可以使用var操作符&#xff0c;后跟变量名 ES实现变量初始化&#xff0c;因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符&#xff0c;可以创建一个全局变量 如果需要定义…...