import type { Node } from 'cc'; import { oops } from '../../core/Oops'; import { gui } from '../../core/gui/Gui'; import type { UIParam } from '../../core/gui/layer/LayerUIElement'; import { LayerUIElement } from '../../core/gui/layer/LayerUIElement'; import { ViewUtil } from '../../core/utils/ViewUtil'; import { ecs } from '../../libs/ecs/ECS'; import type { ECSEntity } from '../../libs/ecs/ECSEntity'; import type { CompType } from '../../libs/ecs/ECSModel'; import type { CCBusiness } from './CCBusiness'; import { GameComponent } from './GameComponent'; /** ECS 游戏模块实体 */ export abstract class CCEntity extends ecs.Entity { //#region 子模块管理 /** 单例子实体集合(key: 实体类构造函数,value: 实体实例) */ private singletons: Map = null!; /** * 批量添加单例子实体 * @param clss 单例子实体类数组 */ addChildSingletons(...clss: OopsFramework.EntityCtor[]): void { for (const ctor of clss) { this.addChildSingleton(ctor); } } /** * 添加单例子实体 * @param cls 单例子实体类 * @returns 单例子实体 */ addChildSingleton(cls: OopsFramework.EntityCtor): T { if (this.singletons == null) this.singletons = new Map(); if (this.singletons.has(cls)) { console.error('[OopsFramework]', `${cls.name} 单例子实体已存在`); return null!; } const entity = ecs.getEntity(cls); this.singletons.set(cls, entity); this.addChild(entity); return entity as T; } /** * 获取单例子实体 * @param cls 单例子实体类 * @returns 单例子实体,不存在则返回 null */ getChildSingleton(cls: OopsFramework.EntityCtor): T { if (!this.singletons) return null!; return (this.singletons.get(cls) as T) || null!; } /** * 移除单例子实体 * @param cls 单例子实体类 */ removeChildSingleton(cls: OopsFramework.EntityCtor): void { if (!this.singletons) return; const entity = this.singletons.get(cls); if (entity) { this.singletons.delete(cls); this.removeChild(entity); // 销毁实体及其资源,避免内存泄漏 if (entity && typeof entity.destroy === 'function') { entity.destroy(); } } } //#endregion //#region 游戏视图层管理 /** * 通过资源内存中获取预制上的组件添加到ECS实体中 * @param ctor 界面逻辑组件(支持 ECSView 或使用 gui.register 注册的 GameComponent) * @param parent 显示对象父级 * @param path 显示资源地址(可选,不传时使用 @game.prefab 装饰器注册的路径) * @param bundleName 资源包名称(可选,不传时使用 @game.prefab 装饰器注册的包名) * @returns 预制体节点,如果实体已销毁则返回 null */ async addPrefab( ctor: OopsFramework.ViewCtor, parent: OopsFramework.View, path?: string, bundleName?: string ): Promise { // 未传入路径时,从装饰器注册的数据中获取 if (path == null) { path = (ctor as any).GAME_PREFAB_PATH; bundleName = (ctor as any).GAME_PREFAB_BUNDLE; if (path == null) { throw new Error(`组件 ${(ctor as any).name} 未使用 @prefab.register 装饰器注册,请添加 @prefab.register('path/to/prefab') 装饰器或手动传入路径参数`); } } let node: Node; // 跟随父节点释放自动释放当前资源 if (parent instanceof GameComponent) { const result = await parent.createPrefabNode(path, bundleName); if (result == null) return null; node = result; // 检查实体是否已销毁 if ( !this.isValid) { console.warn('[OopsFramework]', `实体已销毁,取消添加预制体组件: ${(ctor as any).name}`); node.destroy(); return null; } const comp = node.getComponent(ctor); if (comp) this.add(comp as unknown as ecs.Comp); node.parent = parent.node; } // 手动内存管理 else { node = await ViewUtil.createPrefabNodeAsync(path, bundleName); // 检查实体是否已销毁 if (!this.isValid) { console.warn('[OopsFramework]', `实体已销毁,取消添加预制体组件: ${(ctor as any).name}`); node.destroy(); return null; } const comp = node.getComponent(ctor); if (comp) this.add(comp as unknown as ecs.Comp); node.parent = parent; } return node; } /** * 移除预制体组件及其节点 * @param node 要销毁的节点或组件(Node 或 GameComponent) */ removePrefab(node: OopsFramework.View): void { if (node instanceof GameComponent) { node.remove(); } else { node.destroy(); } } /** * 添加视图层组件 * @param ctor 界面逻辑组件(支持 CCView 或使用 gui.register 注册的 GameComponent 子类) * @param params 界面参数 * @returns 界面节点,如果实体已销毁则返回 null */ async addUi(ctor: OopsFramework.UICtor, params?: UIParam): Promise { const key = gui.internal.getKey(ctor); if (!key) { throw new Error(`${ctor.name} 界面组件未使用 gui.register 注册`); } if (params == null) { params = { preload: true }; } else { params.preload = true; } if (oops.gui.has(key)) { console.warn('[OopsFramework]', `${key} 界面已存在`); return oops.gui.get(key); } const node = await oops.gui.open(key, params); // 检查实体是否已销毁 if (!this.isValid) { console.warn('[OopsFramework]', `实体已销毁,取消添加界面组件: ${key}`); oops.gui.remove(key); return null; } const comp = node.getComponent(ctor) as unknown as ecs.Comp; if (comp) this.add(comp); oops.gui.show(key); return node; } /** * 移除视图层组件 * @param ctor 界面逻辑组件(支持 CCView 或使用 gui.register 注册的 GameComponent 子类) */ removeUi(ctor: OopsFramework.UICtor) { const key = gui.internal.getKey(ctor); if (key) { const node = oops.gui.get(key); if (node == null) { console.warn('[OopsFramework]', `${key} 界面不存在或已关闭`); return; } const layer = node.getComponent(LayerUIElement); if (layer) { // 处理界面关闭动画播放完成后,移除ECS组件,避免使用到组件实体数据还在动画播放时在使用导致的空对象问题 layer.onClose = () => { try { const view = node.getComponent(ctor) as unknown as ecs.Comp; if (view) this.remove(ctor as unknown as CompType); } catch (error) { console.error('[OopsFramework]', `移除界面组件失败: ${key}`, error); } }; oops.gui.remove(key); } else { // 没有 LayerUIElement,直接移除 this.remove(ctor as unknown as CompType); } } else { // 组件未使用 gui.register 注册,尝试直接移除 this.remove(ctor as unknown as CompType); } } //#endregion //#region 游戏业务层管理 /** 模块业务逻辑组件集合(key: 业务类构造函数,value: 业务实例) */ private businesss: Map> = null!; /** * 批量添加业务逻辑组件 * @param clss 业务逻辑组件类数组 */ addBusinesss(...clss: OopsFramework.BusinessCtor[]) { for (const ctor of clss) { this.addBusiness(ctor); } } /** * 添加业务逻辑组件 * @param cls 业务逻辑组件类 * @returns 业务逻辑组件实例 */ addBusiness>(cls: OopsFramework.BusinessCtor): T { if (this.businesss == null) this.businesss = new Map(); if (this.businesss.has(cls)) { console.error('[OopsFramework]', `${cls.name} 业务逻辑组件已存在`); return null!; } const business = new cls(); business.ent = this; //@ts-ignore business.init(); this.businesss.set(cls, business); // 将业务逻辑组件直接附加到实体对象身上,方便直接获取 Reflect.set(this, cls.name, business); return business as T; } /** * 获取业务逻辑组件 * @param cls 业务逻辑组件类 * @returns 业务逻辑组件实例,不存在则返回 null */ getBusiness>(cls: OopsFramework.BusinessCtor): T { if (!this.businesss) return null!; return (this.businesss.get(cls) as T) || null!; } /** * 移除业务逻辑组件 * @param cls 业务逻辑组件类 */ removeBusiness>(cls: OopsFramework.BusinessCtor): void { if (this.businesss) { const business = this.businesss.get(cls); if (business) { business.destroy(); this.businesss.delete(cls); // 清理实体上的业务逻辑组件引用 Reflect.set(this, cls.name, null); } } } //#endregion destroy(): void { // 1. 先销毁所有子实体,避免内存泄漏 if (this.singletons) { this.singletons.forEach((entity) => { if (entity && typeof entity.destroy === 'function') { entity.destroy(); } }); this.singletons.clear(); this.singletons = null!; } // 2. 再销毁所有业务组件 if (this.businesss) { this.businesss.forEach((business) => business.destroy()); this.businesss.clear(); this.businesss = null!; } super.destroy(); } }