用23种设计模式打造一个cocos creator的游戏框架----(四)装饰器模式

1、模式标准

模式名称:装饰器模式

模式分类:结构型

模式意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。

结构图:

适用于:

  1. 当需要给一个对象在运行时添加更多的责任时。
  2. 当需要通过组合和封装来添加功能,而不是通过继承来添加功能时。

主要成员

  1. 抽象组件(Component):这是一个接口,定义了一个对象可以接受额外责任的方式。它是所有对象(主对象以及装饰器对象)共享的接口。

  2. 具体组件(Concrete Component):这是抽象组件的具体实现。它定义了一个具体的对象,可以给这个对象添加一些职责。

  3. 抽象装饰类(Decorator):这是所有装饰器类的基类,它定义了一个与抽象组件接口一致的接口,这样它就可以装饰所有的具体组件。抽象装饰类通常有一个成员变量,用来保存一个抽象组件的引用。

  4. 具体装饰类(Concrete Decorator):具体装饰类是抽象装饰类的子类,它定义了具体的装饰效果。每个具体装饰类都增加了一些新的行为或状态。具体装饰类可以通过调用抽象组件的方法,并添加新的行为或状态,来扩展抽象组件的功能。

2、分析与设计

在开发web应用时,用惯了mvc,但开发游戏时,才发现mvc不够用了。web开发,只要处理完用户的当前请求,就完事了。但游戏开发不同,游戏里是一个迷你世界,和我们现实有很多相似之处。现实世界里面有各种各样的系统,每个生物都借助外部系统或内部系统进行着各项活动。前辈们把它们抽象成 ecs(  entity实体  component组件 system系统 ),这的确大大简化了游戏开发。比如:给玩家添加一个登录组件,玩家就有了登录功能,登录完成后,卸载组件释放资源。给玩家添加一个遥感组件,玩家就有了操控摇杆的功能,游戏结束时移除摇杆组件等等。

回到我们的设计模式,ecs算装饰器模式吗?看适用性上是非常相似的

看看GPT是如何回答的

实体组件系统(Entity-Component-System,ECS)模式和装饰器模式在某些方面有一些相似之处,尤其是在它们都允许在运行时动态添加和删除对象的行为方面。然而,它们的核心目标和实现方式是不同的,所以它们并不完全相同。

ECS模式是一种常用于游戏开发的架构模式,它的核心思想是通过组合而非继承来实现对象的行为。在ECS模式中,实体(Entity)是一个标识符,组件(Component)包含数据,系统(System)则包含行为。实体通过添加或删除组件来改变其行为,而系统则负责处理具有特定组件的实体。这种模式的主要优点在于它的灵活性和可扩展性,因为你可以通过简单地添加或删除组件来改变实体的行为。

装饰器模式的目标是允许我们在运行时向对象添加新的行为,而不改变其类的定义。它通过将对象封装在装饰器对象中来实现这一点,装饰器对象实现了与原对象相同的接口,并在调用原对象的方法之前或之后添加新的行为。

所以,尽管ECS模式和装饰器模式都允许动态改变对象的行为,但它们的实现方式和目标是不同的。在ECS模式中,实体没有固定的行为,而是由其组件和系统决定的。而在装饰器模式中,对象的基本行为是由其类定义的,装饰器只是添加或修改这些行为。此外,ECS模式中的实体并不需要实现与其组件一致的接口,这是它与装饰器模式的另一个主要区别。

从GPT的分析中已经给出了,ecs模式是给一个实体(标识符)添加和删除组件来改变其行为。装饰器模式,对象本身是一个类,且有其基本的行为,装饰器只是在其基础上拓展功能。

细心点的人可能早已发现装饰器模式在cocos creator中其实已有通过的node,component为其实现了,需要什么功能挂载一个组件,这不就是装饰器吗?接下来就修改一下我们的意图。

意图:动态地给一个对象(cc.node)添加一些额外的职责(功能component)。相关的功能也可以随着需求的变化而从对象(cc.node)中删除。

3、开始打造

export namespace CCObject {export enum Flags {....}export let __props__: string[];export let __values__: string[];
}
 export class Node extends CCObject implements ISchedulable, CustomSerializable {addComponent<T extends Component>(classConstructor: __private._types_globals__Constructor<T>): T;}
export class Component extends CCObject {node: Node;addComponent<T extends Component>(classConstructor: __private._types_globals__Constructor<T>): T | null;}

4、开始使用 

用过cocos creator ,相信大家都应该很熟悉cocos的组件使用了

这里举几个和设计模式结构图类似的例子

this.node.getComponent(Label).string = '123'
this.node.getChildByName('progress').getComponent(ProgressBar).progress = 456
this.getComponent(UnitItem).addComponent(Label).string = '789'

5、其他-打造ECS

因为是游戏框架且打算使用ecs模式,就在装饰器模式的下面贴出一个简易的ecs(虽然ecs和装饰器模式不完全相同,只是相似)

在ECS模式中,实体(Entity)是一个标识符,组件(Component)包含数据,系统(System)则包含行为。

从概念上实体只是一个标识符没有具体的行为,实体通过添加或删除组件来改变其行为,而系统则负责处理具有特定组件的实体。

import { Comp } from "./Comp";export class Entity {private static _entities: Map<number, Entity> = new Map();private static nextEntityId = 0;// 添加一个公共的 getter 方法来获取 entitiesstatic get entities(): Map<number, Entity> {return this._entities;}static createEntity(): Entity {const entity = new Entity();this.entities.set(entity.id, entity);return entity;}static removeEntity(entity: Entity): void {for (let component of entity.components.values()) {Comp.removeComp(component)}this.entities.delete(entity.id);}static getEntity(entityId: number): Entity | undefined {return this.entities.get(entityId);}static generateEntityId(): number {return this.nextEntityId++;}/** 单实体上挂载的组件 */public components: Map<new () => any, Comp> = new Map();/** 实体id */public readonly id: number;constructor() {this.id = Entity.generateEntityId();this.components = new Map();}/** 单实体上挂载组件 */attachComponent<T extends Comp>(componentClass: new () => T): T {const hascomponent = this.components.get(componentClass) as T;if (hascomponent) {console.error('已存在组件,不会触发挂载事件')return hascomponent;} else {const component = Comp.createComp(componentClass, this);this.components.set(componentClass, component);// console.log('实体挂载了组件', this.components, this)return component;}}/** 单实体上卸载组件 */detachComponent<T extends Comp>(componentClass: new () => T): void {const component = this.components.get(componentClass);if (component) {this.components.delete(componentClass);Comp.removeComp(component)// console.log('实体卸载了组件', this.components, this)}}getComponent<T extends Comp>(componentClass: new () => T): T | undefined {return this.components.get(componentClass) as T;}}

import { Entity } from "./Entity";/*** 组件*/
export abstract class Comp {/*** 组件池*/private static compsPool: Map<new () => any, Comp[]> = new Map();/*** 创建组件* @param compClass * @returns */public static createComp<T extends Comp>(compClass: new () => T, entity: Entity): T {// 获取对应组件类的池子let pool = this.compsPool.get(compClass);// 如果池子不存在,为组件类创建一个新的空池子if (!pool) {pool = [];this.compsPool.set(compClass, pool);}// 如果池子中有实例,则取出并返回;否则创建一个新实例并返回let comp = pool.length > 0 ? pool.pop() as T : new compClass();comp.entity = entitysetTimeout(() => {comp.onAttach(entity); // 延迟0,防止属性数据未初始化赋值就已经执行挂载}, 0)return comp}static removeComp(comp: Comp) {comp.onDetach(comp.entity);comp.entity = nullcomp.reset();// 获取组件实例的构造函数const compClass = comp.constructor as new () => Comp;// 从组件池中找到对应的构造函数对应的池子const pool = this.compsPool.get(compClass);// 如果池子存在,将组件实例放回池子中if (pool) {pool.push(comp);} else {// 如果池子不存在,创建一个新的池子并将组件实例放入this.compsPool.set(compClass, [comp]);}}/*** 单体组件的实体*/public entity: Entity | null = null;/** * 组件挂载并初始化后的回调*/abstract callback: Function;/** 监听挂载到实体 */abstract onAttach(entity: Entity): void/** 监听从实体卸载 */abstract onDetach(entity: Entity): void/** 重置 */abstract reset(): void
}

export abstract class System {update?(dt: number);// 为了简化系统,系统内方法都是静态方法,直接调用
}

6、其他-使用ECS

main.ts 挂载在root下面

    onLoad() {window['xhgame'] = xhgame // 方便console中查看全局const gameDesign = new GameDesign();switch (this.gameCode) {case 'demo': // demogameDesign.setGameBuilder(new DemoGameBuilder(this));gameInstance.game = gameDesign.buildGame<DemoGame>()break;}gameInstance.game.start()// 添加有update的系统(临时放置)xhgame.game.updateSystems.push(GameMoveSystem)}protected update(dt: number): void {if (xhgame.game && xhgame.game.updateSystems.length > 0) {for (const system of xhgame.game.updateSystems) {const _system = system as System_system.update && _system.update(dt);}}}
        // 创建一个player实体const player_entity = Entity.createEntity();ooxh.game.playerEntity = player_entityplayer_entity.attachComponent(PlayerStateComp) // 状态组件player_entity.attachComponent(PlayerLoginComp) // 登录组件player_entity.attachComponent(PlayerTouchMoveComp) // 触控组件
export class BattleInitSystem extends System {// 开始初始化战役static startInit(comp: BattleInitComp, callback: Function) {this.showUnit() // 单位callback && callback()}// 显示各种单位static showUnit() {...}
}export class BattleInitComp extends Comp {callback: Function = nullreset() {this.callback = null}onAttach(entity: Entity) {BattleInitSystem.startInit(this, () => {this.callback && this.callback()})}onDetach(entity: Entity) {}
}

ooxh.game.battleEntity.attachComponent(BattleInitComp).callback = () => {ooxh.game.battleEntity.detachComponent(BattleInitComp)
}

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

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

相关文章

Native Drawing 开发指导,实现 HarmonyOS 基本图形和字体的绘制

场景介绍 Native Drawing 模块提供了一系列的接口用于基本图形和字体的绘制。常见的应用场景举例&#xff1a; ● 2D 图形绘制。 ● 文本绘制。 接口说明 详细的接口说明请参考Drawing。 2D 图形绘制开发步骤 以下步骤描述了如何使用 Native Drawing 模块的画布画笔绘制一…

OpenCV交叉编译

1.下载代码解压 tar -zxvf opencv-4.8.1.tar.gz cd cd opencv-4.8.1 sudo mkdir chmod 777 build cd build 2.配置交叉编译工具 根据自己的板子进行修改 -D CMAKE_C_COMPILERaarch64-mix210-linux-gcc -D CMAKE_CXX_COMPILERaarch64-mix210-linux-g 3.cmake生成makefi…

Axure动态面板控制

首先创建一个项目&#xff0c;拖拽几个矩形喝一个动态面板 然后双击动态面板添加状态state1,state2,state3 然后分别在state1,state2,state3编辑导航对应的内容。 接下来就是添加交互事件&#xff0c;将不同导航对应不同的state. 点击“交互”->鼠标点击->进入交互编辑…

Unity中Batching优化的动态合批

文章目录 前言一、动态合批的规则1、材质相同是合批的前提&#xff0c;但是如果是材质实例的话&#xff0c;则一样无法合批。2、支持不同网格的合批3、动态合批需要网格支持的顶点条件二、我们导入一个模型并且制作一个Shader&#xff0c;来测试动态合批1、我们选择模型的 Mesh…

【改进YOLOv8】融合Gold-YOLO的车辆未礼让行人检测系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着交通工具的普及和道路交通量的增加&#xff0c;交通安全问题日益凸显。尤其是车辆未礼让行人的情况频繁发生&#xff0c;给行人的生命安全带来了严重威胁。因…

【Java】I/O流—File类:从0到1的全面解析

&#x1f38a;专栏【Java】 &#x1f33a;每日一句:看不清楚未来时,就比别人坚持久一点 ⭐欢迎并且感谢大家指出我的问题 目录 1.File概述 2.File构造方法 (1).根据文件路径创建文件对象 (2).根据父路径名字符串和子路径名字符串创建对象 (3).根据父路径对应文件对象和子路…

【C语言】数据在内存中的存储

目录 练笔 整型数据的存储&#xff1a; char 型数据——最简单的整型 整型提升&#xff1a; 推广到其他整形&#xff1a; 大小端&#xff1a; 浮点型数据的存储&#xff1a; 存储格式&#xff1a; 本篇详细介绍 整型数据&#xff0c;浮点型数据 在计算机中是如何储存的。…

【玩转TableAgent 数据智能分析】-- 数据分析不再是专业人士的专利

文章目录 前言一、TableAgent介绍TableAgent 数据分析智能体融合创新应用的新成果Table Family 二、注册TableAgent访问TableAgent注册用量 三、 体验TableAgent样例数据集体验选择样例数据集样例数据集进行数据分析数据图 样例数据集进行数据分析规定图表格式数据图 自定义数据…

开源MES/免费MES/开源MES生产流程管理

一、什么是MES生产管理流程 生产管理系统&#xff08;又称制造执行系统&#xff09;是一种集成了计划、生产、质量控制、库存管理和材料申请等生产流程的管理系统。工厂生产管理流程是企业中实现高效生产的重要一环。 二、工厂生产管理流程的步骤 步骤一&#xff1a;计划和排…

生成测试数据的4种方法、5种工具介绍

在软件测试中&#xff0c;测试数据是测试用例的基础&#xff0c;对测试结果的准确性和全面性有着至关重要的影响。 因此&#xff0c;在进行软件测试时&#xff0c;需要生成测试数据以满足测试场景和要求。本文将介绍什么情况下需要生成测试数据&#xff0c;如何生成测试数据&a…

数字语言的进化:TikTok词汇如何反映社交变革?

随着数字媒体的崛起&#xff0c;社交平台成为了信息传递和文化表达的重要渠道。TikTok作为一款风靡全球的短视频应用&#xff0c;不仅改变了人们的娱乐方式&#xff0c;还在语言层面上带来了一系列新的词汇和表达方式。 本文将深入探讨数字语言的进化&#xff0c;聚焦于TikTok…

动态代理IP和静态代理IP有什么区别,适用场景是什么?

互联网行业的从业者经常会用到一种工具&#xff0c;那就是代理IP工具。动态代理IP和静态代理IP是两种常见的代理IP技术&#xff0c;它们在网络通信中起到了重要的作用&#xff0c;比如大数据行业的从业者会经常需要用到动态代理IP&#xff0c;跨境行业的从业者会经常用到静态代…

如何本地搭建Linux DataEase数据可视化分析工具并实现公网访问

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

ahk系列-windows超级运行框-表达式计算(1)—get取值

1、环境准备 windows 7&#xff0c;8&#xff0c;10&#xff0c;11操作系统ahk 2.x_64位 2、使用方式 get 表达式 可以获取配置文件getconfig.txt中配置的值&#xff0c;get可以计算“[ ]”中的表达式 也可以获取用户&#xff0c;系统的环境变量&#xff0c;或者是path 只…

从零开始:同城O2O外卖APP的技术开发指南

随着互联网的迅速发展&#xff0c;O2O&#xff08;OnlinetoOffline&#xff09;模式在各个行业都取得了巨大成功&#xff0c;而同城外卖APP更是成为人们生活中不可或缺的一部分。本文将从零开始&#xff0c;为您提供一份同城O2O外卖APP的技术开发指南&#xff0c;让您能够深入了…

家政小程序源码,师傅竞价接单

家政预约上门服务小程序开发方案&#xff0c;php开发语言&#xff0c;前端是uniapp&#xff0c;有成品源码&#xff0c;可以二开&#xff0c;可以定制。 一家政小程序用户端功能&#xff1a;服务分类、在线预约、在线下单。 师傅端&#xff1a;在线接单&#xff0c;竞价&…

用C语言实现链栈的基本操作

#include <stdio.h> #include <malloc.h> #define ElemType char//相当于ElemType等同于char类型 //链式结构 数据域指针域 typedef struct LinkStackNode//定义一个链栈的结构体类型 {ElemType data;//ElemType是链栈的元素类型&#xff0c;代表数据域struct Lin…

在JSP项目中编写一个接口返回JSON 供JSP界面异步请求数据

首先 我们要引入json处理的依赖工具 在 pom.xml文件的 dependency 标签中加入如下代码 <dependency><groupId>com.googlecode.json-simple</groupId><artifactId>json-simple</artifactId><version>1.1.1</version> </dependenc…

mockito加junit实现单元测试笔记

目录 一、简介1.1 单元测试的特点1.2 mock类框架使用场景1.3 常用mock类框架1.3.1 mockito1.3.2 easymock1.3.3 powermock1.3.4 JMockit 二、mockito的单独使用2.1 mock对象与spy对象2.2 初始化mock/spy对象的方式初始化mock/spy对象第1种方式初始化mock/spy对象第2种方式初始化…

新版idea创建maven项目时的下载问题

新版idea创建时没有一个直接的maven选项 而是一个Maven Archetype选项&#xff0c;我们只需要选择它也是一样的&#xff0c;后面跟着选就行 配置国内下载源的方法如下&#xff1a; 1. 2. 3. 代码&#xff1a; <mirror> <id>alimaven</id> <name>al…