mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-04-04 10:41:04 +00:00
15 KiB
15 KiB
实体组件模块
实体组件系统是一种用于游戏开发的架构模式,它将游戏对象(实体)的数据(组件)和行为分离。
特点
- 不同实体上的组件更新顺序管理(
只根据注册的组件更新顺序更新,跟实体无关
) - 灵活的EC装饰器 (配合插件
kunpo-ec
使用,配置实体组件信息,一键导出) - 支持多世界(多战斗场景,互不影响)
- 区分数据组件和逻辑组件,只更新逻辑组件
插件链接
- kunpo-ec: https://store.cocos.com/app/detail/7311
使用
creator插件kunpo-ec
kunpo-cc
可以方便创建、配置、导出实体,操作界面如下图:
使用
-
组件类型声明
/** * @Author: gongxh * @Date: 2025-01-23 * @Description: 组件枚举 */ import { cc } from "../header"; /** 数据组件类型 */ enum DataComponentType { Health, Transform, RootNode, LimitMove, /** 渲染组件 (多个) */ Render, } /** 逻辑组件类型 (组件更新数据从上到下) */ export enum SystemComponentType { Move = 100000, ScreenRebound, /** 位置更新系统 */ PositionUpdateSystem = 120000, } export const ComponentType = { ...DataComponentType, ...SystemComponentType }; export type ComponentType = DataComponentType | SystemComponentType; /** 自定义组件更新顺序列表 */ export const componentUpdateOrderList = cc.Enum.getList(cc.Enum(SystemComponentType)).map(item => item.value).sort((a, b) => a - b);
-
编写组件脚本
import { AnimationClip, Asset, AudioClip, Color, Enum, JsonAsset, ParticleAsset, Prefab, Size, Skeleton, SpriteFrame, Vec2, Vec3 } from "cc"; import { _ecdecorator, Component } from "kunpocc"; import { ComponentType } from "../../ComponentTypes"; const { ecclass, ecprop } = _ecdecorator; enum HealthType { HP = 1, Max = 2, Current = 3 } // 注册组件 (必须) @ecclass("Health", ComponentType.Health, { describe: "血量组件" }) export class Health extends Component { // 注册组件属性 (可选: 使用kunpo-ec插件则必须注册) @ecprop({ type: "entity", defaultValue: "", displayName: "实体", tips: "实体" }) private testentity: string = ""; @ecprop({ type: "array", format: "entity", displayName: "实体数组", tips: "实体数组" }) private testentityarray: string[] = []; @ecprop({ type: 'int', defaultValue: 0, displayName: "血量", tips: "当前血量提示" }) private hp: number = 0; @ecprop({ type: 'float', defaultValue: 0, displayName: "最大血量", tips: "最大血量提示" }) private maxHp: number = 0; @ecprop({ type: 'string', defaultValue: "", displayName: "字符串", tips: "字符串提示" }) private string: string = ""; @ecprop({ type: 'boolean', defaultValue: false, displayName: "布尔值", tips: "布尔值提示" }) private bool: boolean = true; @ecprop({ type: "enum", format: Enum(HealthType), defaultValue: HealthType.Current, displayName: "枚举", tips: "枚举提示" }) private hpeunm: HealthType = HealthType.Current; @ecprop({ type: "spriteframe", displayName: "精灵帧" }) private spriteFrame: SpriteFrame; @ecprop({ type: "asset", displayName: "资源" }) private asset: Asset; @ecprop({ type: "prefab", displayName: "预制体" }) private prefab: Prefab; @ecprop({ type: "skeleton", displayName: "骨骼动画" }) private skeleton: Skeleton; @ecprop({ type: "particle", displayName: "粒子" }) private particle: ParticleAsset; @ecprop({ type: "animation", displayName: "动画" }) private animation: AnimationClip; @ecprop({ type: "audio", displayName: "音频" }) private audio: AudioClip; @ecprop({ type: "jsonAsset", displayName: "json资源" }) private jsonAsset: JsonAsset; @ecprop({ type: "object", format: { hp1: { type: "object", format: { hp: "int", max: "int" } }, hp2: { type: "object", format: { hp: "int", max: "int" } }, }, }) private obj: { hp1: { hp: number, max: number }, hp2: { hp: number, max: number } }; @ecprop({ type: "array", format: "int", }) private arr: number[]; @ecprop({ type: "array", format: { type: "object", format: { hp: "int", max: "int" } } }) private arrobj: { hp: number, max: number }[]; @ecprop({ type: "vec2", displayName: "向量2" }) private vec2: Vec2; @ecprop({ type: "vec3", displayName: "向量3" }) private vec3: Vec3; @ecprop({ type: "color", defaultValue: Color.RED, displayName: "颜色" }) private color: Color; @ecprop({ type: "size", displayName: "尺寸" }) private size: Size; protected onAdd(): void { // 设置组件是否更新,只有需要更新的组件才设置 this.needUpdate = true; } protected onEnter(): void { // 可在此获取同实体上的其他组件 let transform = this.getComponent(ComponentType.Transform); /** 获取单例组件 */ let signleton = this.entity.entityManager.getSingleton(ComponentType.XXXX); } protected onRemove(): void { // 清理组件数据 } }
-
创建ec世界,并设置更新
/** * @Author: Gongxh * @Date: 2025-01-16 * @Description: 战斗界面 */ import { ECManager } from "kunpocc"; import { componentUpdateOrderList } from "../../ec/ComponentTypes"; import { cc, fgui, kunpo } from "../../header"; const { uiclass, uiprop, uiclick } = kunpo._uidecorator; @uiclass("Window", "Game", "GameWindow") export class GameWindow extends kunpo.Window { @uiprop container: fgui.GComponent; public onInit() { console.log("GameWindow onInit"); this.adapterType = kunpo.AdapterType.Full; this.type = kunpo.WindowType.CloseAll; this.bgAlpha = 0; } protected onShow() { console.log("GameWindow onShow"); /** 创建一个ec世界的节点 */ let node = new cc.Node(); this.container.node.addChild(node); /** * 创建一个ec世界 * 参数1: 世界名称 * 参数2: 世界节点 * 参数3: 组件更新顺序列表 * 参数4: 实体池的最大缓存数量,多余的不会被缓存,根据需要调整 * 参数5: 预创建的实体数量,根据需要调整 */ kunpo.log("需要更新的组件", componentUpdateOrderList); ECManager.createECWorld("world", node, componentUpdateOrderList, 100, 10); } protected onClose() { /** 退出游戏时 销毁ec世界 */ ECManager.destroyECWorld("world"); } @uiclick private onBack(): void { kunpo.WindowManager.showWindow("HomeWindow"); } @uiclick private onCreateEntity(): void { /** 创建一个实体 */ ECManager.createEntity("world", "entity1"); } protected onUpdate(dt: number): void { /** 更新ec世界 */ ECManager.getECWorld("world").update(dt); } }
重点接口
注:详细说明查看声明文件 kunpocc.d.ts
-
总管理器
ECManager
/**注册所有组件 如果GameEntry因分包导致,组件的代码注册晚于CocosEntry的onInit函数, 则需要在合适的时机手动调用此方法*/ public static registerComponents(): void /** * 创建EC世界 创建EC世界前必须先注册组件 * @param {string} worldName 名称 * @param {Node} node 世界节点 * @param {number[]} componentUpdateOrderList 组件更新顺序列表 (只传需要更新的组件列表) * @param {number} [maxCapacityInPool=128] 实体池最大容量,多余的实体不会缓存 * @param {number} [preloadEntityCount=32] 预加载Entity数量 */ public static createECWorld(worldName: string, node: Node, componentUpdateOrderList: number[], maxCapacityInPool = 128, preloadEntityCount = 32): EntityManager /** 获取EC世界 */ public static getECWorld(worldName: string): EntityManager /** 获取EC世界节点 */ public static getECWorldNode(worldName: string): Node /** 销毁EC世界 */ public static destroyECWorld(worldName: string): void /** * 注册配置表中的实体信息 * 如果在GameEntry中配置了ecConfig,则此方法会自动调用 * @param config 实体配置信息,格式为 {实体名: {组件名: 组件数据}} */ public static registerEntityConfig(config: { [entityName: string]: IEntityConfig }): void /** * 添加实体信息 (如果已经存在, 则数据组合) * 如果存在编辑器编辑不了的数据 用来给编辑器导出的实体信息 添加扩展数据 * @param name 实体名 * @param info 实体信息 */ public static addEntityInfo(name: string, info: IEntityConfig): void /** 获取实体配置信息 */ public static getEntityInfo(name: string): Record<string, any> /** * 创建实体 * @param worldName 实体管理器名称 * @param name 实体名字 * @returns {kunpo.Entity} 实体 */ public static createEntity(worldName: string, name: string): Entity /** * 销毁实体 * @param worldName 世界名称 * @param entity 实体 */ public static destroyEntity(worldName: string, entity: Entity): void /** * 通过实体ID销毁实体 * @param worldName 世界名称 * @param entityId 实体ID */ public static destroyEntityById(worldName: string, entityId: number): void
-
实体管理器 (创建的world)
EntityManager
/** * 通过实体ID获取实体 * @param {EntityId} entityId 实体Id * @returns {(Entity | null)} 实体 */ public getEntity(entityId: EntityId): Entity | null /** * 获取指定标签的实体 * @param {number} tag 标签 * @returns {Entity[]} 返回的实体池 */ public getEntitiesByTag(tag: number): Entity[] /** * 根据实体ID判断实体是否存在 * @param {EntityId} entityId 实体Id * @returns {boolean} */ public exists(entityId: EntityId): boolean /** 添加单例组件 */ public addSingleton(component: Component): void /** 获取单例组件 */ public getSingleton<T extends Component>(componentType: number): T /** 删除单例组件 */ public removeSingleton(componentType: number): void /** 是否存在对应的单例组件 */ public hasSingleton(componentType: number): boolean /** 激活单例组件 */ public activeSingleton(): void /** 更新 需要外部调用 */ public update(dt: number): void
-
实体
Entity
/** 实体名称 */ public name: string; /** 实体ID */ public id: EntityId; /** 实体标识 */ public tags: Set<number>; /** 实体状态 */ public states: Map<number, number>; /** 是否被激活 (添加到实体管理器时激活) */ public active: boolean = false; /** 所属实体管理器 (实体创建后直接赋值) */ public entityManager: EntityManager; /** 所有组件 */ public readonly components: Map<number, Component> = new Map(); /** 添加标签 标签除了表示Entity,还可以通过EntityManager获取指定标签的Entity */ public addTag(...tag: number[]): void /** 删除标签 */ public removeTag(tag: number): void /** 是否包含标签 */ public hasTag(...tag: number[]): boolean /** 获取组件 */ public getComponent<T extends Component>(componentType: number): T /** 添加组件 */ public addComponent(component: Component): void /** 删除组件 */ public removeComponent(componentType: number): void /** 删除所有组件 */ public removeAllComponents(): void /** * 是否包含组件 * @param {number} componentType 组件类型 */ public hasComponent(componentType: number): boolean /** 销毁自己 */ public destroy(): void { this.entityManager.destroyEntityById(this.id); } /** * 添加监听 * @param eventName 监听的消息名 * @param callback 回调 * @param entityId 实体ID * @param once 是否单次监听 */ public addEvent(eventName: string, callback: (...args: any[]) => void, once: boolean = false): void /** * 发送消息 * @param eventName 消息名 * @param entityId 实体ID * @param args 发送参数 */ public sendListener(eventName: string, ...args: any[]): void /** 删除监听 */ public removeListener(eventName: string, callback?: (...args: any[]) => void): void /** * 添加状态 * 状态采用计数方式,对状态处理时需要保证addState和removeState成对存在 * @param {number} state 状态类型 */ public addState(state: number): void /** * 删除状态 * @param {number} state 状态类型 * @returns {boolean} 如果计数为0或状态不存在,则返回true */ public removeState(state: number): boolean /** 是否包含指定状态 */ public hasState(state: number): boolean /** 清除状态 */ public clearState(state: number): void /** 清除所有状态 */ public clearAllStates(): void
-
组件
Component
/** 组件名 */ public name: string; /** 组件类型 */ public type: number; /** 是否需要更新 */ public needUpdate: boolean; /** 所属实体 */ public entity: Entity; /** 所属组件管理器 */ public componentManager: ComponentManager; /** * 获取同实体上的组件 * @param {number} componentType 组件类型 */ public getComponent<T extends Component>(componentType: number): T /** 删除自己 */ public destroySelf(): void /** * 生命周期函数 * 被添加到实体 对应onDestroy */ protected onAdd(): void /** * 生命周期函数 * 组件被销毁 对应onAdd */ protected onDestroy(): void /** * 生命周期函数 * 可在此方法获取实体其他组件 */ protected abstract onEnter(): void; /** * 生命周期函数 * 从实体中删除前执行的函数 在此函数中清理初始化的数据 */ protected abstract onRemove(): void; /** * 更新函数 */ protected onUpdate(dt: number): void