重构ECS框架实现代码分离方便后续扩展新特性

This commit is contained in:
dgflash
2022-05-08 19:31:37 +08:00
parent 5f5c2e5398
commit 9d88ecfaac
43 changed files with 1243 additions and 1202 deletions

View File

@@ -227,7 +227,7 @@
"_priority": 1073741824,
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": 375,
"_orthoHeight": 427.33542319749216,
"_near": 1,
"_far": 2000,
"_color": {

View File

@@ -8,8 +8,8 @@
"__id__": 1
},
"optimizationPolicy": 0,
"asyncLoadAssets": false,
"persistent": false
"persistent": false,
"asyncLoadAssets": false
},
{
"__type__": "cc.Node",
@@ -197,6 +197,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": true,
"_isBold": true,
"_isUnderline": false,
@@ -382,6 +383,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -533,6 +535,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -811,6 +814,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -962,6 +966,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -1240,6 +1245,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -1389,6 +1395,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -1671,6 +1678,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -1821,6 +1829,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -2101,6 +2110,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -2250,6 +2260,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -2530,6 +2541,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -2679,6 +2691,7 @@
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -3011,6 +3024,7 @@
"_enableWrapText": false,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
@@ -3114,7 +3128,7 @@
"__id__": 153
},
"once": false,
"interval": 500,
"interval": 0,
"disabledEffect": false,
"_id": ""
},
@@ -3293,6 +3307,7 @@
"_enableWrapText": false,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,

View File

@@ -6,7 +6,7 @@
*/
import { setDisplayStats, _decorator } from 'cc';
import { DEBUG, JSB } from 'cc/env';
import { ecs } from './core/libs/ECS';
import { ecs } from './core/libs/ecs/ECS';
import { oops } from './core/Oops';
import { CommonEnter } from './game/common/ecs/CommonEnter';
import { smc } from './game/common/ecs/SingletonModuleComp';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "1a67473d-eb8f-4021-9e62-a4fe85df465e",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,245 @@
import { ECSComp } from "./ECSComp";
import { ECSEntity } from "./ECSEntity";
import { ECSMatcher } from "./ECSMatcher";
import { ECSModel } from "./ECSModel";
import { createGroup, ECSComblockSystem, ECSRootSystem, ECSSystem } from "./ECSSystem";
export module ecs {
export type Entity = ECSEntity;
export type Comp = ECSComp;
export type RootSystem = ECSRootSystem;
export type ComblockSystem = ECSComblockSystem;
export type System = ECSSystem;
export const Entity = ECSEntity;
export const Comp = ECSComp;
export const RootSystem = ECSRootSystem;
export const System = ECSSystem;
export const ComblockSystem = ECSComblockSystem;
/** 注:不要尝试修改此对象数据,非对外使用 */
export const model = new ECSModel();
export type CompAddOrRemove = (entity: Entity) => void;
export type CompType<T> = CompCtor<T> | number;
//#region 接口
export interface EntityCtor<T> {
new(): T;
}
export interface IComp {
canRecycle: boolean;
ent: Entity;
reset(): void;
}
export interface CompCtor<T> {
new(): T;
tid: number;
compName: string;
}
export interface IMatcher {
mid: number;
indices: number[];
key: string;
isMatch(entity: Entity): boolean;
}
/**
* 如果需要监听实体首次进入System的情况实现这个接口。
*
* entityEnter会在update方法之前执行实体进入后不会再次进入entityEnter方法中。
* 当实体从当前System移除下次再次符合条件进入System也会执行上述流程。
*/
export interface IEntityEnterSystem<E extends Entity = Entity> {
entityEnter(entity: E): void;
}
/** 如果需要监听实体从当前System移除需要实现这个接口。*/
export interface IEntityRemoveSystem<E extends Entity = Entity> {
entityRemove(entity: E): void;
}
/** 第一次执行update */
export interface ISystemFirstUpdate<E extends Entity = Entity> {
firstUpdate(entity: E): void;
}
/** 执行update */
export interface ISystemUpdate<E extends Entity = Entity> {
update(entity: E): void;
}
//#endregion
/**
* 注册组件到ecs系统中
* @param compName 由于js打包会改变类名所以这里必须手动传入组件的名称。
* @param canNew 标识是否可以new对象。想继承自Cocos Creator的组件就不能去new需要写成@ecs.register('name', false)
*/
export function register<T>(compName: string, canNew: boolean = true) {
return function (ctor: CompCtor<T>) {
if (ctor.tid === -1) {
ctor.tid = model.compTid++;
ctor.compName = compName;
if (canNew) {
model.compCtors.push(ctor);
model.compPools.set(ctor.tid, []);
}
else {
model.compCtors.push(null!);
}
model.compAddOrRemove.set(ctor.tid, []);
}
else {
throw new Error(`重复注册组件: ${compName}.`);
}
}
}
/** 扩展:获取带 eid 自增量的实体继承Entity方式的编码风格可减少一定代码量 */
export function getEntity<T extends Entity>(ctor: EntityCtor<T>): T {
var entitys = model.entityPool.get(ctor.name) || [];
let entity: any = entitys.pop();
if (!entity) {
entity = new ctor();
entity.eid = model.eid++; // 实体id也是有限的资源
}
if (entity.init)
entity.init();
else
console.error(`${ctor.name} 实体缺少 init 方法初始化默认组件`);
model.eid2Entity.set(entity.eid, entity);
return entity as T;
}
/**
* 动态查询实体
* @param matcher
* @returns
*/
export function query<E extends Entity = Entity>(matcher: IMatcher): E[] {
let group = model.groups.get(matcher.mid);
if (!group) {
group = createGroup(matcher);
model.eid2Entity.forEach(group.onComponentAddOrRemove, group);
}
return group.matchEntities as E[];
}
/** 清理所有的实体 */
export function clear() {
model.eid2Entity.forEach((entity) => {
entity.destroy();
});
model.groups.forEach((group) => {
group.clear();
});
model.compAddOrRemove.forEach(callbackLst => {
callbackLst.length = 0;
});
model.eid2Entity.clear();
model.groups.clear();
}
/**
* 根据实体id获得实体对象
* @param eid
*/
export function getEntityByEid<E extends Entity = Entity>(eid: number): E {
return model.eid2Entity.get(eid) as E;
}
/** 当前活动中的实体数量 */
export function activeEntityCount() {
return model.eid2Entity.size;
}
/** 创建实体 */
function createEntity<E extends Entity = Entity>(): E {
let entity = new Entity();
entity.eid = model.eid++; // 实体id也是有限的资源
model.eid2Entity.set(entity.eid, entity);
return entity as E;
}
/**
* 指定一个组件创建实体,返回组件对象。
* @param ctor
*/
function createEntityWithComp<T extends IComp>(ctor: CompCtor<T>): T {
let entity = createEntity();
return entity.add(ctor);
}
//#region 过滤器
/**
* 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件
* 进行添加操作引发Group重复添加实体。
* @param args
*/
export function allOf(...args: CompType<IComp>[]) {
return new ECSMatcher().allOf(...args);
}
/**
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体。
* @param args 组件索引
*/
export function anyOf(...args: CompType<IComp>[]) {
return new ECSMatcher().anyOf(...args);
}
/**
* 表示关注只拥有这些组件的实体
*
* 注意:
* 不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件。
* @param args 组件索引
*/
export function onlyOf(...args: CompType<IComp>[]) {
return new ECSMatcher().onlyOf(...args);
}
/**
* 不包含指定的任意一个组件
*
* eg.
* ecs.excludeOf(A, B);表示不包含组件A或者组件B
* @param args
*/
export function excludeOf(...args: CompType<IComp>[]) {
return new ECSMatcher().excludeOf(...args);
}
//#endregion
//#region 单例组件
/**
* 获取单例组件
* @param ctor 组件类
*/
export function getSingleton<T extends IComp>(ctor: CompCtor<T>) {
if (!model.tid2comp.has(ctor.tid)) {
let comp = createEntityWithComp(ctor) as T;
model.tid2comp.set(ctor.tid, comp);
}
return model.tid2comp.get(ctor.tid) as T;
}
/**
* 注册单例。主要用于那些不能手动创建对象的组件
* @param obj
*/
export function addSingleton(obj: IComp) {
let tid = (obj.constructor as CompCtor<IComp>).tid;
if (!model.tid2comp.has(tid)) {
model.tid2comp.set(tid, obj);
}
}
//#endregion
}

View File

@@ -2,10 +2,8 @@
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "cdf35542-2aa6-4319-b138-da7226504f07",
"uuid": "82e3b858-f25a-4985-8dfd-db86fcd8369e",
"files": [],
"subMetas": {},
"userData": {
"simulateGlobals": []
}
"userData": {}
}

View File

@@ -0,0 +1,28 @@
import { ecs } from "./ECS";
import { ECSEntity } from "./ECSEntity";
/** 组件里面只放数据可能在实际写代码的时候比较麻烦。如果是单纯对组件内的数据操作可以在组件里面写方法 */
export abstract class ECSComp implements ecs.IComp {
/**
* 组件的类型id-1表示未给该组件分配id
*/
static tid: number = -1;
static compName: string;
/**
* 拥有该组件的实体
*/
ent!: ECSEntity;
/**
* 是否可回收组件对象,默认情况下都是可回收的。
* 如果该组件对象是由ecs系统外部创建的则不可回收需要用户自己手动进行回收。
*/
canRecycle: boolean = true;
/**
* 组件被回收时会调用这个接口。可以在这里重置数据,或者解除引用。
*
* **不要偷懒,除非你能确定并保证组件在复用时,里面的数据是先赋值然后再使用。**
*/
abstract reset(): void;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3d017661-0194-47e1-b3df-a4baf9fbef05",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,227 @@
import { ecs } from "./ECS";
import { ECSMask } from "./ECSMask";
//#region 辅助方法
/**
* 实体身上组件有增删操作,广播通知对应的观察者
* @param entity 实体对象
* @param componentTypeId 组件类型id
*/
function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number) {
let events = ecs.model.compAddOrRemove.get(componentTypeId);
for (let i = events!.length - 1; i >= 0; i--) {
events![i](entity);
}
// 判断是不是删了单例组件
if (ecs.model.tid2comp.has(componentTypeId)) {
ecs.model.tid2comp.delete(componentTypeId);
}
}
/**
* 创建组件对象
* @param ctor
*/
function createComp<T extends ecs.IComp>(ctor: ecs.CompCtor<T>): T {
var cct = ecs.model.compCtors[ctor.tid];
if (!cct) {
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
}
let comps = ecs.model.compPools.get(ctor.tid)!;
let component = comps.pop() || new (cct as ecs.CompCtor<T>);
return component as T;
}
/**
* 销毁实体。
*
* 缓存销毁的实体,下次新建实体时会优先从缓存中拿。
* @param entity
*/
function destroyEntity(entity: ECSEntity) {
if (ecs.model.eid2Entity.has(entity.eid)) {
var entitys = ecs.model.entityPool.get(entity.constructor.name);
if (entitys == null) {
entitys = [];
ecs.model.entityPool.set(entity.constructor.name, entitys);
}
entitys.push(entity);
ecs.model.eid2Entity.delete(entity.eid);
}
else {
console.warn('试图销毁不存在的实体');
}
}
//#endregion
export class ECSEntity {
/**
* 实体唯一标识,不要手动修改。
*/
eid: number = -1;
private mask = new ECSMask();
/**
* 当前实体身上附加的组件构造函数
*/
private compTid2Ctor: Map<number, ecs.CompType<ecs.IComp>> = new Map();
/**
* 配合 entity.remove(Comp, false) 记录组件实例上的缓存数据,在添加时恢复原数据
*/
private compTid2Obj: Map<number, ecs.IComp> = new Map();
/**
* 根据组件id动态创建组件并通知关心的系统。
*
* 如果实体存在了这个组件,那么会先删除之前的组件然后添加新的。
*
* 注意不要直接new Componentnew来的Component不会从Component的缓存池拿缓存的数据。
* @param componentTypeId 组件id
* @param isReAdd true-表示用户指定这个实体可能已经存在了该组件那么再次add组件的时候会先移除该组件然后再添加一遍。false-表示不重复添加组件。
*/
add<T extends ecs.IComp>(obj: T): ECSEntity;
add(ctor: number, isReAdd?: boolean): ECSEntity;
add<T extends ecs.IComp>(ctor: ecs.CompCtor<T>, isReAdd?: boolean): T;
add<T extends ecs.IComp>(ctor: ecs.CompType<T>, isReAdd?: boolean): T;
add<T extends ecs.IComp>(ctor: ecs.CompType<T> | T, isReAdd: boolean = false): T | ECSEntity {
// console.log('typeof: ', typeof ctor);
if (typeof ctor === 'function') {
let compTid = ctor.tid;
if (ctor.tid === -1) {
throw Error('组件未注册!');
}
if (this.compTid2Ctor.has(compTid)) { // 判断是否有该组件,如果有则先移除
if (isReAdd) {
this.remove(ctor);
}
else {
console.log(`已经存在组件:${ctor.compName}`);
// @ts-ignore
return this[ctor.compName] as T;
}
}
this.mask.set(compTid);
let comp: T;
if (this.compTid2Obj.has(compTid)) {
comp = this.compTid2Obj.get(compTid) as T;
this.compTid2Obj.delete(compTid);
}
else {
// 创建组件对象
comp = createComp(ctor) as T;
}
// 将组件对象直接附加到实体对象身上,方便直接获取
// @ts-ignore
this[ctor.compName] = comp;
this.compTid2Ctor.set(compTid, ctor);
comp.ent = this;
// 广播实体添加组件的消息
broadcastCompAddOrRemove(this, compTid);
return comp;
}
else {
let tmpCtor = (ctor.constructor as ecs.CompCtor<T>);
let compTid = tmpCtor.tid;
// console.assert(compTid !== -1 || !compTid, '组件未注册!');
// console.assert(this.compTid2Ctor.has(compTid), '已存在该组件!');
if (compTid === -1 || compTid == null) {
throw Error('组件未注册');
}
if (this.compTid2Ctor.has(compTid)) {
throw Error('已经存在该组件');
}
this.mask.set(compTid);
//@ts-ignore
this[tmpCtor.compName] = ctor;
this.compTid2Ctor.set(compTid, tmpCtor);
//@ts-ignore
ctor.ent = this;
//@ts-ignore
ctor.canRecycle = false;
broadcastCompAddOrRemove(this, compTid);
return this;
}
}
addComponents<T extends ecs.IComp>(...ctors: ecs.CompType<T>[]) {
for (let ctor of ctors) {
this.add(ctor);
}
return this;
}
get(ctor: number): number;
get<T extends ecs.IComp>(ctor: ecs.CompCtor<T>): T;
get<T extends ecs.IComp>(ctor: ecs.CompCtor<T> | number): T {
// @ts-ignore
return this[ctor.compName];
}
has(ctor: ecs.CompType<ecs.IComp>): boolean {
if (typeof ctor == "number") {
return this.mask.has(ctor);
}
else {
return this.compTid2Ctor.has(ctor.tid);
}
}
/**
*
* @param ctor 组件构造函数或者组件Tag
* @param isRecycle 是否回收该组件对象。对于有些组件上有大量数据,当要描述移除组件但是不想清除组件上的数据是可以
* 设置该参数为false这样该组件对象会缓存在实体身上下次重新添加组件时会将该组件对象添加回来不会重新从组件缓存
* 池中拿一个组件来用。
*/
remove(ctor: ecs.CompType<ecs.IComp>, isRecycle: boolean = true) {
let hasComp = false;
//@ts-ignore
let componentTypeId = ctor.tid;
//@ts-ignore
let compName = ctor.compName;
if (this.mask.has(componentTypeId)) {
hasComp = true;
//@ts-ignore
let comp = this[ctor.compName] as IECSComp;
//@ts-ignore
comp.ent = null;
if (isRecycle) {
comp.reset();
if (comp.canRecycle) {
ecs.model.compPools.get(componentTypeId)!.push(comp);
}
}
else {
this.compTid2Obj.set(componentTypeId, comp);
}
}
if (hasComp) {
//@ts-ignore
this[compName] = null;
this.mask.delete(componentTypeId);
this.compTid2Ctor.delete(componentTypeId);
broadcastCompAddOrRemove(this, componentTypeId);
}
}
private _remove(comp: ecs.CompType<ecs.IComp>) {
this.remove(comp, false);
}
/**
* 销毁实体,实体会被回收到实体缓存池中。
*/
destroy() {
this.compTid2Ctor.forEach(this._remove, this);
destroyEntity(this);
this.compTid2Obj.clear();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1fb62582-dcf6-4fbc-b863-a1941fad1109",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,77 @@
import { ecs } from "./ECS";
import { ECSEntity } from "./ECSEntity";
export class ECSGroup<E extends ECSEntity = ECSEntity> {
/** 实体筛选规则 */
private matcher: ecs.IMatcher;
private _matchEntities: Map<number, E> = new Map();
private _entitiesCache: E[] | null = null;
/**
* 符合规则的实体
*/
get matchEntities() {
if (this._entitiesCache === null) {
this._entitiesCache = Array.from(this._matchEntities.values());
}
return this._entitiesCache;
}
/**
* 当前group中实体的数量。
*
* 注:不要手动修改这个属性值。
* 注其实可以通过this._matchEntities.size获得实体数量但是需要封装get方法。为了减少一次方法的调用所以才直接创建一个count属性
*/
count = 0;
/** 获取matchEntities中第一个实体 */
get entity(): E {
return this.matchEntities[0];
}
private _enteredEntities: Map<number, E> | null = null;
private _removedEntities: Map<number, E> | null = null;
constructor(matcher: ecs.IMatcher) {
this.matcher = matcher;
}
onComponentAddOrRemove(entity: E) {
if (this.matcher.isMatch(entity)) { // Group只关心指定组件在实体身上的添加和删除动作。
this._matchEntities.set(entity.eid, entity);
this._entitiesCache = null;
this.count++;
if (this._enteredEntities) {
this._enteredEntities.set(entity.eid, entity);
this._removedEntities!.delete(entity.eid);
}
}
else if (this._matchEntities.has(entity.eid)) { // 如果Group中有这个实体但是这个实体已经不满足匹配规则则从Group中移除该实体
this._matchEntities.delete(entity.eid);
this._entitiesCache = null;
this.count--;
if (this._enteredEntities) {
this._enteredEntities.delete(entity.eid);
this._removedEntities!.set(entity.eid, entity);
}
}
}
watchEntityEnterAndRemove(enteredEntities: Map<number, E>, removedEntities: Map<number, E>) {
this._enteredEntities = enteredEntities;
this._removedEntities = removedEntities;
}
clear() {
this._matchEntities.clear();
this._entitiesCache = null;
this.count = 0;
this._enteredEntities?.clear();
this._removedEntities?.clear();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "c21a2de8-f4fe-4534-96a6-70c9a86167ec",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,51 @@
import { ecs } from "./ECS";
export class ECSMask {
private mask: Uint32Array;
private size: number = 0;
constructor() {
let length = Math.ceil(ecs.model.compTid / 31);
this.mask = new Uint32Array(length);
this.size = length;
}
set(num: number) {
// https://stackoverflow.com/questions/34896909/is-it-correct-to-set-bit-31-in-javascript
// this.mask[((num / 32) >>> 0)] |= ((1 << (num % 32)) >>> 0);
this.mask[((num / 31) >>> 0)] |= (1 << (num % 31));
}
delete(num: number) {
this.mask[((num / 31) >>> 0)] &= ~(1 << (num % 31));
}
has(num: number) {
return !!(this.mask[((num / 31) >>> 0)] & (1 << (num % 31)));
}
or(other: ECSMask) {
for (let i = 0; i < this.size; i++) {
// &操作符最大也只能对2^30进行操作如果对2^31&2^31会得到负数。当然可以(2^31&2^31) >>> 0这样多了一步右移操作。
if (this.mask[i] & other.mask[i]) {
return true;
}
}
return false;
}
and(other: ECSMask) {
for (let i = 0; i < this.size; i++) {
if ((this.mask[i] & other.mask[i]) != this.mask[i]) {
return false;
}
}
return true;
}
clear() {
for (let i = 0; i < this.size; i++) {
this.mask[i] = 0;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "d1869e0f-3dbb-46b3-9229-828fef689057",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,214 @@
import { ecs } from "./ECS";
import { ECSEntity } from "./ECSEntity";
import { ECSMask } from "./ECSMask";
let macherId: number = 1;
/**
* 筛选规则间是“与”的关系
* 比如ecs.Macher.allOf(...).excludeOf(...)表达的是allOf && excludeOf即实体有“这些组件” 并且 “没有这些组件”
*/
export class ECSMatcher implements ecs.IMatcher {
protected rules: BaseOf[] = [];
protected _indices: number[] | null = null;
public isMatch!: (entity: ECSEntity) => boolean;
public mid: number = -1;
private _key: string | null = null;
public get key(): string {
if (!this._key) {
let s = '';
for (let i = 0; i < this.rules.length; i++) {
s += this.rules[i].getKey()
if (i < this.rules.length - 1) {
s += ' && '
}
}
this._key = s;
}
return this._key;
}
constructor() {
this.mid = macherId++;
}
/**
* 匹配器关注的组件索引。在创建Group时Context根据组件id去给Group关联组件的添加和移除事件。
*/
get indices() {
if (this._indices === null) {
this._indices = [];
this.rules.forEach((rule) => {
Array.prototype.push.apply(this._indices, rule.indices);
});
}
return this._indices;
}
/**
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体。
* @param args 组件索引
*/
anyOf(...args: ecs.CompType<ecs.IComp>[]): ECSMatcher {
this.rules.push(new AnyOf(...args));
this.bindMatchMethod();
return this;
}
/**
* 组件间是与的关系,表示关注拥有所有这些组件的实体。
* @param args 组件索引
*/
allOf(...args: ecs.CompType<ecs.IComp>[]): ECSMatcher {
this.rules.push(new AllOf(...args));
this.bindMatchMethod();
return this;
}
/**
* 表示关注只拥有这些组件的实体
*
* 注意:
* 不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件。
* @param args 组件索引
*/
onlyOf(...args: ecs.CompType<ecs.IComp>[]): ECSMatcher {
this.rules.push(new AllOf(...args));
let otherTids: ecs.CompType<ecs.IComp>[] = [];
for (let ctor of ecs.model.compCtors) {
if (args.indexOf(ctor) < 0) {
otherTids.push(ctor);
}
}
this.rules.push(new ExcludeOf(...otherTids));
this.bindMatchMethod();
return this;
}
/**
* 不包含指定的任意一个组件
* @param args
*/
excludeOf(...args: ecs.CompType<ecs.IComp>[]) {
this.rules.push(new ExcludeOf(...args));
this.bindMatchMethod();
return this;
}
private bindMatchMethod() {
if (this.rules.length === 1) {
this.isMatch = this.isMatch1;
}
else if (this.rules.length === 2) {
this.isMatch = this.isMatch2;
}
else {
this.isMatch = this.isMatchMore;
}
}
private isMatch1(entity: ECSEntity): boolean {
return this.rules[0].isMatch(entity);
}
private isMatch2(entity: ECSEntity): boolean {
return this.rules[0].isMatch(entity) && this.rules[1].isMatch(entity);
}
private isMatchMore(entity: ECSEntity): boolean {
for (let rule of this.rules) {
if (!rule.isMatch(entity)) {
return false;
}
}
return true;
}
clone(): ECSMatcher {
let newMatcher = new ECSMatcher();
newMatcher.mid = macherId++;
this.rules.forEach(rule => newMatcher.rules.push(rule));
return newMatcher;
}
}
abstract class BaseOf {
indices: number[] = [];
protected mask = new ECSMask();
constructor(...args: ecs.CompType<ecs.IComp>[]) {
let componentTypeId = -1;
let len = args.length;
for (let i = 0; i < len; i++) {
if (typeof (args[i]) === "number") {
componentTypeId = args[i] as number;
}
else {
componentTypeId = (args[i] as ecs.CompCtor<ecs.IComp>).tid;
}
if (componentTypeId == -1) {
throw Error('存在没有注册的组件!');
}
this.mask.set(componentTypeId);
if (this.indices.indexOf(componentTypeId) < 0) { // 去重
this.indices.push(componentTypeId);
}
}
if (len > 1) {
this.indices.sort((a, b) => { return a - b; }); // 对组件类型id进行排序这样关注相同组件的系统就能共用同一个group
}
}
toString(): string {
return this.indices.join('-'); // 生成group的key
}
abstract getKey(): string;
abstract isMatch(entity: ECSEntity): boolean;
}
/**
* 用于描述包含任意一个这些组件的实体
*/
class AnyOf extends BaseOf {
public isMatch(entity: ECSEntity): boolean {
// @ts-ignore
return this.mask.or(entity.mask);
}
getKey(): string {
return 'anyOf:' + this.toString();
}
}
/**
* 用于描述包含了“这些”组件的实体,这个实体除了包含这些组件还可以包含其他组件
*/
class AllOf extends BaseOf {
public isMatch(entity: ECSEntity): boolean {
// @ts-ignore
return this.mask.and(entity.mask);
}
getKey(): string {
return 'allOf:' + this.toString();
}
}
/**
* 不包含指定的任意一个组件
*/
class ExcludeOf extends BaseOf {
public getKey(): string {
return 'excludeOf:' + this.toString();
}
public isMatch(entity: ECSEntity): boolean {
// @ts-ignore
return !this.mask.or(entity.mask);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "37e8aae5-a8f9-4ded-a999-6321c5bc1229",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,43 @@
import { ecs } from "./ECS";
import { ECSEntity } from "./ECSEntity";
import { ECSGroup } from "./ECSGroup";
export class ECSModel {
/** 实体自增id */
eid = 1;
/** 组件类型id */
compTid = 0;
/** 组件缓存池 */
compPools: Map<number, ecs.IComp[]> = new Map();
/** 组件构造函数 */
compCtors: (ecs.CompCtor<any> | number)[] = [];
/**
* 每个组件的添加和删除的动作都要派送到“关心”它们的group上。goup对当前拥有或者之前删除前拥有该组件的实体进行组件规则判断。判断该实体是否满足group
* 所期望的组件组合。
*/
compAddOrRemove: Map<number, ecs.CompAddOrRemove[]> = new Map();
/** 编号获取组件 */
tid2comp: Map<number, ecs.IComp> = new Map();
/**
* 实体对象缓存池
*/
entityPool: Map<string, ECSEntity[]> = new Map();
/**
* 通过实体id查找实体对象
*/
eid2Entity: Map<number, ECSEntity> = new Map();
/**
* 缓存的group
*
* key是组件的筛选规则一个筛选规则对应一个group
*/
groups: Map<number, ECSGroup> = new Map();
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1d60e80c-eabf-46b5-ae0e-a7754005218c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,231 @@
import { ecs } from "./ECS";
import { ECSEntity } from "./ECSEntity";
import { ECSGroup } from "./ECSGroup";
/**
* 创建group每个group只关心对应组件的添加和删除
* @param matcher 实体筛选器
*/
export function createGroup<E extends ECSEntity = ECSEntity>(matcher: ecs.IMatcher): ECSGroup<E> {
let group = ecs.model.groups.get(matcher.mid);
if (!group) {
group = new ECSGroup(matcher);
ecs.model.groups.set(matcher.mid, group);
let careComponentTypeIds = matcher.indices;
for (let i = 0; i < careComponentTypeIds.length; i++) {
ecs.model.compAddOrRemove.get(careComponentTypeIds[i])!.push(group.onComponentAddOrRemove.bind(group));
}
}
return group as unknown as ECSGroup<E>;
}
export abstract class ECSComblockSystem<E extends ECSEntity = ECSEntity> {
protected group: ECSGroup<E>;
protected dt: number = 0;
private enteredEntities: Map<number, E> = null!;
private removedEntities: Map<number, E> = null!;
private hasEntityEnter: boolean = false;
private hasEntityRemove: boolean = false;
private hasUpdate: boolean = false;
private tmpExecute: ((dt: number) => void) | null = null;
private execute!: (dt: number) => void;
constructor() {
let hasOwnProperty = Object.hasOwnProperty;
let prototype = Object.getPrototypeOf(this);
let hasEntityEnter = hasOwnProperty.call(prototype, 'entityEnter');
let hasEntityRemove = hasOwnProperty.call(prototype, 'entityRemove');
let hasFirstUpdate = hasOwnProperty.call(prototype, 'firstUpdate');
let hasUpdate = hasOwnProperty.call(prototype, 'update');
this.hasEntityEnter = hasEntityEnter;
this.hasEntityRemove = hasEntityRemove;
this.hasUpdate = hasUpdate;
if (hasEntityEnter || hasEntityRemove) {
this.enteredEntities = new Map<number, E>();
this.removedEntities = new Map<number, E>();
this.execute = this.execute1;
this.group = createGroup(this.filter());
this.group.watchEntityEnterAndRemove(this.enteredEntities, this.removedEntities);
}
else {
this.execute = this.execute0;
this.group = createGroup(this.filter());
}
if (hasFirstUpdate) {
this.tmpExecute = this.execute;
this.execute = this.updateOnce;
}
}
init(): void {
}
onDestroy(): void {
}
hasEntity(): boolean {
return this.group.count > 0;
}
/**
* 先执行entityEnter最后执行firstUpdate
* @param dt
* @returns
*/
private updateOnce(dt: number) {
if (this.group.count === 0) {
return;
}
this.dt = dt;
// 处理刚进来的实体
if (this.enteredEntities.size > 0) {
var entities = this.enteredEntities.values();
for (let entity of entities) {
(this as unknown as ecs.IEntityEnterSystem).entityEnter(entity);
}
this.enteredEntities.clear();
}
// 只执行firstUpdate
for (let entity of this.group.matchEntities) {
(this as unknown as ecs.ISystemFirstUpdate).firstUpdate(entity);
}
this.execute = this.tmpExecute!;
this.execute(dt);
this.tmpExecute = null;
}
/**
* 只执行update
* @param dt
* @returns
*/
private execute0(dt: number): void {
if (this.group.count === 0) return;
this.dt = dt;
// 执行update
if (this.hasUpdate) {
for (let entity of this.group.matchEntities) {
(this as unknown as ecs.ISystemUpdate).update(entity);
}
}
}
/**
* 先执行entityRemove再执行entityEnter最后执行update
* @param dt
* @returns
*/
private execute1(dt: number): void {
if (this.removedEntities.size > 0) {
if (this.hasEntityRemove) {
var entities = this.removedEntities.values();
for (let entity of entities) {
(this as unknown as ecs.IEntityRemoveSystem).entityRemove(entity);
}
}
this.removedEntities.clear();
}
if (this.group.count === 0) return;
this.dt = dt;
// 处理刚进来的实体
if (this.enteredEntities!.size > 0) {
if (this.hasEntityEnter) {
var entities = this.enteredEntities!.values();
for (let entity of entities) {
(this as unknown as ecs.IEntityEnterSystem).entityEnter(entity);
}
}
this.enteredEntities!.clear();
}
// 执行update
if (this.hasUpdate) {
for (let entity of this.group.matchEntities) {
(this as unknown as ecs.ISystemUpdate).update(entity);
}
}
}
/**
* 实体过滤规则
*
* 根据提供的组件过滤实体。
*/
abstract filter(): ecs.IMatcher;
}
/**
* System的root对游戏中的System遍历从这里开始。
*
* 一个System组合中只能有一个RootSystem可以有多个并行的RootSystem。
*/
export class ECSRootSystem {
private executeSystemFlows: ECSComblockSystem[] = [];
private systemCnt: number = 0;
add(system: ECSSystem | ECSComblockSystem) {
if (system instanceof ECSSystem) {
// 将嵌套的System都“摊平”放在根System中进行遍历减少execute的频繁进入退出。
Array.prototype.push.apply(this.executeSystemFlows, system.comblockSystems);
}
else {
this.executeSystemFlows.push(system as ECSComblockSystem);
}
this.systemCnt = this.executeSystemFlows.length;
return this;
}
init() {
this.executeSystemFlows.forEach(sys => sys.init());
}
execute(dt: number) {
for (let i = 0; i < this.systemCnt; i++) {
// @ts-ignore
this.executeSystemFlows[i].execute(dt);
}
}
clear() {
this.executeSystemFlows.forEach(sys => sys.onDestroy());
}
}
/**
* 系统组合器用于将多个相同功能模块的系统逻辑上放在一起。System也可以嵌套System。
*/
export class ECSSystem {
private _comblockSystems: ECSComblockSystem[] = [];
get comblockSystems() {
return this._comblockSystems;
}
add(system: ECSSystem | ECSComblockSystem) {
if (system instanceof ECSSystem) {
Array.prototype.push.apply(this._comblockSystems, system._comblockSystems);
system._comblockSystems.length = 0;
}
else {
this._comblockSystems.push(system as ECSComblockSystem);
}
return this;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "9261f456-8364-4163-9931-6c526c635402",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "70bdbf80-64ba-465d-baca-d19506dee907",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -6,7 +6,7 @@
*/
import { Message } from "../../core/common/event/MessageManager";
import { ecs } from "../../core/libs/ECS";
import { ecs } from "../../core/libs/ecs/ECS";
import { GameEvent } from "../common/config/GameEvent";
import { AccountNetDataComp, AccountNetDataSystem } from "./bll/AccountNetData";
import { AccountModelComp } from "./model/AccountModelComp";

View File

@@ -8,7 +8,7 @@
import { v3 } from "cc";
import { Message } from "../../../core/common/event/MessageManager";
import { storage } from "../../../core/common/storage/StorageManager";
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { oops } from "../../../core/Oops";
import { GameEvent } from "../../common/config/GameEvent";
import { netConfig } from "../../common/net/NetConfig";

View File

@@ -7,7 +7,7 @@
* @LastEditTime: 2022-01-27 11:07:05
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { Role } from "../../role/Role";
/**

View File

@@ -7,7 +7,7 @@
import { Component, Node, _decorator } from 'cc';
import { EventDispatcher } from '../../../core/common/event/EventDispatcher';
import { ecs } from '../../../core/libs/ECS';
import { ecs } from '../../../core/libs/ecs/ECS';
import { ViewUtil } from '../../../core/utils/ViewUtil';
const { ccclass, property } = _decorator;

View File

@@ -7,7 +7,7 @@
import { Node, _decorator } from 'cc';
import { EventDispatcher } from "../../../core/common/event/EventDispatcher";
import { ecs } from "../../../core/libs/ECS";
import { ecs } from '../../../core/libs/ecs/ECS';
import VMParent from "../../../core/libs/model-view/VMParent";
import { ViewUtil } from "../../../core/utils/ViewUtil";

View File

@@ -4,7 +4,7 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-02-25 09:49:22
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { EcsAccountSystem } from "../../account/Account";
import { EcsRoleSystem } from "../../role/Role";
import { EcsPositionSystem } from "./position/EcsPositionSystem";

View File

@@ -5,7 +5,7 @@
* @LastEditTime: 2022-03-15 10:36:13
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { Account } from "../../account/Account";
import { Initialize } from "../../initialize/Initialize";

View File

@@ -4,7 +4,7 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-01-27 11:10:44
*/
import { ecs } from "../../../../core/libs/ECS";
import { ecs } from "../../../../core/libs/ecs/ECS";
import { MoveToSystem } from "./MoveTo";
export class EcsPositionSystem extends ecs.System {

View File

@@ -6,7 +6,7 @@
*/
import { Node, Vec3 } from "cc";
import { Timer } from "../../../../core/common/manager/TimerManager";
import { ecs } from "../../../../core/libs/ECS";
import { ecs } from "../../../../core/libs/ecs/ECS";
import { Vec3Util } from "../../../../core/utils/Vec3Util";
/** 向目标移动,移动过程中目标位置变化会自动修正移动目标点,直到未修正前移动到目标点停止 */

View File

@@ -5,8 +5,8 @@
* @LastEditTime: 2022-04-14 18:16:20
*/
import { Component, EventTouch, _decorator } from "cc";
import { ecs } from "../../core/libs/ecs/ECS";
import { tips } from "../../core/gui/prompt/TipsManager";
import { ecs } from "../../core/libs/ECS";
import { oops } from "../../core/Oops";
import { UIID } from "../common/config/GameUIConfig";
import { SingletonModuleComp } from "../common/ecs/SingletonModuleComp";

View File

@@ -7,8 +7,8 @@
import { Node } from "cc";
import { resLoader } from "../../core/common/loader/ResLoader";
import { AsyncQueue, NextFunction } from "../../core/common/queue/AsyncQueue";
import { ecs } from "../../core/libs/ecs/ECS";
import { UICallbacks } from "../../core/gui/layer/Defines";
import { ecs } from "../../core/libs/ECS";
import { oops } from "../../core/Oops";
import { config } from "../common/config/Config";
import { UIID } from "../common/config/GameUIConfig";

View File

@@ -6,7 +6,7 @@
*/
import { sys, _decorator } from "cc";
import { resLoader } from "../../../core/common/loader/ResLoader";
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { oops } from "../../../core/Oops";
import { JsonUtil } from "../../../core/utils/JsonUtil";
import { Account } from "../../account/Account";

View File

@@ -6,7 +6,7 @@
* @LastEditTime: 2022-04-25 12:02:10
*/
import { Node, Vec3 } from "cc";
import { ecs } from "../../core/libs/ECS";
import { ecs } from "../../core/libs/ecs/ECS";
import { ViewUtil } from "../../core/utils/ViewUtil";
import { MoveToComp } from "../common/ecs/position/MoveTo";
import { RoleChangeJobComp, RoleChangeJobSystem } from "./bll/RoleChangeJob";

View File

@@ -6,7 +6,7 @@
*/
import { Message } from "../../../core/common/event/MessageManager";
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { RoleModelJobComp } from "../model/RoleModelJobComp";
import { Role } from "../Role";
import { RoleEvent } from "../RoleEvent";

View File

@@ -1,4 +1,4 @@
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { RoleAttributeType } from "../model/RoleEnum";
import { RoleModelLevelComp } from "../model/RoleModelLevelComp";
import { Role } from "../Role";

View File

@@ -5,7 +5,7 @@
* @LastEditTime: 2022-03-10 10:23:41
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { VM } from "../../../core/libs/model-view/ViewModel";
import { RoleAttributeType } from "./RoleEnum";
import { RoleModelComp } from "./RoleModelComp";

View File

@@ -5,7 +5,7 @@
* @LastEditTime: 2022-03-10 10:25:15
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { VM } from "../../../core/libs/model-view/ViewModel";
import { RoleNumeric } from "./attribute/RoleNumeric";
import { RoleNumericMap } from "./attribute/RoleNumericMap";

View File

@@ -4,7 +4,7 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-01-29 10:56:57
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { TableRoleJob } from "../../common/table/TableRoleJob";
import { RoleAttributeType } from "./RoleEnum";
import { RoleModelComp } from "./RoleModelComp";

View File

@@ -5,7 +5,7 @@
* @LastEditTime: 2022-03-10 11:38:31
*/
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { VM } from "../../../core/libs/model-view/ViewModel";
import { TableRoleLevelUp } from "../../common/table/TableRoleLevelUp";

View File

@@ -6,7 +6,7 @@
*/
import { sp, _decorator } from "cc";
import { ecs } from "../../../core/libs/ECS";
import { ecs } from "../../../core/libs/ecs/ECS";
import { CCComp } from "../../common/ecs/CCComp";
import { Role } from "../Role";
import { RoleEvent } from "../RoleEvent";

View File

@@ -1,6 +1,6 @@
import { EventTouch, Node, _decorator } from "cc";
import { ecs } from "../../../core/libs/ecs/ECS";
import { oops } from "../../../core/Oops";
import { ecs } from "../../../core/libs/ECS";
import { UIID } from "../../common/config/GameUIConfig";
import { CCComp } from "../../common/ecs/CCComp";
import { SingletonModuleComp } from "../../common/ecs/SingletonModuleComp";