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

简介

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

原理

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

大致示意图如下:

组件

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

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

因此我们可以定义一个组件抽象类 Comp,Comp 类不关心组件的具体信息,只关心组件的基本属性、组件的初始化和重置方法。

Comp
1
2
3
4
5
6
7
8
9
10

public abstract class Comp
{
public int compId { get; set; }
public string compName { get; set; }

public Comp() { Reset(); }
public abstract void Reset();
}

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

实体

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

掩码工具

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

这里我们使用一个二进制数组来保存,用数组的目的是如果组件数超过二进制数大小,就在数组增加一个二进制数来保存。在 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 就是往哪个插槽插入组件,这样就能表示保存的组件了。

ECSMask
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
using System.Collections.Generic;
using UnityEngine;

public class ECSMask
{
private List<int> _mask;

private int _size;

public ECSMask() {
_size = Mathf.CeilToInt(ECSManager.Ins().GetTotalCompNum() / 31f);
_mask = new List<int>(_size);
int data = 0;
_mask.Add(data);
}

public void Set(int id)
{
_mask[id / 31] |= (1 << (id % 31));
}

public void Remove(int id)
{
_mask[id / 31] &= ~(1 << (id % 31));
}

public bool Has(int id)
{
return (_mask[id / 31] & (1 << (id % 31))) > 0;
}

//和其他mask比较

public bool Or(ECSMask other)
{
for (int i = 0; i < _size; ++i)
{
if ((_mask[i] & other._mask[i]) > 0)
{
return true;
}
}
return false;
}

public bool And(ECSMask other)
{
for (int i = 0; i < _size; ++i)
{
if ((_mask[i] & other._mask[i]) != _mask[i])
{
return false;
}
}
return true;
}

public void Clear()
{
for(int i = 0; i < _size; ++i)
{
_mask[i] = 0;
}
}
}

筛选工具

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

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

判断是否匹配只需要调用掩码定义的规则即可。

ECSRule
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

using System;
using System.Collections.Generic;

/// <summary>
/// ECS规则匹配类型
/// </summary>
public enum ECSRuleType
{
AllOf,
AnyOf,
ExcludeOf
}

public class ECSRule
{
int compId = -1;

private ECSMask _mask = new ECSMask();

private ECSRuleType _type;

private List<int> _compIds = new List<int>();

public ECSRule(ECSRuleType ecsType, params Type[] comps)
{
_type = ecsType;
foreach (var comp in comps)
{
compId = ECSManager.Ins().GetCompId(comp);

if (compId == -1)
{
ConsoleUtils.Warn("存在未注册组件", compId);
}
else
{
_mask.Set(compId);
_compIds.Add(compId);
}
}

if(_compIds.Count > 0)
{
_compIds.Sort((a,b) => { return a.CompareTo(b); });
}
}

public List<int> GetCompIds()
{
return _compIds;
}

/// <summary>
/// 是否匹配
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool isMatch(Entity entity)
{
switch (_type)
{
case ECSRuleType.AllOf:
return _mask.And(entity.mask);
case ECSRuleType.AnyOf:
return _mask.Or(entity.mask);
case ECSRuleType.ExcludeOf:
return !_mask.Or(entity.mask);
}
return false;
}
}

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

ECSMatcher
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
using System;
using System.Collections.Generic;
using System.Linq;

public class ECSMatcher
{
public static int _matcherId = 0;

public int _mid = _matcherId++;

private List<ECSRule> _rules = new List<ECSRule>();

public ECSMatcher AnyOf(params Type[] comps) {
_rules.Add(new ECSRule(ECSRuleType.AnyOf, comps));
return this;
}

public ECSMatcher AllOf(params Type[] comps)
{
_rules.Add(new ECSRule(ECSRuleType.AllOf, comps));
return this;
}

public ECSMatcher ExcludeOf(params Type[] comps)
{
_rules.Add(new ECSRule(ECSRuleType.ExcludeOf, comps));
return this;
}

public ECSMatcher OnlyOf(params Type[] comps)
{
_rules.Add(new ECSRule(ECSRuleType.AllOf, comps));
List<Type> compList = new List<Type>();
foreach (var comp in comps)
{
if (!comps.Contains(comp))
{
compList.Add(comp);
}
}
Type[] compArray = compList.ToArray();
_rules.Add(new ECSRule(ECSRuleType.ExcludeOf,compArray));
return this;
}

public bool IsMatch(Entity entity)
{
foreach (var rule in _rules)
{
if (!rule.isMatch(entity))
{
return false;
}
}
return true;
}

public List<int> GetCompIds()
{
List<int> result = null;

foreach (var rule in _rules)
{
List<int> ids = rule.GetCompIds();
if(result == null)
{
result = ids;
}
else
{
result.AddRange(ids);
}
}
return result;
}
}

大致的关系如下图所示

实体类

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

大概的思路如图所示(本图为 TS 版本,Unity 版本没有重新添加组件,也没有设置组件实例为 null)

首先是组件的增加。

我们设置一个字典 _compsInEntity 来保存当前实体的组件,设置一个字典 _compsRemoved 用来保存已经移除但没有回收的组件。

首先我们判断组件是否已注册,已注册并且已存在的情况下返回当前组件,否则就创建新组件,然后把组件添加到实体掩码,并且添加到已保存的组件字典中。

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

然后是组件的移除。

组件的移除和添加类似。

首先判断组件是否存在,是的话标记存在,取出当前组件的实例,然后判断是否需要回收,需要回收的话执行组件初始化操作并回收,不需要的话就把组件放到移除列表中。

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

接着是组件的查找。

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

最后是实体的移除。

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

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
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

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using static Unity.Burst.Intrinsics.X86.Avx;

public class Entity
{
public int id { get; set; }

public string name { get; set; }

public ECSMask mask { get; set; }

private Dictionary<Type, Comp> _compsInEntity = new Dictionary<Type, Comp>();

private Dictionary<Type, Comp> _compsRemoved = new Dictionary<Type, Comp>();

public Entity() {
mask = new ECSMask();
}

/// <summary>
/// 添加组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comp"></param>
/// <returns></returns>
public T Add<T>() where T : Comp
{
//Comp comp;
Type comp = typeof(T);
Comp instance;
int compId = ECSManager.Ins().GetCompId(comp);
if (mask.Has(compId))
{
instance = _compsInEntity[comp];
return instance as T;
}

if (_compsRemoved.ContainsKey(comp))
{
instance = _compsRemoved[comp];
_compsRemoved.Remove(comp);
}
else
{
instance = ECSManager.Ins().CreateComp(comp);
}


if (instance == null)
{
ConsoleUtils.Warn("组件未注册");
}
else
{
_compsInEntity.Add(comp, instance);

mask.Set(compId);

ECSManager.Ins().CompChangeBroadcast(this, compId);
}

return instance as T;
}

/// <summary>
/// 添加组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comp"></param>
/// <returns></returns>
public void Add(Type comp)
{
//Comp comp;
Comp instance;
int compId = ECSManager.Ins().GetCompId(comp);
if (mask.Has(compId))
{
return;
}

if (_compsRemoved.ContainsKey(comp))
{
instance = _compsRemoved[comp];
_compsRemoved.Remove(comp);
}
else
{
instance = ECSManager.Ins().CreateComp(comp);
}


if (instance == null)
{
ConsoleUtils.Warn("组件未注册");
}
else
{
_compsInEntity.Add(comp, instance);

mask.Set(compId);

ECSManager.Ins().CompChangeBroadcast(this, compId);
}
}

/// <summary>
/// 添加组件
/// </summary>
/// <param name="compIds"></param>
public void AddComps(params Type[] comps)
{
foreach (Type comp in comps)
{
Add(comp);
}
}

/// <summary>
/// 获取组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="compId"></param>
/// <returns></returns>
public T Get<T>() where T : Comp
{
Comp instance;
_compsInEntity.TryGetValue(typeof(T), out instance);
return instance as T;
}

/// <summary>
/// 是否有组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool Has<T>()
{
Type comp = typeof(T);
int compId = ECSManager.Ins().GetCompId(comp);
return mask.Has(compId);
}

/// <summary>
/// 移除组件
/// </summary>
/// <param name="compId"></param>
/// <param name="recycle"></param>
public void Remove<T>(bool recycle = true)
{
Type comp = typeof(T);
int compId = ECSManager.Ins().GetCompId(comp);
if (mask.Has(compId))
{
Comp instance = _compsInEntity[comp];

if (recycle)
{
ECSManager.Ins().RemoveComp(instance);
}
else
{
_compsRemoved.Add(comp, instance);
}

_compsInEntity.Remove(comp);
mask.Remove(compId);
ECSManager.Ins().CompChangeBroadcast(this, compId);
}
else
{
ConsoleUtils.Warn("组件不存在");
}
}

/// <summary>
/// 移除组件
/// </summary>
/// <param name="compId"></param>
/// <param name="recycle"></param>
public void Remove(Type comp, bool recycle = true)
{
int compId = ECSManager.Ins().GetCompId(comp);
if (mask.Has(compId))
{
Comp instance = _compsInEntity[comp];

if (recycle)
{
ECSManager.Ins().RemoveComp(instance);
}
else
{
_compsRemoved.Add(comp, instance);
}

_compsInEntity.Remove(comp);
mask.Remove(compId);
ECSManager.Ins().CompChangeBroadcast(this, compId);
}
else
{
ConsoleUtils.Warn("组件不存在");
}
}

public void RemoveComps(bool recycle, params Type[] comps)
{
foreach (Type comp in comps)
{
Remove(comp, recycle);
}
}

public void Clear()
{
foreach(var data in _compsInEntity.ToList())
{
Remove(data.Key);
}

foreach(var data in _compsRemoved.ToList())
{
ECSManager.Ins().RemoveComp(data.Value);
}

_compsInEntity.Clear();
_compsRemoved.Clear();
mask.Clear();
}
}

系统

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

系统的结构如下图所示

群组

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

我们保存一个字典 _entities 用于在系统运行时遍历当前组内的所有实体。

我们定义两个委托,用于监听实体变化,一个是实体进入该组,一个是实体移出该组:

1
2
3
public delegate void EnterListener(Entity entity);

public delegate void RemoveListener(Entity entity);

然后我们通过如下方法添加和移除委托:

1
2
3
4
5
6
7
8
9
10
11
public void AddEntityListener(EnterListener enter, RemoveListener remove)
{
_enterListener += enter;
_removeListener += remove;
}

public void RemoveEntityListener(EnterListener enter, RemoveListener remove)
{
_enterListener -= enter;
_removeListener -= remove;
}

整个组的类如下:

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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public delegate void EnterListener(Entity entity);

public delegate void RemoveListener(Entity entity);

public class Group
{
private ECSMatcher _matcher;

private EnterListener _enterListener;

private RemoveListener _removeListener;

private List<Entity> _entities = new List<Entity>();

public Group(ECSMatcher matcher)
{
_matcher = matcher;
}

public void OnCompChange(Entity entity)
{
if(_matcher.IsMatch(entity))
{
_enterListener.Invoke(entity);
_entities.Add(entity);
}
else
{
_removeListener.Invoke(entity);
_entities.Remove(entity);
}
}

public void AddEntityListener(EnterListener enter, RemoveListener remove)
{
_enterListener += enter;
_removeListener += remove;
}

public void RemoveEntityListener(EnterListener enter, RemoveListener remove)
{
_enterListener -= enter;
_removeListener -= remove;
}

public List<Entity> GetEntities()
{
return _entities;
}

public void Clear()
{
_entities.Clear();
}
}

系统实现

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

系统的具体实现核心目标是实现实体进入时的逻辑处理,每帧逻辑处理和实体移除时的逻辑处理。

首先我们定义两个数组 _enters_removes 用于保存进入和移出当前系统的实体。

然后我们通过如下方法注册和反注册实体进入和移出数组的监听:

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

public ECSSystem()
{
_group = ECSManager.Ins().CreateGroup(Filter());
_group.AddEntityListener(EntityEnter, EntityRemove);
}

private void EntityRemove(Entity entity)
{
List<Entity> entities = _group.GetEntities();
if (entities.Contains(entity))
{
_removes.Add(entity);
}
}

private void EntityEnter(Entity entity)
{
_enters.Add(entity);
}

public virtual void OnDestroy()
{
_group.RemoveEntityListener(EntityEnter, EntityRemove);
}

通过在构造函数中注册监听,每当组内实体发生变化的时候,组内注册的所有系统的实体进入和移出数组的方法, EntityEnterEntityRemove,都会执行,也就是把实体添加或移除到当前系统中。

然后就是系统的遍历处理了。

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
using System.Collections.Generic;

public abstract class ECSSystem
{
private Group _group;

protected float _dt = 0;

private List<Entity> _enters = new List<Entity>();

private List<Entity> _removes = new List<Entity>();

public ECSSystem()
{
_group = ECSManager.Ins().CreateGroup(Filter());
_group.AddEntityListener(EntityEnter, EntityRemove);
}

public void Execute(float dt)
{
_dt = dt;

List<Entity> entities = _group.GetEntities();

OnEnter(_enters);
OnUpdate(entities);
OnRemove(_removes);

_removes.Clear();
_enters.Clear();
}

public void DrawGizmos()
{
List<Entity> entities = _group.GetEntities();
OnDrawGizmos(entities);
}

private void EntityEnter(Entity entity)
{
_enters.Add(entity);
}

private void EntityRemove(Entity entity)
{
List<Entity> entities = _group.GetEntities();
if (entities.Contains(entity))
{
_removes.Add(entity);
}
}

public virtual void Init() { }
public virtual void OnDestroy()
{
_group.RemoveEntityListener(EntityEnter, EntityRemove);
}
public virtual void OnEnter(List<Entity> entities) { }
public virtual void OnRemove(List<Entity> entities) { }

public virtual void OnDrawGizmos(List<Entity> entities) { }
public abstract ECSMatcher Filter();
public abstract void OnUpdate(List<Entity> entities);
}

在核心处理函数 Execute 中我们按照顺序处理 OnEnterOnUpdateOnRemove ,然后我们把 _enters_removes 清空,防止多次执行。

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

世界生命周期提供一个 Init 方法,来遍历所有系统并且调用对应系统的初始化;提供一个 Update 方法来遍历所有系统,执行每帧更新的内容;提供一个 Clear 方法来遍历所有系统,调用系统销毁时的 OnDestroy 方法。

然后就是 Add 方法,传入系统时直接加入 _systems 。添加新系统我们需要重写 SystemAdd 方法,所有的添加行为都在这个方法里执行。

所有的方法在我们自定义世界的时候都不需要修改,只需要在 SystemAdd 中 Add 新的系统即可。

ECSWorld
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
using System.Collections.Generic;
using UnityEngine;

public abstract class ECSWorld
{
private List<ECSSystem> _systems = new List<ECSSystem>();

private float _time = 0;

public ECSWorld() {
SystemAdd();
Init();
}

public void Init()
{
_time = Time.time;
foreach (var system in _systems)
{
system.Init();
}
}

public abstract void SystemAdd();

public void Update()
{
float dt = Time.time - _time;
foreach (var system in _systems)
{
system.Execute(dt);
}
_time += dt;
//ConsoleUtils.Log("间隔时间",dt);
}

public void DrawGrizmos()
{
foreach (var system in _systems)
{
system.DrawGizmos();
}
}

public void Add(ECSSystem system)
{
_systems.Add(system);
}

public void Remove(ECSSystem system)
{
_systems.Remove(system);
}

public void Clear()
{
_systems.Clear();
}
}

管理器

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

ECS 管理器的核心功能简单概括如下图所示(Unity 版本取消标签,取消判断实例化并直接走是分支)

组件注册

我们在使用组件之前,都要对组件进行注册。

我们通过特性 Attribute 来实现组件类的预注册。

CompRegister
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;

[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)
]
public class CompRegister : Attribute
{
public CompRegister(Type type)
{
Register(type);
}

public void Register(Type type)
{
int id = ECSManager.Ins().GetCompId();
ECSManager.Ins().CompRegister(type, id);
}
}
启动Attribute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void Init()
{
Assembly assembly = Assembly.GetAssembly(typeof(ECSManager));
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
//获取自定义特性的过程中自动执行构造函数
var attr = type.GetCustomAttribute<CompRegister>();
if (attr != null)
{
_total++;
}
}
}

组件功能

组件的功能很简单,只是运用了对象池的概念。我们定义一个实体池_comps用于保存当前所有的组件 id,定义一个组件实例缓存池_compPool用来当组件的对象池。

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
/// <summary>
/// 注册组件
/// </summary>
/// <param name="comp"></param>
/// <param name="compId"></param>
public void CompRegister(Type comp, int compId)
{
_comps.Add(comp, compId);
_compPool.Add(compId, new Stack<Comp>());
ConsoleUtils.Log("注册类:", compId, comp);
}

/// <summary>
/// 创建组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comp"></param>
/// <returns></returns>
public Comp CreateComp(Type comp)
{
if (!_comps.ContainsKey(comp))
{
ConsoleUtils.Warn("未找到对应组件:", comp);
return null;
}
else
{
int compId = _comps[comp];
Comp instance;
Stack<Comp> queue;
_compPool.TryGetValue(compId, out queue);
if (queue == null || queue.Count == 0)
{
instance = ClassFactory.CreateClass<Comp>(comp);
instance.compId = compId;
}
else
{
instance = queue.Pop();
}

return instance;
}
}

/// <summary>
/// 回收组件
/// </summary>
/// <param name="comp"></param>
public void RemoveComp(Comp comp)
{
Stack<Comp> queue;
_compPool.TryGetValue(comp.compId, out queue);
if (queue == null)
{
queue = new Stack<Comp>();
_compPool.Add(comp.compId, queue);
}

comp.Reset();
queue.Push(comp);
}

实体功能

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

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
public Entity CreateEntity()
{
if (_entityPool.Count == 0)
{
Entity entity = new Entity();
entity.id = _entityId++;
_entities.Add(entity.id,entity);
return entity;
}
else
{
Entity entity = _entityPool.Pop();
_entities.Add(entity.id, entity);
return _entityPool.Pop();
}
}

public Entity GetEntity(int id)
{
Entity entity;
_entities.TryGetValue(id, out entity);
return entity;
}

public void RemoveEntity(Entity entity)
{
entity.Clear();
_entityPool.Push(entity);
_entities.Remove(entity.id);
}

系统功能

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

组件变化的监听是在创建群组的时候就绑定好的。通过获取对应组件 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
public Group CreateGroup(ECSMatcher matcher)
{
Group res;
_groups.TryGetValue(matcher, out res);
if (res == null)
{
res = new Group(matcher);
_groups.Add(matcher, res);
List<int> compIds = matcher.GetCompIds();
foreach (int compId in compIds)
{
List<Action<Entity>> actionList;
_onCompChangeAction.TryGetValue(compId, out actionList);
if (actionList == null)
{
actionList = new List<Action<Entity>>();
_onCompChangeAction.Add(compId, actionList);
}
actionList.Add(res.OnCompChange);
}
}
return res;
}

public void CompChangeBroadcast(Entity entity, int compId)
{
List<Action<Entity>> actionList;
_onCompChangeAction.TryGetValue(compId, out actionList);
if (actionList != null)
{
foreach (Action<Entity> action in actionList)
{
action.Invoke(entity);
}
}
}

public ECSMatcher AnyOf(params Type[] comps)
{
return new ECSMatcher().AnyOf(comps);
}

public ECSMatcher AllOf(params Type[] comps)
{
return new ECSMatcher().AllOf(comps);
}

public ECSMatcher ExcludeOf(params Type[] comps)
{
return new ECSMatcher().ExcludeOf(comps);
}

public ECSMatcher OnlyOf(params Type[] comps)
{
return new ECSMatcher().OnlyOf(comps);
}

总的 ECS 管理类代码如下:

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



using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Security.Cryptography;
using Unity.VisualScripting;

public class ECSManager : Singleton<ECSManager>
{
private int _compId = 1;

private int _entityId = 0;

private int _total = 0;

private Dictionary<Type, int> _comps = new Dictionary<Type, int>();

private Dictionary<ECSMatcher, Group> _groups = new Dictionary<ECSMatcher, Group>();
/// <summary>
/// 组件变化监听字典
/// </summary>
private Dictionary<int, List<Action<Entity>>> _onCompChangeAction = new Dictionary<int, List<Action<Entity>>>();
/// <summary>
/// 实体对象池
/// </summary>
private Stack<Entity> _entityPool = new Stack<Entity>();

/// <summary>
/// 实体对象
/// </summary>
private Dictionary<int, Entity> _entities = new Dictionary<int, Entity>();

private Dictionary<int, Stack<Comp>> _compPool = new Dictionary<int, Stack<Comp>>();

public void Init()
{
Assembly assembly = Assembly.GetAssembly(typeof(ECSManager));
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
//获取自定义特性的过程中自动执行构造函数
var attr = type.GetCustomAttribute<CompRegister>();
if(attr != null)
{
_total++;
}
}
}

public void SetTotalCompNum(int num)
{
_total = num;
}

public int GetTotalCompNum()
{
return _total;
}

public int GetCompId()
{
return _compId++;
}

public int GetCompId(Type comp)
{
return _comps[comp];
}

public Dictionary<Type, int> GetAllCompTypes()
{
return _comps;
}

/// <summary>
/// 注册组件
/// </summary>
/// <param name="comp"></param>
/// <param name="compId"></param>
public void CompRegister(Type comp, int compId)
{
_comps.Add(comp, compId);
_compPool.Add(compId, new Stack<Comp>());
ConsoleUtils.Log("注册类:", compId, comp);
}

/// <summary>
/// 创建组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comp"></param>
/// <returns></returns>
public Comp CreateComp(Type comp)
{
if (!_comps.ContainsKey(comp))
{
ConsoleUtils.Warn("未找到对应组件:", comp);
return null;
}
else
{
int compId = _comps[comp];
Comp instance;
Stack<Comp> queue;
_compPool.TryGetValue(compId, out queue);
if (queue == null || queue.Count == 0)
{
instance = ClassFactory.CreateClass<Comp>(comp);
instance.compId = compId;
}
else
{
instance = queue.Pop();
}

return instance;
}
}

/// <summary>
/// 回收组件
/// </summary>
/// <param name="comp"></param>
public void RemoveComp(Comp comp)
{
Stack<Comp> queue;
_compPool.TryGetValue(comp.compId, out queue);
if (queue == null)
{
queue = new Stack<Comp>();
_compPool.Add(comp.compId, queue);
}

comp.Reset();
queue.Push(comp);
}

public Group CreateGroup(ECSMatcher matcher)
{
Group res;
_groups.TryGetValue(matcher, out res);
if (res == null)
{
res = new Group(matcher);
_groups.Add(matcher, res);
List<int> compIds = matcher.GetCompIds();
foreach (int compId in compIds)
{
List<Action<Entity>> actionList;
_onCompChangeAction.TryGetValue(compId, out actionList);
if (actionList == null)
{
actionList = new List<Action<Entity>>();
_onCompChangeAction.Add(compId, actionList);
}
actionList.Add(res.OnCompChange);
}
}
return res;
}

public Entity CreateEntity()
{
if (_entityPool.Count == 0)
{
Entity entity = new Entity();
entity.id = _entityId++;
_entities.Add(entity.id,entity);
return entity;
}
else
{
Entity entity = _entityPool.Pop();
_entities.Add(entity.id, entity);
return _entityPool.Pop();
}
}

public Entity GetEntity(int id)
{
Entity entity;
_entities.TryGetValue(id, out entity);
return entity;
}

public void RemoveEntity(Entity entity)
{
entity.Clear();
_entityPool.Push(entity);
_entities.Remove(entity.id);
}

public void CompChangeBroadcast(Entity entity, int compId)
{
List<Action<Entity>> actionList;
_onCompChangeAction.TryGetValue(compId, out actionList);
if (actionList != null)
{
foreach (Action<Entity> action in actionList)
{
action.Invoke(entity);
}
}
}

public ECSMatcher AnyOf(params Type[] comps)
{
return new ECSMatcher().AnyOf(comps);
}

public ECSMatcher AllOf(params Type[] comps)
{
return new ECSMatcher().AllOf(comps);
}

public ECSMatcher ExcludeOf(params Type[] comps)
{
return new ECSMatcher().ExcludeOf(comps);
}

public ECSMatcher OnlyOf(params Type[] comps)
{
return new ECSMatcher().OnlyOf(comps);
}
}

使用方法

Comp示例
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
[CompRegister(typeof(TransformComp))]
public class TransformComp : Comp
{
public Vector3 position;

public Quaternion rotation;

public Vector3 scale;

public Vector3 lastPosition;

public Quaternion lastRotation;

public Vector3 lastScale;

public bool changed = false;

public override void Reset()
{
position = Vector3.zero;
rotation = Quaternion.identity;
scale = Vector3.one;
lastPosition = Vector3.zero;
lastRotation = Quaternion.identity;
lastScale = Vector3.one;
changed = false;
}
}
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
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

using System.Collections.Generic;
using UnityEngine;

public class MoveSystem : ECSSystem
{

public override ECSMatcher Filter()
{
return ECSManager.Ins().AllOf(typeof(TransformComp), typeof(MoveComp));
}

public override void OnUpdate(List<Entity> entities)
{
foreach (Entity entity in entities)
{
MoveComp move = entity.Get<MoveComp>();

if (move.inputForward != Vector3.zero && !entity.Has<ClimbUpComp>() && (!move.isClimb || move.isClimb && move.climbJump == 0))
{
if (move.isClimb)
{
move.curForwad = move.inputForward;
}
else
{
move.curForwad = move.forward;
move.fixedForward = move.up * move.forward;
}
move.nextPostition += move.fixedForward * move.speed;
}
}
}

/// <summary>
/// 辅助线
/// </summary>
/// <param name="entities"></param>
public override void OnDrawGizmos(List<Entity> entities)
{
foreach (Entity entity in entities)
{
MoveComp move = entity.Get<MoveComp>();
TransformComp transform = entity.Get<TransformComp>();

Gizmos.DrawLine(transform.position, transform.position + move.fixedForward * 100);
}
}
}

World示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LogicWorld : ECSWorld
{
public override void SystemAdd()
{
Add(new TransformSystem());
Add(new ClimbSystem());
Add(new MoveSystem());
Add(new JumpSystem());
Add(new ClimbUpSystem());
Add(new QTreeSystem());
Add(new QObjFindingSystem());
Add(new WeaponSystem());
Add(new LogicAniSystem());
Add(new AniSystem());
}
}
主循环示例
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
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class Launcher : MonoBehaviour
{
private bool _inited = false;

private int _initIndex = 0;

private LogicWorld _logicWorld;

private PhysicWorld _physicWorld;

private RenderWorld _renderWorld;

void Start()
{
InitNext();
}

private void Update()
{
if (_inited)
{
_renderWorld.Update();
}
}

private void FixedUpdate()
{
if (_inited)
{
_logicWorld.Update();
_physicWorld.Update();
}
}

void OnDrawGizmos()
{
if (_inited)
{
_logicWorld.DrawGrizmos();
_physicWorld.DrawGrizmos();
_renderWorld.DrawGrizmos();
}
}

private void InitNext()
{
ConsoleUtils.Log("当前步骤",_initIndex);
switch (_initIndex++)
{
case 0:
InitUtils();
break;
case 1:
LoadRes();
break;
case 2:
InitManager();
break;
case 3:
StartGame();
break;
case 4:
InitGlobals();
break;
case 5:
_inited = true;
UIManager.Ins().Hide(_loadingView);
break;
}
}

private void LoadRes()
{
InitNext();
}

private void InitGlobals()
{
InitNext();
}

private void InitUtils()
{
InitNext();
}

private void InitManager()
{
ECSManager.Ins().Init();
InitNext();
}

private void StartGame()
{
_logicWorld = new LogicWorld();
_physicWorld = new PhysicWorld();
_renderWorld = new RenderWorld();

InitNext();
}
}

项目工程

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

注意事项

本文的 ECS 架构并未做多线程或并行处理,这部分内容请自行改造。

本文内容与TS 实现 ECS 架构一文基本相同,只是做了 Unity 版本的移植和调整并且修复一些 bug,不过建议以本文为主。

更新日志

2024-03-08

  1. 更新 ECS 基本内容。

评论