This commit is contained in:
honmono
2022-03-21 17:27:37 +08:00
commit 91e741a895
320 changed files with 42373 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
export type EntityIndex = number;
export type ComPoolIndex = number;
export enum ComType {
ComCocosNode = 0,
ComMovable = 1,
ComNodeConfig = 2,
ComBehaviorTree = 3,
ComTransform = 4,
ComMonitor = 5,
ComRoleConfig = 6,
ComAttackable = 7,
ComBeAttacked = 8
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "ce38ec2f-50f5-4f7a-ad24-48a576032322",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,36 @@
import { ComType, EntityIndex } from "./Const";
/** 构造函数 */
export interface ECSComConstructor extends Function {
new(): any;
}
export interface ECSTypedComConstructor<T> extends ECSComConstructor {
new():T;
}
/** 通过type存取 构造函数 */
const ComConsMap: {[key: number]: ECSComConstructor} = cc.js.createMap();
function RegistComConstructor(comType: ComType, func: ECSComConstructor) {
ComConsMap[comType] = func;
}
export function GetComConstructor(comType: ComType) {
return ComConsMap[comType];
}
/** 通过构造函数存取 type */
function SetComConstructorType(comCons: ECSComConstructor, type: ComType) {
comCons['__type__'] = type;
}
export function GetComConstructorType<T>(comCons: {prototype: T}): ComType {
return comCons['__type__'];
}
/** ECSComponent */
export function ECSComponent(type: ComType) {
return function(func: ECSComConstructor) {
SetComConstructorType(func, type);
RegistComConstructor(type, func);
};
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "58b062eb-ce1e-49dd-8520-15713b92a51f",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,35 @@
import { ComPoolIndex } from "./Const";
import { ECSTypedComConstructor } from "./ECSComponent";
/**
* 组件池
*/
export class ECSComponentPool<T> {
private _componentConstructor: ECSTypedComConstructor<T>;
public constructor(comCons: ECSTypedComConstructor<T>) {
this._componentConstructor = comCons;
}
private _components: T[] = []; // components
private _reservedIdxs: ComPoolIndex[] = []; // 缓存的component idx
public get(idx: ComPoolIndex): T {
return this._components[idx];
}
public alloc(): ComPoolIndex {
if(this._reservedIdxs.length > 0) {
let ret = this._reservedIdxs.pop();
this._componentConstructor.apply(this._components[ret]); // 重置对象
return ret;
}
let newInstance = new this._componentConstructor();
this._components.push(newInstance);
return this._components.length - 1;
}
public free(idx: ComPoolIndex) {
this._reservedIdxs.push(idx);
}
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "a40c60fb-7834-4de4-b1e9-6a346dd68e60",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,67 @@
import {ECSWorld} from "./ECSWorld"
import {ECSComConstructor, GetComConstructorType } from "./ECSComponent";
import { ComPoolIndex, ComType } from "./Const";
import { ECSComponentPool } from "./ECSComponentPool";
/** 实体 */
export class Entity {
public id: number; // 唯一标识
public index: number; //
public dead: boolean; //
// 实体上的组件, 存放的是ComponentPool的index
private _components: Array<ComPoolIndex> = new Array<ComPoolIndex>(Object.keys(ComType).length/2).fill(-1);
private _world: ECSWorld = null;
public get world(): ECSWorld {
return this._world;
}
public set world(world: ECSWorld) {
this._world = world;
}
/** 获取实体上的组件 */
public getComponent<T>(typeOrFunc: ComType | {prototype: T}): ComPoolIndex {
let type = typeof typeOrFunc == 'number' ? typeOrFunc : GetComConstructorType(typeOrFunc);
let comPoolIdx = this._components[type];
if(comPoolIdx == -1) return -1;
return comPoolIdx;
}
/** 添加组件 */
public addComponent<T>(func: {prototype: T}): ComPoolIndex {
let type = GetComConstructorType(func);
if(this._components[type] !== -1) {
return this._components[type];
}
let comPoolIdx = this._components[type] = this._world.getComponentPool(func).alloc();
this._world.setEntityDirty(this);
return comPoolIdx;
}
/** 移除组件 */
public removeComponent<T extends ECSComConstructor>(func: ECSComConstructor, dirty = true) {
let comPoolIdx = this._components[GetComConstructorType(func)];
if(comPoolIdx == -1) {
console.error(`[ECSEntity]: removeComponent error, type: ${GetComConstructorType(func)}`);
return false;
}
this._components[GetComConstructorType(func)] = -1;
this._world.getComponentPool<ECSComponentPool<T>>(func).free(comPoolIdx);
dirty && this._world.setEntityDirty(this);
return true;
}
/** 移除所有组件 */
public removeAllComponents(dirty: boolean) {
for(let type = 0; type < this._components.length; type++) {
let comPoolIdx = this._components[type];
if(comPoolIdx == -1) continue;
this._world.getComponentPool<ECSComponentPool<any>>(type).free(comPoolIdx);
}
this._components.fill(-1);
if(dirty) {
this._world.setEntityDirty(this);
}
}
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "f6eafcbc-0cee-4a3d-90a7-d1d89d934869",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,67 @@
import { ComType, EntityIndex } from "./Const";
import { Entity } from "./ECSEntity";
import { ECSWorld } from "./ECSWorld";
export class ECSFillter {
private _world: ECSWorld = null;
private _entitiesMap = new Map<EntityIndex, boolean>();
private _acceptComTypes: ComType[] = []; // 接收的组件类型
private _rejectComTypes: ComType[] = []; // 拒绝的组件类型
public constructor(world: ECSWorld, accepts?: ComType[], rejects?: ComType[]) {
this._world = world;
this._acceptComTypes = accepts && accepts.length > 0 ? accepts : this._acceptComTypes;
this._rejectComTypes = rejects && rejects.length > 0 ? rejects : this._rejectComTypes;
}
public get entities() {
return this._entitiesMap;
}
public onEntityEnter(entity: EntityIndex) {
if(this._entitiesMap.has(entity)) {
console.warn(`[ECSFillter]: addEntity entity is had ${entity}`);
return true;
}
this._entitiesMap.set(entity, true);
return true;
}
public onEntityLeave(entity: EntityIndex) {
if(!this._entitiesMap.has(entity)) {
console.warn(`[ECSFillter]: removeEntity entity not had ${entity}`);
return true;
}
this._entitiesMap.delete(entity);
}
public walk(callback?: (entity: number) => boolean) {
this._entitiesMap.forEach((value, entity) => {
callback(entity);
});
}
public isAccept(entity: Entity) {
for(let i = 0; i < this._acceptComTypes.length; i++) {
if(entity.getComponent(this._acceptComTypes[i]) == -1) {
return false;
}
}
for(let i = 0; i < this._rejectComTypes.length; i++) {
if(entity.getComponent(this._rejectComTypes[i]) !== -1) {
return false;
}
}
return true;
}
public isContains(entity: number) {
return this._entitiesMap.has(entity);
}
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "a2265ecd-3751-461b-9d2f-755ccc56c3da",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,14 @@
import { ECSWorld } from "./ECSWorld";
export abstract class ECSSystem {
/** 连接 */
public abstract onAdd(world: ECSWorld): void;
/** 断开连接 */
public abstract onRemove(world: ECSWorld): void;
/** 添加实体 */
public abstract onEntityEnter(world: ECSWorld, entity: number): void;
/** */
public abstract onEntityLeave(world: ECSWorld, entity: number): void;
/** 更新 */
public abstract onUpdate(world: ECSWorld, dt: number): void;
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "925b6e67-1234-4933-a867-339843b9a663",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

203
assets/Script/ECS/lib/ECSWorld.ts Executable file
View File

@@ -0,0 +1,203 @@
import { Entity } from "./ECSEntity"
import { ECSFillter } from "./ECSFillter"
import { ECSComConstructor, GetComConstructor as GetComConstructor, GetComConstructorType } from "./ECSComponent";
import { ECSSystem } from "./ECSSystem";
import { ComPoolIndex, ComType, EntityIndex } from "./Const";
import { ECSComponentPool } from "./ECSComponentPool";
/**
*
*/
export class ECSWorld {
private _systems: ECSSystem[] = []; // world内所有的system
private _entities: Entity[] = []; // world内所有的entity
private _reservedIds: number[] = []; // 缓存
private _componentPools: ECSComponentPool<any>[] = [];
private _fillters = new Map<string, ECSFillter>();
private _entitiesToDelete: number[] = [];
private _entityIdSeed: number = 0;
/** 获取ComponentPool */
public getComponentPool<T>(typeOrFunc: ComType | {prototype: T}): ECSComponentPool<T> {
let type = typeof typeOrFunc == "number" ? typeOrFunc : GetComConstructorType(typeOrFunc);
if(!this._componentPools[type]) {
this._componentPools[type] = new ECSComponentPool<T>(GetComConstructor(type));
}
return this._componentPools[type] as any;
}
/** 添加system */
public addSystem(system: ECSSystem) {
this._systems.push(system);
system.onAdd(this);
for(let i = 0; i < this._entities.length; i++) {
if(this._entities[i].id !== -1) {
system.onEntityEnter(this, i);
}
}
}
/** 移除system */
public removeSystem(system: ECSSystem) {
system.onRemove(this);
for(let i = 0; i < this._entities.length; i++) {
if(this._entities[i].id !== -1) {
system.onEntityLeave(this, i);
}
}
for(let i = this._systems.length - 1; i >= 0; i--) {
if(this._systems[i] == system) {
this._systems.splice(i, 1);
}
}
}
/** 创建实体 */
public createEntity(): number {
let entity: Entity = null;
let index = -1;
if(this._reservedIds.length > 0) {
index = this._reservedIds.pop();
entity = this._entities[index];
}else {
entity = new Entity();
index = this._entities.length;
this._entities.push(entity);
}
entity.id = this._entityIdSeed++;
entity.world = this;
entity.index = index;
entity.dead = false;
for(let system of this._systems) {
system.onEntityEnter(this, entity.index);
}
return entity.index;
}
/** 移除实体 */
public removeEntity(entity: EntityIndex): boolean {
if(entity <= 0) return false;
if(!this._entities[entity] || this._entities[entity].dead) {
console.warn(`[ECSWorld] removeEntity entity is removed`);
return false;
}
this._entities[entity].dead = true;
this._entitiesToDelete.push(entity);
this._fillters.forEach((fillter, key) => {
fillter.isContains(entity) && fillter.onEntityLeave(entity);
});
for(let system of this._systems) {
system.onEntityLeave(this, entity);
}
return true;
}
public getComponent<T>(entity: EntityIndex, com: {prototype: T}) {
if(!this._entities[entity]) return null;
let comPoolIdx = this._entities[entity].getComponent(com);
return this.getComponentPool<T>(com).get(comPoolIdx);
}
public removeComponent(entity: EntityIndex, com: ECSComConstructor) {
if(!this._entities[entity]) return ;
this._entities[entity].removeComponent(com);
}
public addComponent<T>(entity: EntityIndex, com: {prototype: T}) {
if(!this._entities[entity]) return null;
let comPoolIdx = this._entities[entity].addComponent(com);
return this.getComponentPool<T>(com).get(comPoolIdx)
}
public getSingletonComponent<T>(com: {prototype: T}): T {
let entity = this._entities[0];
let comPoolIdx = entity.getComponent(<ECSComConstructor>com);
let pool = this.getComponentPool<T>(com);
if(comPoolIdx >= 0) return pool.get(comPoolIdx);
return pool.get(entity.addComponent(com));
}
public setEntityDirty(entity: Entity): void {
this._fillters.forEach((fillter, key) => {
let accept = !entity.dead && fillter.isAccept(entity);
if(accept != fillter.isContains(entity.index)) {
accept ? fillter.onEntityEnter(entity.index) : fillter.onEntityLeave(entity.index);
}
});
}
public getEntityId(entity: EntityIndex) : number {
return this._entities[entity].id;
}
public getFilter(fillterKey: string): ECSFillter {
if(this._fillters.has(fillterKey)) {
return this._fillters.get(fillterKey);
}
let [acceptStr, rejectStr] = fillterKey.split("-");
let accept = acceptStr && acceptStr.length > 0 ? acceptStr.split(',').map(Number) : null;
let reject = rejectStr && rejectStr.length > 0 ? rejectStr.split(',').map(Number) : null;
let fillter = new ECSFillter(this, accept, reject);
this._fillters.set(fillterKey, fillter);
// 将当期的entity放入fillter
for(let i=1; i<this._entities.length; i++) {
const entity = this._entities[i];
if(fillter.isAccept(entity)) {
fillter.onEntityEnter(entity.index);
}
}
return fillter;
}
public update(dt:number) {
for(let system of this._systems) {
system.onUpdate(this, dt);
}
if(this._entitiesToDelete.length > 0) {
this._realRemoveEntity();
}
}
private _realRemoveEntity() {
for(let entityIdx of this._entitiesToDelete) {
this._entities[entityIdx].removeAllComponents(false);
this._entities[entityIdx].id = -1;
this._reservedIds.push(entityIdx);
}
this._entitiesToDelete.length = 0;
}
}
export function GenFillterKey(accepts: ECSComConstructor[], rejects?: ECSComConstructor[]) {
let acceptTypes: ComType[] = [];
let rejectTypes: ComType[] = [];
if(accepts && accepts.length > 0) {
for(let i = 0; i < accepts.length; i++) {
acceptTypes[i] = GetComConstructorType(accepts[i]);
}
}
if(rejects && rejects.length > 0) {
for(let i = 0; i < rejects.length; i++) {
rejectTypes[i] = GetComConstructorType(rejects[i]);
}
}
if(acceptTypes.length < 0) {
console.error(`[ECSWorld]: GenFillterKey 必须要有accpters`);
return "";
}
acceptTypes.sort();
rejectTypes.sort();
let key = Array.prototype.join.call(acceptTypes, ",");
if(!rejectTypes || rejectTypes.length <= 0) return key;
key += '-';
key += Array.prototype.join.call(rejectTypes, ",");
return key;
}

View File

@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "decf2eee-3ef7-456d-bd8c-8d4eaa63e617",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}