Files
oops-plugin-framework/assets/module/common/CCEntity.ts

293 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { __private, Node } from 'cc';
import { resLoader } from '../../core/common/loader/ResLoader';
import { gui } from '../../core/gui/Gui';
import type { UIParam } from '../../core/gui/layer/LayerUIElement';
import { LayerUIElement } from '../../core/gui/layer/LayerUIElement';
import { oops } from '../../core/Oops';
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 type { CCView } from './CCView';
import { GameComponent } from './GameComponent';
/** ECS 组件构造函数类型(用于继承自 ecs.Comp 的组件) */
type ECSCtor<T extends ecs.Comp> =
| __private.__types_globals__Constructor<T>
| __private.__types_globals__AbstractedConstructor<T>;
/** UI 组件构造函数类型(用于继承自 GameComponent 并使用 gui.register 注册的组件) */
type UICtor<T extends GameComponent = GameComponent> =
| __private.__types_globals__Constructor<T>
| __private.__types_globals__AbstractedConstructor<T>;
/** ECS 游戏视图组件类型(继承自 CCView用于完整的 ECS 组件) */
export type ECSView = CCView<CCEntity>;
/** GUI 视图组件类型(继承自 GameComponent使用 @gui.register 装饰器注册的组件) */
export type GUIView = GameComponent;
/** ECS 实体构造函数类型 */
type EntityCtor<T extends CCEntity = CCEntity> = new (...args: any[]) => T;
/** ECS 业务逻辑组件构造函数类型 */
type BusinessCtor<T extends CCBusiness<CCEntity> = CCBusiness<CCEntity>> = new (...args: any[]) => T;
/** ECS 游戏模块实体 */
export abstract class CCEntity extends ecs.Entity {
//#region 子模块管理
/** 单例子实体集合key: 实体类构造函数value: 实体实例) */
private singletons: Map<EntityCtor, ECSEntity> = null!;
/**
* 批量添加单例子实体
* @param clss 单例子实体类数组
*/
addChildSingletons<T extends CCEntity>(...clss: EntityCtor<T>[]): void {
for (const ctor of clss) {
this.addChildSingleton<T>(ctor);
}
}
/**
* 添加单例子实体
* @param cls 单例子实体类
* @returns 单例子实体
*/
addChildSingleton<T extends CCEntity>(cls: EntityCtor<T>): T {
if (this.singletons == null) this.singletons = new Map();
if (this.singletons.has(cls)) {
console.error(`${cls.name} 单例子实体已存在`);
return null!;
}
const entity = ecs.getEntity<T>(cls);
this.singletons.set(cls, entity);
this.addChild(entity);
return entity as T;
}
/**
* 获取单例子实体
* @param cls 单例子实体类
* @returns 单例子实体,不存在则返回 null
*/
getChildSingleton<T extends CCEntity>(cls: EntityCtor<T>): T {
if (!this.singletons) return null!;
return (this.singletons.get(cls) as T) || null!;
}
/**
* 移除单例子实体
* @param cls 单例子实体类
*/
removeChildSingleton<T extends CCEntity>(cls: EntityCtor<T>): 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 界面逻辑组件
* @param parent 显示对象父级
* @param path 显示资源地址
* @param bundleName 资源包名称
*/
async addPrefab<T extends ECSView>(
ctor: ECSCtor<T>,
parent: Node | GameComponent,
path: string,
bundleName: string = resLoader.defaultBundleName
): Promise<Node> {
let node: Node;
// 跟随父节点释放自动释放当前资源
if (parent instanceof GameComponent) {
node = await parent.createPrefabNode(path, bundleName);
const comp = node.getComponent(ctor);
if (!comp) {
throw new Error(`组件 ${ctor.name} 不存在于预制 ${path}`);
}
this.add(comp);
node.parent = parent.node;
}
// 手动内存管理
else {
node = await ViewUtil.createPrefabNodeAsync(path, bundleName);
const comp = node.getComponent(ctor);
if (!comp) {
throw new Error(`组件 ${ctor.name} 不存在于预制 ${path}`);
}
this.add(comp);
node.parent = parent;
}
return node;
}
/**
* 添加视图层组件
* @param ctor 界面逻辑组件(支持 CCView 或使用 gui.register 注册的 GameComponent 子类)
* @param params 界面参数
* @returns 界面节点
*/
async addUi<T extends GUIView>(ctor: UICtor<T>, params?: UIParam): Promise<Node> {
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(`${key} 界面已存在`);
return oops.gui.get(key);
}
const node = await oops.gui.open(key, params);
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: UICtor) {
const key = gui.internal.getKey(ctor);
if (key) {
const node = oops.gui.get(key);
if (node == null) {
console.warn(`${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<ecs.IComp>);
}
catch (error) {
console.error(`移除界面组件失败: ${key}`, error);
}
};
oops.gui.remove(key);
}
else {
// 没有 LayerUIElement直接移除
this.remove(ctor as unknown as CompType<ecs.IComp>);
}
}
else {
// 组件未使用 gui.register 注册,尝试直接移除
this.remove(ctor as unknown as CompType<ecs.IComp>);
}
}
//#endregion
//#region 游戏业务层管理
/** 模块业务逻辑组件集合key: 业务类构造函数value: 业务实例) */
private businesss: Map<BusinessCtor, CCBusiness<CCEntity>> = null!;
/**
* 批量添加业务逻辑组件
* @param clss 业务逻辑组件类数组
*/
addBusinesss(...clss: BusinessCtor[]) {
for (const ctor of clss) {
this.addBusiness(ctor);
}
}
/**
* 添加业务逻辑组件
* @param cls 业务逻辑组件类
* @returns 业务逻辑组件实例
*/
addBusiness<T extends CCBusiness<CCEntity>>(cls: BusinessCtor<T>): T {
if (this.businesss == null) this.businesss = new Map();
if (this.businesss.has(cls)) {
console.error(`${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<T extends CCBusiness<CCEntity>>(cls: BusinessCtor<T>): T {
if (!this.businesss) return null!;
return (this.businesss.get(cls) as T) || null!;
}
/**
* 移除业务逻辑组件
* @param cls 业务逻辑组件类
*/
removeBusiness<T extends CCBusiness<CCEntity>>(cls: BusinessCtor<T>): 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();
}
}