抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

简介

ECS 架构是 实体(Entity)-组件(Component)-系统(System) 组成的架构,主要目的是对数据和逻辑进行解耦以便更好的维护系统。其运行的原理也能提高 CPU 的缓存命中,即可以提高游戏运行的性能。

原理

概述

ECS 架构简单来说就是用实体来组合组件,用组件来保存数据,用系统来进行运算。单一的实体没有任何意义,只有在组合组件之后这个实体才有意义。组件只能用于保存数据,不能自己进行任何的运算。系统只负责运算,不会持久化保存任何数据。这样就把数据和逻辑分离开来,通过组合的方式实现特定的功能,实现数据和逻辑的解耦,以及逻辑与逻辑之间的解耦。

大致示意图如下

组件

组件是 ECS 架构的基础,实体需要组件,系统也根据组件处理逻辑。组件的设计很简单,我们只需要以下基础属性和方法:

  1. 组件 id,每个组件都拥有一个属于自己的 id,这个 id 是全局唯一的。
  2. 组件名,每个组件都拥有一个属于自己的名字,这个名字是全局唯一的。
  3. 实体,保存组件所属的具体实体。
  4. 是否可以被回收,有些组件在实体移除后需要回收,有些则不从实体回收。
  5. 重置方法,用于回收组件之后进行组件的重置。

因此我们可以定义一个组件接口,接口不关心组件的具体信息,只关心组件是否可以回收、组件的所属以及重置方法。

IComp
1
2
3
4
5
6
7
import Entity from "../Entity";

export default interface IComp {
canRecycle: boolean;
entity: Entity;
reset(): void;
}

然后我们定义一个抽象组件基类。

Comp
1
2
3
4
5
6
7
8
9
10
import Entity from "./Entity";
import IComp from "./Interface/IComp";

export abstract class Comp implements IComp {
static tid = -1;
static compName: string;
public canRecycle: boolean = true;
public entity: Entity | null;
abstract reset(): void;
}

然后我们就可以定义实际的 Comp 类来保存我们需要的数据。

组件还有一种形式是标签,标签不继承抽象组件基类,但是在注册的时候也是和组件共享一套 id 自增规则。注意,标签类中的属性要任意赋一个值,这样才能在注册的时候获取到这个属性。

实体

实体是组件的合集,虽然实体的概念很简单,但是实体的实现却比较复杂。实体需要实现组件的添加和移除,也要在添加移除组件的时候通知对应系统进行处理,同时实体也要提供组件的查询功能。当然,实体自身也要提供移除的方法。

掩码工具

由于实体需要查询组件,系统也需要查询组件,因此我们需要先设计对应的功能。此处我们选择用二进制数来制作掩码系统保存组件信息。

这里我们使用一个二进制数组来保存,用数组的目的是如果组件数超过二进制数大小,就在数组增加一个二进制数来保存。在 32 位二进制数中,由于与(&)操作符最大只能操作 30 位数(一位符号位,一位进位),因此一个数只保存 30 个组件。

由于组件的数量在游戏的最开始就初始化完成,因此 Mask 实例的组件总数是固定的。

当我们进行掩码运算时,传入的组件 id 转为二进制数和当前的掩码进行比较,例如我们设置组件时,假设当前掩码为 0000 0000 0000 0000 ,传入的组件 id 为 3,则我们把组件 id 化为 1 << (3 % 31),即 1 左移 3 位,得到 10000000 0000 0000 0000 & 1000 = 0000 0000 0000 1000,最终的组件就保存下来了。

也就是说 32 位掩码就是插槽,组件的 id 就是往哪个插槽插入组件,这样就能表示保存的组件了。

Mask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import ECSManager from "./ECSManager";

export default class Mask {
/** 32位二进制数组 由于&操作符最大只能30位操作 故每个三十二位二进制保存30个组件 */
private mask: Uint32Array;
private size: number = 0;

constructor() {
//计算32位掩码数量 (总组件数/31)
let length = Math.ceil(ECSManager.getInst().getCompTid() / 31);
this.mask = new Uint32Array(length);
this.size = length;
}

/**
* 设置掩码 或
* @param num
*/
set(num: number) {
/// >>> 无符号位移 高位补0
this.mask[(num / 31) >>> 0] |= 1 << num % 31;
}

/**
* 移除掩码 与 取反
* @param num
*/
delete(num: number) {
this.mask[(num / 31) >>> 0] &= ~(1 << num % 31);
}

/**
* 查找 与
* @param num
* @returns
*/
has(num: number) {
// !!取布尔值 0或1
return !!(this.mask[(num / 31) >>> 0] & (1 << num % 31));
}

or(other: Mask) {
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: Mask) {
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;
}
}
}

筛选工具

因为系统以实体进行遍历的,所以有了掩码之后,我们就可以对实体进行筛选了,通过比较实体的组件掩码和筛选工具的组件掩码,我们就可以筛选出系统需要的实体。

首先我们定义一个匹配规则抽象基类,基类的构造函数根据传入的组件设置掩码,并且按顺序保存组件 id。

然后我们定义各个匹配规则的类,判断是否匹配只需要调用掩码定义的规则即可。

所有规则类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { CompTypeUnion } from "./ECSManager";
import Entity from "./Entity";
import { CompType } from "./Interface/CompType";
import IComp from "./Interface/IComp";
import Mask from "./Mask";

export abstract class BaseOf {
protected mask = new Mask();
public indices: number[] = [];
constructor(...args: CompTypeUnion<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 CompType<IComp>).tid;
}
if (componentTypeId == -1) {
console.error("存在没有注册的组件!");
}
this.mask.set(componentTypeId);

if (this.indices.indexOf(componentTypeId) < 0) {
// 去重
this.indices.push(componentTypeId);
}
}
if (len > 1) {
// 对组件类型id进行排序,这样关注相同组件的系统就能共用同一个group
this.indices.sort((a, b) => {
return a - b;
});
}
}

public toString(): string {
return this.indices.join("-"); // 生成group的key
}

public abstract getKey(): string;

public abstract isMatch(entity: Entity): boolean;
}

/**
* 用于描述包含任意一个这些组件的实体
*/
export class AnyOf extends BaseOf {
public isMatch(entity: Entity): boolean {
// @ts-ignore
return this.mask.or(entity.mask);
}

getKey(): string {
return "anyOf:" + this.toString();
}
}

/**
* 用于描述包含了“这些”组件的实体,这个实体除了包含这些组件还可以包含其他组件
*/
export class AllOf extends BaseOf {
public isMatch(entity: Entity): boolean {
// @ts-ignore
return this.mask.and(entity.mask);
}

getKey(): string {
return "allOf:" + this.toString();
}
}

/**
* 不包含指定的任意一个组件
*/
export class ExcludeOf extends BaseOf {
public getKey(): string {
return "excludeOf:" + this.toString();
}

public isMatch(entity: Entity): boolean {
// @ts-ignore
return !this.mask.or(entity.mask);
}
}

然后我们定义匹配器,匹配器可以包含复数规则,即匹配器是规则的集合,是所有匹配的实际执行类。匹配器也需要一个全局唯一的 id,给后面的 Group 使用。

IMatcher
1
2
3
4
5
6
7
8
import Entity from "../Entity";

export interface IMatcher {
mid: number;
indices: number[];
key: string;
isMatch(entity: Entity): boolean;
}
Matcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import ECSManager, { CompTypeUnion } from "./ECSManager";
import Entity from "./Entity";
import { AllOf, AnyOf, BaseOf, ExcludeOf } from "./FilterRule";
import IComp from "./Interface/IComp";
import { IMatcher } from "./Interface/IMatcher";

/**
* 筛选规则间是“与”的关系
* 比如:ecs.Macher.allOf(...).excludeOf(...)表达的是allOf && excludeOf,即实体有“这些组件” 并且 “没有这些组件”
*/
export class Matcher implements IMatcher {
protected rules: BaseOf[] = [];
protected _indices: number[] | null = null;
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 = ECSManager.getInst().getMatherId();
}

/**
* 匹配器关注的组件索引。在创建Group时,Context根据组件id去给Group关联组件的添加和移除事件。
*/
public 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 组件索引
*/
public anyOf(...args: CompTypeUnion<IComp>[]): Matcher {
this.rules.push(new AnyOf(...args));
return this;
}

/**
* 组件间是与的关系,表示关注拥有所有这些组件的实体。
* @param args 组件索引
*/
public allOf(...args: CompTypeUnion<IComp>[]): Matcher {
this.rules.push(new AllOf(...args));
return this;
}

/**
* 表示关注只拥有这些组件的实体
*
* 注意:
* 不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件。
* @param args 组件索引
*/
public onlyOf(...args: CompTypeUnion<IComp>[]): Matcher {
this.rules.push(new AllOf(...args));
let otherTids: CompTypeUnion<IComp>[] = [];
for (let comp of ECSManager.getInst().getComps()) {
if (args.indexOf(comp) < 0) {
otherTids.push(comp);
}
}
this.rules.push(new ExcludeOf(...otherTids));
return this;
}

/**
* 不包含指定的任意一个组件
* @param args
*/
public excludeOf(...args: CompTypeUnion<IComp>[]) {
this.rules.push(new ExcludeOf(...args));
return this;
}

public isMatch(entity: Entity): boolean {
for (let rule of this.rules) {
if (!rule.isMatch(entity)) {
return false;
}
}
return true;
}

public clone(): Matcher {
let newMatcher = new Matcher();
newMatcher.mid = ECSManager.getInst().getMatherId();
this.rules.forEach((rule) => newMatcher.rules.push(rule));
return newMatcher;
}
}

大致的关系如下图所示

实体类

现在我们可以实现实体类了。

大概的思路如图所示

首先是组件的增加。

我们设置一个 Map(下文都叫字典)_compInEntity 来保存当前实体的组件,设置一个字典 _compRemoved 用来保存已经移除但没有回收的组件。

添加组件的时候先判断是组件还是标签。

  • 标签的情况,判断标签是否已经注册,已注册的情况下把标签添加到实体掩码,并且添加到已保存的组件字典中即可。
  • 组件的情况,判断组件是否已注册,已注册并且已存在的情况下判断是否需要重新添加,需要的话就移除当前的组件,然后把组件添加到实体掩码,并且添加到已保存的组件字典中。

添加完成之后,我们还需要广播增删事件给系统。

然后是组件的移除。

组件的移除和添加类似,也需要分为标签和组件两个部分。

  • 标签的情况,判断标签是否存在,是的话标记存在,到最后统一处理。
  • 组件的情况,判断组件是否存在,是的话标记存在,取出当前组件的实例,设置实例的实体为 null,判断是否需要回收,需要回收的话执行组件初始化操作并回收,不需要的话就把组件放到移除列表中。

最后我们执行掩码的移除和组件存在列表的移除,并且广播通知系统执行对应操作。

在组件的添加和移除中,我们都可以设置实体类的对应名称属性为组件实例,以便更方便地访问组件。

接着是组件的查找。

组件的查找很简单,可以用掩码也可以用字典,根据自己的需要即可。组件的获取同理,可以用字典也可以用属性,自己决定即可。

最后是实体的移除。

实体移除的时候要移除所有组件,包括存在的和移除的列表。然后实体可以放入对象池。

Entity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import ECSManager, { CompTypeUnion } from "./ECSManager";
import { CompType } from "./Interface/CompType";
import IComp from "./Interface/IComp";
import Mask from "./Mask";

export default class Entity {
public eid: number = -1;
private mask = new Mask();

/**
* 当前实体上的组件
*/
private _compInEntity: Map<number, CompTypeUnion<IComp>> = new Map();
/**
* 保存在实体上的已移除的组件
*/
private _compRemoved: Map<number, CompTypeUnion<IComp>> = new Map();

constructor() {}

/**
* 添加组件
* @param comp
* @param isReAdded
* @returns
*/
public add<T extends IComp>(
comp: CompTypeUnion<T>,
isReAdded = false
): T | null {
let compId;
if (typeof comp == "number") {
compId = comp;
if (ECSManager.getInst().hasTag(comp)) {
this.mask.set(comp);
this._compInEntity.set(comp, comp);
let tagName = ECSManager.getInst().getTag(comp)!;
// @ts-ignore
this[tagName] = comp;
ECSManager.getInst().broadcastCompAddOrRemove(this, comp);
} else {
console.error("不存在的tag!");
}
return null;
} else {
compId = comp.tid;
if (compId == -1) {
console.error("组件未注册");
return null;
}

if (this._compInEntity.has(compId)) {
if (isReAdded) {
this.remove(comp);
} else {
console.log("组件" + comp.compName + "已存在");
return this[comp.compName];
}
}

this.mask.set(compId);

let compInstance: T;
if (this._compRemoved.has(compId)) {
compInstance = this._compRemoved.get(compId) as unknown as T;
this._compRemoved.delete(compId);
} else {
compInstance = ECSManager.getInst().createComp(comp);
}

this[comp.compName] = compInstance;
this._compInEntity.set(compId, comp);
compInstance.entity = this;
//TODO 广播添加组件消息
ECSManager.getInst().broadcastCompAddOrRemove(this, compId);

return compInstance as T;
}
}

/**
* 添加组件
* @param comps
* @returns
*/
public addComps(reAdded = false, ...comps) {
for (let comp of comps) {
this.add(comp, reAdded);
}
return this;
}

/**
* 获得组件
* @param comp
* @returns
*/
public get<T>(comp: CompTypeUnion<T>) {
let compName;
if (typeof comp == "number") {
compName = ECSManager.getInst().getTag(comp);
} else {
compName = comp.compName;
}
return this[compName] as T;
}

/**
* 判断组件是否存在
* @param comp
* @returns
*/
public has<T>(comp: CompTypeUnion<T>) {
if (typeof comp == "number") {
return this.mask.has(comp);
} else {
return this._compInEntity.has(comp.tid);
}
}

private _remove(comp: CompTypeUnion<IComp>) {
//TODO git上为false 此处测试true
this.remove(comp, true);
}

/**
* 移除组件
* @param comp 组件
* @param isRecycle 是否回收
*/
public remove(comp: CompTypeUnion<IComp>, isRecycle: boolean = true) {
let compName: string;
let id = -1;
let hasComp = false;
if (typeof comp == "number") {
id = comp;
if (this.mask.has(id)) {
hasComp = true;
compName = ECSManager.getInst().getTag(id);
} else {
console.warn("试图移除不存在的tag");
return;
}
} else {
id = comp.tid;
compName = comp.compName;
if (this.mask.has(id)) {
hasComp = true;
let compInstance = this[compName] as CompType<IComp>;
compInstance.entity = null;
if (isRecycle) {
compInstance.reset();
if (compInstance.canRecycle) {
ECSManager.getInst().recycle(id, compInstance);
}
} else {
this._compRemoved.set(id, compInstance);
}
} else {
console.warn("试图移除不存在的组件", compName);
}
}

if (hasComp) {
this[compName] = null;
this.mask.delete(id);
this._compInEntity.delete(id);
ECSManager.getInst().broadcastCompAddOrRemove(this, id);
// console.log("广播移除组件", compName);
}
}

/**
* 移除组件
* @param isRecycle
* @param args
*/
public removeComps(isRecycle = true, ...args: CompTypeUnion<IComp>[]) {
for (let c of args) {
this.remove(c, isRecycle);
}
}

/**
* 移除实体
*/
public removeSelf() {
this._compInEntity.forEach(this._remove, this);
this._compRemoved.forEach(this._remove, this);
ECSManager.getInst().removeEntity(this.eid, this);
}
}

系统

系统根据所需要的组件来筛选实体,有时候不同系统需要的组件相同,因此我们使用组 Group 来管理系统。

系统的结构如下图所示

群组

群组包含一个匹配器,如前文所说,匹配器的 id 也是群组的 id。群组只关心组件匹配的实体,操作的对象也都是实体。

我们保存一个匹配实体字典,以实体 id 为 key,以及一个缓存实体数组。缓存实体数组是系统运行时遍历的,每次组件广播后删除,下次重新缓存。

另外,我们保存一个进入实体列表和一个移除实体列表,这两个列表保存的是系统中对应列表的引用,在群组里专门负责监控组件变化时实体的进入和移除情况。

Group
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import Entity from "./Entity";
import { IMatcher } from "./Interface/IMatcher";

export default class Group<E extends Entity = Entity> {
/**
* 实体筛选规则
*/
private matcher: IMatcher;

private _matchEntities: Map<number, E> = new Map();

private _entitiesCache: E[] | null = null;

/**
* 符合规则的实体
*/
public get matchEntities() {
if (this._entitiesCache === null) {
this._entitiesCache = Array.from(this._matchEntities.values());
}
return this._entitiesCache;
}

/**
* 当前group中实体的数量。
*
* 不要手动修改这个属性值。
*/
public count = 0; // 其实可以通过this._matchEntities.size获得实体数量,但是需要封装get方法。为了减少一次方法的调用所以才直接创建一个count属性

/**
* 获取matchEntities中第一个实体
*/
get entity(): E {
return this.matchEntities[0];
}

private _enteredEntities: Map<number, E> | null = null;
private _removedEntities: Map<number, E> | null = null;

constructor(matcher: IMatcher) {
this.matcher = matcher;
}

/**
* 组件变化监听
* @param entity 实体
*/
public 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);
}
if (this._removedEntities) {
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);
}
if (this._removedEntities) {
this._removedEntities.set(entity.eid, entity);
}
}
}

/**
* 监控进入/移除数组
* @param enteredEntities
* @param removedEntities
*/
public 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();
}
}

系统实现

系统实现分为两个部分,一个部分是系统的具体实现,还有一个部分是根系统。

系统的具体实现核心目标是实现实体进入时的逻辑处理,每帧逻辑处理和实体移除时的逻辑处理。因此我们定义如下接口

ISystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Entity from "../Entity";

/**
* 如果需要监听实体首次进入System的情况,实现这个接口。
*
* entityEnter会在update方法之前执行,实体进入后,不会再次进入entityEnter方法中。
* 当实体从当前System移除,下次再次符合条件进入System也会执行上述流程。
*/
export interface IEntityEnterSystem<E extends Entity = Entity> {
entityEnter(entities: E[]): void;
}

/**
* 如果需要监听实体从当前System移除,需要实现这个接口。
*/
export interface IEntityRemoveSystem<E extends Entity = Entity> {
entityRemove(entities: E[]): void;
}

/**
* 第一次执行update
*/
export interface ISystemFirstUpdate<E extends Entity = Entity> {
firstUpdate(entities: E[]): void;
}

我们在系统实现类的构造函数中判断是否有我们需要实现的方法,分为以下两种情况:

  1. 有首次进入/移除的逻辑函数execute1。这时候我们要初始化实体进入/移除的数组,并且在组里监听这两个数组的实体变化。之后设置执行函数为execute1
  2. 没有上述逻辑,直接设置执行函数为每帧更新的逻辑函数execute0

如果有第一次执行 update 的函数updateOnce的话,就保存当前设置的执行函数,并且设执行函数为updateOnce

execute1execute0updateOnce执行之后,我们都要置实体进入/移除的数组为空,防止下次执行时重复对已执行实体再次执行对应逻辑。

如果还有其他需求,也可以自行定义对应的接口和具体的处理逻辑。

ComblockSystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import ECSManager from "./ECSManager";
import Entity from "./Entity";
import Group from "./Group";
import { IMatcher } from "./Interface/IMatcher";
import {
IEntityEnterSystem,
ISystemFirstUpdate,
IEntityRemoveSystem,
} from "./Interface/ISystem";

export abstract class ComblockSystem<E extends Entity = Entity> {
protected _group: Group<E>;
protected _dt: number = 0;

private _enteredEntities: Map<number, E> | null = null;
private _removedEntities: Map<number, E> | null = null;

private _hasEntityEnter: boolean = false;
private _hasEntityRemove: 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");

this._hasEntityEnter = hasEntityEnter;
this._hasEntityRemove = hasEntityRemove;

if (hasEntityEnter || hasEntityRemove) {
this._enteredEntities = new Map<number, E>();
this._removedEntities = new Map<number, E>();

this.execute = this.execute1;
this._group = ECSManager.getInst().createGroup(this.filter());
this._group.watchEntityEnterAndRemove(
this._enteredEntities,
this._removedEntities
);
} else {
this.execute = this.execute0;
this._group = ECSManager.getInst().createGroup(this.filter());
}

if (hasFirstUpdate) {
this._tmpExecute = this.execute;
this.execute = this.updateOnce;
}
}

init(): void {}

onDestroy(): void {}

hasEntity(): boolean {
return this._group.count > 0;
}

private updateOnce(dt: number) {
if (this._group.count === 0) {
return;
}
this._dt = dt;
// 处理刚进来的实体
if (this._enteredEntities && this._enteredEntities.size > 0) {
(this as unknown as IEntityEnterSystem).entityEnter(
Array.from(this._enteredEntities.values()) as E[]
);
this._enteredEntities.clear();
}
(this as unknown as ISystemFirstUpdate).firstUpdate(
this._group.matchEntities
);
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;
this.update(this._group.matchEntities);
}

/**
* 先执行entityRemove,再执行entityEnter,最后执行update。
* @param dt
* @returns
*/
private execute1(dt: number): void {
if (this._removedEntities && this._removedEntities.size > 0) {
if (this._hasEntityRemove) {
(this as unknown as IEntityRemoveSystem).entityRemove(
Array.from(this._removedEntities.values()) as E[]
);
}
this._removedEntities.clear();
}
if (this._group.count === 0) {
return;
}
this._dt = dt;
// 处理刚进来的实体
if (this._enteredEntities && this._enteredEntities.size > 0) {
if (this._hasEntityEnter) {
(this as unknown as IEntityEnterSystem).entityEnter(
Array.from(this._enteredEntities.values()) as E[]
);
}
this._enteredEntities.clear();
}
this.update(this._group.matchEntities as E[]);
}

/**
* 实体过滤规则
*
* 根据提供的组件过滤实体。
*/
abstract filter(): IMatcher;
abstract update(entities: E[]): void;
}

当然,同类型的系统我们也可以用系统组合器来组合使用。

System
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { ComblockSystem } from "./ComBlockSystem";

/**
* 系统组合器,用于将多个相同功能模块的系统逻辑上放在一起。System也可以嵌套System。
*/
export class System {
private _comblockSystems: ComblockSystem[] = [];
get comblockSystems() {
return this._comblockSystems;
}

add(system: System | ComblockSystem) {
if (system instanceof System) {
Array.prototype.push.apply(
this._comblockSystems,
system._comblockSystems
);
system._comblockSystems.length = 0;
} else {
this._comblockSystems.push(system as ComblockSystem);
}
return this;
}
}

所有的系统实现,最后我们都要用根系统来使用。

根系统生命周期提供一个 init 方法,来遍历所有系统并且调用对应系统的初始化;提供一个 execute 方法来遍历所有系统,执行每帧更新的内容;提供一个 clear 方法来遍历所有系统,调用系统销毁时的 onDestroy 方法。

然后就是 add 方法,传入系统组合器时我们会平铺其的所有系统并加入数组;传入系统时直接加入数组。

所有的方法在我们自定义根系统的时候都不需要修改,只需要在构造函数中 add 新的系统即可。

RootSystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { ComblockSystem } from "./ComBlockSystem";
import { System } from "./System";

/**
* System的root,对游戏中的System遍历从这里开始。
*
* 一个System组合中只能有一个RootSystem,可以有多个并行的RootSystem。
*/
export class RootSystem {
private executeSystemFlows: ComblockSystem[] = [];
private systemCnt: number = 0;

add(system: System | ComblockSystem) {
if (system instanceof System) {
// 将嵌套的System都“摊平”,放在根System中进行遍历,减少execute的频繁进入退出。
Array.prototype.push.apply(
this.executeSystemFlows,
system.comblockSystems
);
} else {
this.executeSystemFlows.push(system as ComblockSystem);
}
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());
}
}

管理器

ECS 的基本框架搭建好后,我们还需要一个管理器来管理 ECS 框架的一些操作。下面是本项目的分类,也可以根据需求自己设计。

ECS 管理器的核心功能简单概括如下图所示

组件注册

我们在使用组件之前,都要对组件进行注册。由上文我们知道组件有两种,一种是组件,一种是标签,因此注册也是分两种情况进行。

首先我们定义组件 id_compTid来记录组件 id,定义注册池_registerPool来判断是否存在同名组件或标签,定义组件池_comps保存组件类,定义组件缓存池_compPools来保存组件对象,定义 tag 池_tags来保存标签名,定义组件变化池_compAddOrRemove来保存变化的组件。

组件的注册用装饰器进行,我们定义两个类装饰器函数。

  • 注册组件:传入一个组件名,还有一个可选变量 canNew 表示是否可以实例化,一般我们默认 true,然后我们判断注册的组件是否重复,没重复的话给当前组件命名,组件 id 赋值后自增,可以实例化组件的话就把组件存入组件池,并开辟一个当前组件的缓存池数组以及组件变化池数组。最后在注册池里标记为已注册。

    注册组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    /**
    * 注册组件
    * @param compName
    * @param canNew
    * @returns
    */
    public register<T>(compName: string, canNew: boolean = true) {
    return function (comp: CompType<T>) {
    if (comp.tid == -1) {
    let manager = ECSManager.getInst();
    if (manager._registerPool[compName]) {
    console.warn("组件与标签重名:", compName);
    } else {
    comp.tid = manager._compTid++;
    comp.compName = compName;
    if (canNew) {
    manager._comps.push(comp);
    manager._compPools.set(comp.tid, []);
    }
    else {
    manager._comps.push(null);
    }
    manager._compAddOrRemove.set(comp.tid, []);
    console.log("组件" + compName + "注册成功:", comp.tid)
    manager._registerPool[compName] = true;
    }
    } else {
    console.log("组件已注册");
    }
    }
    }
  • 注册标签:注册标签我们不传入任何参数,我们只需要遍历标签类的所有属性,判断是否已注册,未注册的情况我们给标签 id 赋值并自增,然后和组件一样放入组件池并开辟组件缓存池。标签要额外放到标签池以方便标签的操作。最后也要在注册池里标注为已注册。

    注册tag
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 注册tag
    * @returns
    */
    public registerTag() {
    return function (_class: any) {
    let manager = ECSManager.getInst();
    let tid = manager._compTid;
    for (let k in _class) {
    if (manager._registerPool[k]) {
    console.warn("标签与组件重名:", k);
    } else {
    tid = manager._compTid++;
    _class[k] = tid;
    manager._comps.push(tid);
    manager._compPools.set(tid, []);
    manager._compAddOrRemove.set(tid, []);
    manager._tags.set(tid, k);
    console.log("标签" + k + "注册成功:", tid)
    manager._registerPool[k] = true;
    }
    }
    }
    }

组件功能

组件的功能很简单,只是运用了对象池的概念。

组件功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//-----tag-----

/**
* 是否有tag
* @param id
* @returns
*/
public hasTag(id: number) {
return this._tags.has(id);
}

/**
* 获得tag
* @param id
* @returns
*/
public getTag(id: number): string {
return this._tags.get(id) as string;
}

//-----tag-----

//-----组件-----

/**
* 回收组件
* @param id 组件id
* @param comp 组件实例
*/
public recycle(id: number, comp: IComp) {
this._compPools.get(id)?.push(comp);
}

/**
* 创建组件
* @param comp
* @returns
*/
public createComp<T>(comp: CompType<T>) {
if (!this._comps[comp.tid]) {
console.error("未找到组件" + comp.compName)
}
let compInstance = this._compPools.get(comp.tid)?.pop() || new this._comps[comp.tid];
return compInstance;
}

/**
* 获得所有组件
* @returns
*/
public getComps() {
return this._comps;
}

//-----组件-----

实体功能

实体的功能也很简单,我们定义一个实体池_eid2Entity用于保存当前所有的实体实例,定义一个实体缓存池_eneityPool用来当实体的对象池。

实体功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 创建实体
* @returns
*/
public createEntity<E extends Entity = Entity>(): E {
let entity = this._eneityPool.pop();
if (!entity) {
entity = new Entity();
entity.eid = this._entityId++;
}
this._eid2Entity.set(entity.eid, entity);
return entity as E;
}

/**
* 移除实体
* @param id 实体id
* @param entity 实体
*/
public removeEntity(id: number, entity: Entity) {
if (this._eid2Entity.has(id)) {
this._eneityPool.push(entity);
this._eid2Entity.delete(id);
} else {
console.warn("试图销毁不存在的实体");
}
}

/**
* 根据eid获取实体
* @param eid
* @returns
*/
public getEntityByEid(eid: number) {
return this._eid2Entity.get(eid);
}

/**
* 获得当前活动实体数量
* @returns
*/
public activeEntityCount() {
return this._eid2Entity.size;
}

系统功能

系统功能包括群组功能,过滤功能和组件变化的通知及清除功能。

组件变化的监听是在创建群组的时候就绑定好的。通过获取对应组件 id 的组件变化数组,存入群组的监听函数,就可以在每次组件变化的时候通知所有监听该组件的群组执行对应的函数。

其他功能就是对上文现有功能的再包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//-----群组-----

/**
* 创建群组
* @param matcher
*/
public createGroup<E extends Entity = Entity>(matcher: IMatcher): Group<E> {
let group = this._groups.get(matcher.mid);
if (!group) {
group = new Group(matcher);
this._groups.set(matcher.mid, group);
let careCompIds = matcher.indices;
for (let i = 0, len = careCompIds.length; i < len; i++) {
let child = this._compAddOrRemove.get(careCompIds[i]);
if (!child) {
child = [];
this._compAddOrRemove.set(careCompIds[i], child);
}
child.push(group.onComponentAddOrRemove.bind(group));
}
}
return group as unknown as Group<E>;
}

public query(matcher: IMatcher) {
let group = this._groups.get(matcher.mid);
if (!group) {
group = this.createGroup(matcher);
this._eid2Entity.forEach(group.onComponentAddOrRemove, group);
}
return group.matchEntities;
}

//-----群组-----

/**
* 实体身上组件有增删操作,广播通知对应的观察者。
* @param entity 实体对象
* @param componentTypeId 组件类型id
*/
public broadcastCompAddOrRemove(entity: Entity, componentTypeId: number) {
let events = this._compAddOrRemove.get(componentTypeId);
if (events) {
for (let i = events.length - 1; i >= 0; i--) {
events![i](entity);
}
}
}

/**
* 清除
*/
public clear() {
this._eid2Entity.forEach((entity) => {
entity.removeSelf();
});
this._groups.forEach((group) => {
group.clear();
});
this._compAddOrRemove.forEach(callbackLst => {
callbackLst.length = 0;
});
this._eid2Entity.clear();
this._groups.clear();
}

//-----过滤-----

public getMatherId() {
return this._matcherId++;
}

/**
* 判断是否拥有所有对应组件
* @param args
* @returns
*/
public allOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().allOf(...args);
}

/**
* 判断是否拥有任意对应组件
* @param args
* @returns
*/
public anyOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().anyOf(...args);
}

/**
* 判断是否只包含所有对应组件
* @param args
* @returns
*/
public onlyOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().onlyOf(...args);
}

/**
* 判断是否不包含任意对应组件
* @param args
* @returns
*/
public excludeOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().excludeOf();
}

//-----过滤-----

总的 ECS 管理类代码如下

ECSManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
import { Comp } from "./Comp";
import Entity from "./Entity";
import Group from "./Group";
import { CompType } from "./Interface/CompType";
import IComp from "./Interface/IComp";
import { IMatcher } from "./Interface/IMatcher";
import { Matcher } from "./Mathcer";

export type CompAddOrRemove = (entity: Entity) => void;
export type CompTypeUnion<T> = CompType<T> | number;
export default class ECSManager {
private static _instance: ECSManager | null = null;

private _compTid = 0;

private _entityId = 1;

private _matcherId = 1;

/**
* 注册池,判断是否存在同名组件或标签
*/
private _registerPool = {};

/**
* 组件注册池
*/
private _comps: any[] = [];
/**
* 组件缓存池
*/
private _compPools: Map<number, Comp[]> = new Map();

/**
* tag池
*/
private _tags: Map<number, string> = new Map();

private _compAddOrRemove: Map<number, CompAddOrRemove[]> = new Map();
/**
* 实体池
*/
private _eid2Entity: Map<number, Entity> = new Map();
/**
* 实体缓存池
*/
private _eneityPool: Entity[] = [];

private _groups: Map<number, Group> = new Map();

public static getInst(): ECSManager {
if (!this._instance) {
this._instance = new ECSManager();
}
return this._instance;
}

public getCompTid() {
return this._compTid;
}

//-----注册-----

/**
* 注册组件
* @param compName
* @param canNew
* @returns
*/
public register<T>(compName: string, canNew: boolean = true) {
return function (comp: CompType<T>) {
if (comp.tid == -1) {
let manager = ECSManager.getInst();
if (manager._registerPool[compName]) {
console.warn("组件与标签重名:", compName);
} else {
comp.tid = manager._compTid++;
comp.compName = compName;
if (canNew) {
manager._comps.push(comp);
manager._compPools.set(comp.tid, []);
} else {
manager._comps.push(null);
}
manager._compAddOrRemove.set(comp.tid, []);
console.log("组件" + compName + "注册成功:", comp.tid);
manager._registerPool[compName] = true;
}
} else {
console.log("组件已注册");
}
};
}

/**
* 注册tag
* @returns
*/
public registerTag() {
return function (_class: any) {
let manager = ECSManager.getInst();
let tid = manager._compTid;
for (let k in _class) {
if (manager._registerPool[k]) {
console.warn("标签与组件重名:", k);
} else {
tid = manager._compTid++;
_class[k] = tid;
manager._comps.push(tid);
manager._compPools.set(tid, []);
manager._compAddOrRemove.set(tid, []);
manager._tags.set(tid, k);
console.log("标签" + k + "注册成功:", tid);
manager._registerPool[k] = true;
}
}
};
}

//-----注册-----

//-----tag-----

/**
* 是否有tag
* @param id
* @returns
*/
public hasTag(id: number) {
return this._tags.has(id);
}

/**
* 获得tag
* @param id
* @returns
*/
public getTag(id: number): string {
return this._tags.get(id) as string;
}

//-----tag-----

//-----组件-----

/**
* 回收组件
* @param id 组件id
* @param comp 组件实例
*/
public recycle(id: number, comp: IComp) {
this._compPools.get(id)?.push(comp);
}

/**
* 创建组件
* @param comp
* @returns
*/
public createComp<T>(comp: CompType<T>) {
if (!this._comps[comp.tid]) {
console.error("未找到组件" + comp.compName);
}
let compInstance =
this._compPools.get(comp.tid)?.pop() || new this._comps[comp.tid]();
return compInstance;
}

/**
* 获得所有组件
* @returns
*/
public getComps() {
return this._comps;
}

/**
* 实体身上组件有增删操作,广播通知对应的观察者。
* @param entity 实体对象
* @param componentTypeId 组件类型id
*/
public broadcastCompAddOrRemove(entity: Entity, componentTypeId: number) {
let events = this._compAddOrRemove.get(componentTypeId);
if (events) {
for (let i = events.length - 1; i >= 0; i--) {
events![i](entity);
}
}
}

//-----组件-----

//-----实体-----

/**
* 创建实体
* @returns
*/
public createEntity<E extends Entity = Entity>(): E {
let entity = this._eneityPool.pop();
if (!entity) {
entity = new Entity();
entity.eid = this._entityId++;
}
this._eid2Entity.set(entity.eid, entity);
return entity as E;
}

/**
* 移除实体
* @param id 实体id
* @param entity 实体
*/
public removeEntity(id: number, entity: Entity) {
if (this._eid2Entity.has(id)) {
this._eneityPool.push(entity);
this._eid2Entity.delete(id);
} else {
console.warn("试图销毁不存在的实体");
}
}

/**
* 根据eid获取实体
* @param eid
* @returns
*/
public getEntityByEid(eid: number) {
return this._eid2Entity.get(eid);
}

/**
* 获得当前活动实体数量
* @returns
*/
public activeEntityCount() {
return this._eid2Entity.size;
}

/**
* 清除
*/
public clear() {
this._eid2Entity.forEach((entity) => {
entity.removeSelf();
});
this._groups.forEach((group) => {
group.clear();
});
this._compAddOrRemove.forEach((callbackLst) => {
callbackLst.length = 0;
});
this._eid2Entity.clear();
this._groups.clear();
}

//-----实体-----

//-----群组-----

/**
* 创建群组
* @param matcher
*/
public createGroup<E extends Entity = Entity>(matcher: IMatcher): Group<E> {
let group = this._groups.get(matcher.mid);
if (!group) {
group = new Group(matcher);
this._groups.set(matcher.mid, group);
let careCompIds = matcher.indices;
for (let i = 0, len = careCompIds.length; i < len; i++) {
let child = this._compAddOrRemove.get(careCompIds[i]);
if (!child) {
child = [];
this._compAddOrRemove.set(careCompIds[i], child);
}
child.push(group.onComponentAddOrRemove.bind(group));
}
}
return group as unknown as Group<E>;
}

public query(matcher: IMatcher) {
let group = this._groups.get(matcher.mid);
if (!group) {
group = this.createGroup(matcher);
this._eid2Entity.forEach(group.onComponentAddOrRemove, group);
}
return group.matchEntities;
}

//-----群组-----

//-----过滤-----

public getMatherId() {
return this._matcherId++;
}

/**
* 判断是否拥有所有对应组件
* @param args
* @returns
*/
public allOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().allOf(...args);
}

/**
* 判断是否拥有任意对应组件
* @param args
* @returns
*/
public anyOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().anyOf(...args);
}

/**
* 判断是否只包含所有对应组件
* @param args
* @returns
*/
public onlyOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().onlyOf(...args);
}

/**
* 判断是否不包含任意对应组件
* @param args
* @returns
*/
public excludeOf(...args: CompTypeUnion<IComp>[]) {
return new Matcher().excludeOf();
}

//-----过滤-----
}

使用方法

Tag示例
1
2
3
4
5
6
7
import ECSManager from "../../../ECS/ECSManager";

@ECSManager.getInst().registerTag()
export default class StatusTags {
public static StatusA = 0;
public static StatusB = 1;
}
Comp示例
1
2
3
4
5
6
7
8
9
10
11
12
import { Comp } from "../../../ECS/Comp";
import ECSManager from "../../../ECS/ECSManager";

@ECSManager.getInst().register("Transform")
export default class TransformComp extends Comp {
/** 坐标 */
public position: Laya.Vector3 = new Laya.Vector3();

reset(): void {
this.position.set(0, 0, 0);
}
}
Entity示例
1
2
3
4
5
6
import Entity from "../../../ECS/Entity";
import TransformComp from "../Comp/TransformComp";

export default class RoleEntity extends Entity {
public Transform: TransformComp;
}
ComblockSystem示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { ComblockSystem } from "../../../ECS/ComBlockSystem";
import ECSManager from "../../../ECS/ECSManager";
import { IMatcher } from "../../../ECS/Interface/IMatcher";
import { IEntityEnterSystem } from "../../../ECS/Interface/ISystem";
import ServantComp from "../Comp/ServantComp";
import WorkerComp from "../Comp/WorkerComp";
import RoleEntity from "../Entity/RoleEntity";

/**
* 工作系统
*/
export default class WorkSystem
extends ComblockSystem<RoleEntity>
implements IEntityEnterSystem<RoleEntity>
{
entityEnter(entities: RoleEntity[]): void {
for (let e of entities) {
}
}
filter(): IMatcher {
return ECSManager.getInst().anyOf(WorkerComp, ServantComp);
}
update(entities: RoleEntity[]): void {
for (let e of entities) {
}
}
}
RootSystem示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Globals from "../../../Config/Globals";
import { RootSystem } from "../../../ECS/RootSystem";
import CarShopSystem from "../System/CarShopSystem";
import CarSystem from "../System/CarSystem";
import CustomSystem from "../System/CustomSystem";
import DollSystem from "../System/DollSystem";
import MoveSystem from "../System/MoveSystem";
import NpcSystem from "../System/NpcSystem";
import PathFindingSystem from "../System/PathFindingSystem";
import WorkSystem from "../System/WorkSystem";

/**
* 慢速根系统
*/
export default class RootSlowSystem extends RootSystem {
constructor() {
super();
this.add(new WorkSystem());
}
}
根系统调用示例
1
2
3
4
5
6
7
8
9
10
11
12
onAwake(){
this._rootSlowSystem = new RootSlowSystem();
this._rootSlowSystem.init();
}

onUpdate(){
this._rootSlowSystem.execute(Laya.timer.delta);
}

onDisable() {
this._rootSlowSystem.clear();
}

代码

由于本项目代码较多,因此请移步 GitHub 查看详细代码。

评论