unity-doc-dots

概念

DOTS (Data-Oriented Tech Stack),主要包含Entity Component System(ECS)、Job System、Burst Compiler

DOTS = 缓存友好 + SIMD + 多线程 + struct去GC + LLVM burst编译器优化

  • 充分利用多核处理器,多线程处理让游戏的运行速度更快,更高效。
  • SIMD:单指令,多数据流的指令集优化。
  • Job System:C#任务系统,用于高效运行多线程代码。
  • ECS:实体组件系统,用于默认编写高性能代码。
  • Burst Compiler:Burst编译器,用于生产高度优化的本地代码。
  • Job System和ECS是两个不同的概念,两者这在一起才能发挥最大的优势,当前也可以分开使用。

ECS

使用Entity的方式将数据分离出来,然后在需要的地方提供相应的数据,其中System是为了处理Entity中数据的具体逻辑,还有一个很重要的部分”Filter”进行数据的过滤。简单来讲ECS就是数据与数据处理的一个关系。

Entity-Component-System的优点

  • 高性能
  • 易编写出高度优化的代码
  • 易编写出可重用的代码
  • 数据在内存中是紧密排列的
  • 和Job System、Burst一起可以获得更高的性能

Pure ECS

Pure ECS系统特点:

  • Entities是一个新的Gameobjects
  • 实体上没有多余的Mono脚本:数据存储在Components上;逻辑执行处理在System里面
  • 可以利用C# 的 Job System系统来进行工作。

Hybrid ECS

Hybrid ECS系统特点:

  • 包括所有Pure ECS 的功能
  • 一些特别的工具类:把GameObject当成Entities;把Mono脚本当成Components
  • 更多的支持原生Unity的类

JobSystem

Unity 对 CPU 多核编程的应用。通过把工作分散到 CPU 的各个核心上来
大大提升运行效率。而 ECS 跟他之间的搭配则是由于 ECS 的 System 部分天
然是以批量处理为核心的,因此只要稍加改动,就可以转变为批量的分 Job 交
到多核去处理,实现性能的极大提升。

Burst Compiler

Unity 开发的全新的快速编译器,使得项目性能有了更大的提升。

链接

[dotsApi]https://docs.unity3d.com/Packages/com.unity.collections@1.3/api/Unity.Collections.NativeParallelHashMap-2.html

使用

使用参考-2019

平台:mac
版本:2019.4.13f1

环境搭建

  • 安装package Entities 0.1.1
  • 安装package Mathematics 1.1.0
  • 安装package Hybrid Render 0.1.1
  • 安装package Jobs 0.1.1
  • 安装package Collection 0.1.1
  • 安装package Burst 1.1.2

Pure Ecs

例子运行图
EntityMgr.cs
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
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;

namespace PureEcs
{
public class EntityMgr : MonoBehaviour
{
public GameObject GoPrefab; // 预知体
public int XNum = 10; // x轴数量
public int ZNum = 10; // z轴数量

private NativeArray<Entity> entityArray; // 实体数组

private void Start()
{
GameObject goInst = GameObject.Instantiate(GoPrefab);
Mesh _mesh = goInst.GetComponent<MeshFilter>().mesh;
Material _material = goInst.GetComponent<MeshRenderer>().material;

// 创建实体管理器
EntityManager entityMgr = World.Active.EntityManager;

// 创建实体原型类型
EntityArchetype entityAr = entityMgr.CreateArchetype(
typeof(Rotate),
typeof(Movement),
typeof(Translation),
typeof(Rotation),
typeof(RenderMesh),
typeof(LocalToWorld));

// 创建本地集合类型
entityArray = new NativeArray<Entity>(XNum * ZNum, Allocator.Persistent);

// 创建实体
entityMgr.CreateEntity(entityAr, entityArray);
int idx = 0;
for (int x = 0; x < XNum; x++)
{
for (int z = 0; z < ZNum; z++)
{
entityMgr.SetComponentData(entityArray[idx], new Rotate {RotateSpeed = UnityEngine.Random.Range(5, 15)});
entityMgr.SetComponentData(entityArray[idx], new Movement {MoveSpeed = UnityEngine.Random.Range(1, 5)});
entityMgr.SetComponentData(entityArray[idx], new Translation {Value = new Vector3(x - XNum / 2, noise.cnoise(new float2(x, z)), z - ZNum / 2)});
entityMgr.SetComponentData(entityArray[idx], new Rotation{Value = quaternion.identity});

// 渲染
entityMgr.SetSharedComponentData(entityArray[idx], new RenderMesh
{
material = _material,
mesh = _mesh
});

idx++;
}
}
}

private void OnDestroy()
{
if (entityArray.IsCreated)
{
entityArray.Dispose();
}
}
}
}
MovementSystem.cs
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
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

namespace PureEcs
{
public struct Movement : IComponentData
{
public float MoveSpeed;
}

public class MovementSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref Translation translation, ref Movement moving) =>
{
translation.Value.y += moving.MoveSpeed * Time.deltaTime;
if (translation.Value.y > 4)
{
moving.MoveSpeed = -Mathf.Abs(moving.MoveSpeed);
}
else if (translation.Value.y < -1)
{
moving.MoveSpeed = Mathf.Abs(moving.MoveSpeed);
}
});
}
}
}
RotateSystem.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace PureEcs
{
public struct Rotate : IComponentData
{
public float RotateSpeed;
}

public class RotateSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref Rotate rotate, ref Rotation rotation) =>
{
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotate.RotateSpeed * Time.deltaTime));
});
}
}
}

Hybrid ECS

例子运行图
EntityMgr.cs
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
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Random = UnityEngine.Random;

namespace HybridEcs
{
public class EntityMgr : MonoBehaviour
{
public GameObject goPrefab; // 预知体
public int XNum = 10; // x轴数量
public int ZNum = 10; // z轴数量

private Entity entity; // 实体对象
private EntityManager entityMgr; // 实体管理器

private void Start()
{
// 游戏对象转实体
entity = GameObjectConversionUtility.ConvertGameObjectHierarchy(goPrefab, World.Active);

// 创建实体管理器
entityMgr = World.Active.EntityManager;

// 创建大量实体
for (int x = 0; x < XNum; x++)
{
for (int z = 0; z < ZNum; z++)
{
Entity tmpEntity = entityMgr.Instantiate(entity);
entityMgr.SetComponentData(tmpEntity, new Translation {Value = new float3(x - XNum / 2, noise.cnoise(new float2(x, z)), z - ZNum / 2)});
entityMgr.SetComponentData(tmpEntity, new Rotation{Value = quaternion.identity});

entityMgr.AddComponentData(tmpEntity, new Rotate {RotateSpeed = UnityEngine.Random.Range(5, 15)});
entityMgr.AddComponentData(tmpEntity, new Movement() {MoveSpeed = Random.Range(1, 5)});
}
}

}
}
}
MovementSystem.cs
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
using System.Collections;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

namespace HybridEcs
{
public struct Movement : IComponentData
{
public float MoveSpeed;
}

public class MovementSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref Translation translation, ref Movement movement) =>
{
translation.Value.y += movement.MoveSpeed * Time.deltaTime;

if (translation.Value.y > 4)
{
movement.MoveSpeed = -Mathf.Abs(movement.MoveSpeed);
} else if (translation.Value.y < -1)
{
movement.MoveSpeed = Mathf.Abs(movement.MoveSpeed);
}

});
}
}
}
RotateSystem.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace HybridEcs
{
public struct Rotate : IComponentData
{
public float RotateSpeed;
}

public class RotateSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref Rotate rotate, ref Rotation rotation) =>
{
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotate.RotateSpeed * Time.deltaTime));
});
}
}
}

JobSystem

例子运行图
TestJob.cs
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
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

namespace Jobs
{
public class TestJob : MonoBehaviour
{
public int DoTaskNum = 10; // 同时执行任务数量
public bool UseDotsJob = false; // 是否启用dots的job来执行任务

void Update()
{
float startTime = Time.realtimeSinceStartup;

if (UseDotsJob)
{
NativeList<JobHandle> listJob = new NativeList<JobHandle>(Allocator.Temp);
for(int i = 0; i < DoTaskNum; i++)
{
JobHandle jobHandle = DoDotsTask();
listJob.Add(jobHandle);
}

JobHandle.CompleteAll(listJob);
}
else
{
for (int i = 0; i < DoTaskNum; i++)
{
DoMonoTask();
}
}

// Debug.LogFormat("task cost time : {0}ms useJob({1})", (Time.realtimeSinceStartup - startTime) * 1000, UseDotsJob);
}

private void DoMonoTask()
{
Task.ComplexTask();
}

private JobHandle DoDotsTask()
{
DotsJob job = new DotsJob();
return job.Schedule();
}
}

[BurstCompile]
public struct DotsJob : IJob
{
public void Execute()
{
Task.ComplexTask();
}
}

public static class Task
{
public static void SimpleTask()
{
float value = 0;
for (int i = 0; i < 500; i++)
{
value = math.exp10(math.sqrt(value));
}
}

public static void ComplexTask()
{
float value = 0;
for (int i = 0; i < 50000; i++)
{
value = math.exp10(math.sqrt(value));
}
}
}

}
TestParallelJob.cs
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
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Jobs;

namespace Jobs
{
public enum GoJobRunType
{
MONO,
PARALLEL_FOR,
PARALLEL_FOR_TRANSFORM
}

public class GoInfo
{
public Transform transform;
public float MoveSpeed;
}

public class TestParallelJob : MonoBehaviour
{
public GameObject Prefab; // 预知体
public int XNum = 10; // x轴数量
public int ZNum = 10; // z轴数量
public GoJobRunType RunType = GoJobRunType.MONO;

private List<GoInfo> listGo = new List<GoInfo>();

private void Start()
{
for (int x = 0; x < XNum; x++)
{
for (int z = 0; z < ZNum; z++)
{
Vector3 pos = new Vector3(x - XNum / 2, noise.cnoise(new float2(x, z)), z - ZNum / 2);
GameObject goInst = Instantiate(Prefab, pos, quaternion.identity);

GoInfo goInfo = new GoInfo();
goInfo.transform = goInst.transform;
goInfo.MoveSpeed = UnityEngine.Random.Range(1f, 5);

listGo.Add(goInfo);
}
}
}

private void Update()
{
float startTime = Time.realtimeSinceStartup;

if (RunType == GoJobRunType.MONO)
{
DoMonoTask();
} else if (RunType == GoJobRunType.PARALLEL_FOR){
NativeArray<float3> listPos = new NativeArray<float3>(listGo.Count, Allocator.TempJob);
NativeArray<float> listMoveSpeed = new NativeArray<float>(listGo.Count, Allocator.TempJob);

for (int i = 0; i < listGo.Count; i++)
{
listPos[i] = listGo[i].transform.position;
listMoveSpeed[i] = listGo[i].MoveSpeed;
}

DotsParallelJob job = new DotsParallelJob()
{
listPos = listPos,
listMoveSpeed = listMoveSpeed,
deltaTime = Time.deltaTime,
};

// 参数1:指定总共子线程执行数据数量
// 参数2:每个子线程以下处理多少次
JobHandle jobHandle = job.Schedule(listGo.Count, ZNum);
jobHandle.Complete();

for (int i = 0; i < listGo.Count; i++)
{
listGo[i].transform.position = listPos[i];
listGo[i].MoveSpeed = listMoveSpeed[i];
}

listPos.Dispose();
listMoveSpeed.Dispose();

} else if (RunType == GoJobRunType.PARALLEL_FOR_TRANSFORM)
{
TransformAccessArray listTf = new TransformAccessArray(listGo.Count);
NativeArray<float> listMoveSpeed = new NativeArray<float>(listGo.Count, Allocator.TempJob);

for (int i = 0; i < listGo.Count; i++)
{
listTf.Add(listGo[i].transform);
listMoveSpeed[i] = listGo[i].MoveSpeed;
}

DotsParallelJobTransform job = new DotsParallelJobTransform()
{
listMoveSpeed = listMoveSpeed,
deltaTime = Time.deltaTime,
};

JobHandle jobHandle = job.Schedule(listTf);
jobHandle.Complete();

for (int i = 0; i < listGo.Count; i++)
{
listGo[i].MoveSpeed = listMoveSpeed[i];
}

listMoveSpeed.Dispose();
listTf.Dispose();
}

// Debug.LogFormat("task cost time : {0}ms useJob({1})", (Time.realtimeSinceStartup - startTime) * 1000, UseDotsJob);
}

private void DoMonoTask()
{
foreach (var goInfo in listGo)
{
Vector3 newPos = goInfo.transform.position + new Vector3(0, goInfo.MoveSpeed * Time.deltaTime);
goInfo.transform.position = newPos;

if (newPos.y > 4)
{
goInfo.MoveSpeed = -math.abs(goInfo.MoveSpeed);
} else if (newPos.y < -1)
{
goInfo.MoveSpeed = math.abs(goInfo.MoveSpeed);
}

Task.SimpleTask();
}
}
}

[BurstCompile]
public struct DotsParallelJob : IJobParallelFor
{
public NativeArray<float3> listPos; // 所有物体的坐标列表
public NativeArray<float> listMoveSpeed; // 物体移动速度列表
public float deltaTime; // 帧时间

public void Execute(int index)
{
listPos[index] += new float3(0, listMoveSpeed[index] * deltaTime, 0);
if (listPos[index].y > 4)
{
listMoveSpeed[index] = -math.abs(listMoveSpeed[index]);
} else if (listPos[index].y < -1)
{
listMoveSpeed[index] = math.abs(listMoveSpeed[index]);
}

Task.SimpleTask();
}
}

public struct DotsParallelJobTransform : IJobParallelForTransform
{
public NativeArray<float> listMoveSpeed; // 物体移动速度列表
public float deltaTime; // 帧时间

public void Execute(int index, TransformAccess transform)
{
transform.position += new Vector3(0, listMoveSpeed[index] * deltaTime, 0);
if (transform.position.y > 4)
{
listMoveSpeed[index] = -math.abs(listMoveSpeed[index]);
} else if (transform.position.y < -1)
{
listMoveSpeed[index] = math.abs(listMoveSpeed[index]);
}

Task.SimpleTask();
}
}
}

dots实战-简单打怪物

例子运行图
EntityMgr.cs
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
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace KillMonster
{
public class EntityMgr : MonoBehaviour
{
public int HeroNum = 5; // 默认英雄数量
public int EnemyNum = 100; // 默认敌人数量

public float EnemyCreateStride = 0.1f; // 敌人创建间隔
public int EnemyCreateSpeed = 10; // 敌人创建速度

public GameObject HeroPrefab; // 英雄预知体
public GameObject EnemyPrefab; // 敌人预知体

private float enemyCreateWait; // 下一次敌人创建等待时间

private Entity entityHero; // 英雄实体
private Entity entityEnemy; // 敌人实体
private EntityManager entityMgr; // ecs实体管理器

private void Start()
{
entityMgr = World.Active.EntityManager;

// 游戏对象转实体
entityHero = GameObjectConversionUtility.ConvertGameObjectHierarchy(HeroPrefab, World.Active);
entityEnemy = GameObjectConversionUtility.ConvertGameObjectHierarchy(EnemyPrefab, World.Active);

for (int i = 0; i < HeroNum; i++)
{
CreateOneHero();
}

for (int i = 0; i < EnemyNum; i++)
{
CreateOneEnemy();
}
}

private void Update()
{
enemyCreateWait -= Time.deltaTime;
if (enemyCreateWait > 0)
{
return;
}

for (int i = 0; i < EnemyCreateSpeed; i++)
{
CreateOneEnemy();
}

enemyCreateWait = EnemyCreateStride;
}

/// <summary>
/// 创建一个英雄
/// </summary>
private void CreateOneHero()
{
Entity tmpEntity = entityMgr.Instantiate(entityHero);
entityMgr.SetComponentData(tmpEntity, new Translation {Value = new float3(UnityEngine.Random.Range(-2, 2f), UnityEngine.Random.Range(-2, 2f), 0) });
entityMgr.SetComponentData(tmpEntity, new Rotation{Value = quaternion.identity});
entityMgr.AddComponentData(tmpEntity, new Hero{MoveSpeed = 5});
entityMgr.AddComponentData(tmpEntity, new BaseEntity{EntityType = EntityType.HERO});
}

/// <summary>
/// 创建一个敌人
/// </summary>
private void CreateOneEnemy()
{
Entity tmpEntity = entityMgr.Instantiate(entityEnemy);
entityMgr.SetComponentData(tmpEntity, new Translation {Value = new float3(UnityEngine.Random.Range(-5f, 5f), UnityEngine.Random.Range(-5f, 5f), 0) });
entityMgr.SetComponentData(tmpEntity, new Rotation{Value = quaternion.identity});
entityMgr.AddComponentData(tmpEntity, new Enemy());
entityMgr.AddComponentData(tmpEntity, new BaseEntity{EntityType = EntityType.ENEMY});
}
}
}
EntityComponent.cs
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
using Unity.Entities;

namespace KillMonster
{
public enum EntityType
{
HERO,
ENEMY,
}

public struct BaseEntity : IComponentData
{
public EntityType EntityType;
}

public struct Hero : IComponentData
{
public float MoveSpeed; // 移动速度
}

public struct Enemy : IComponentData
{

}

public struct HadEnemy : IComponentData
{
public Entity Enemy;
}
}
EntitySystem.cs
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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#define FIND_ENEMY_SYSTEM_SIMPLE_

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace KillMonster
{
public struct BlockEntityInfo
{
public Entity Entity;
public float3 Position;
public BaseEntity BaseEntity;
}

/// <summary>
/// 实体分块功能
/// </summary>
public class EntityBlockSystem : ComponentSystem
{
/// <summary>
/// 实体分块信息映射
/// key =》分块id
/// val =》实体信息
/// </summary>
public static NativeMultiHashMap<int, BlockEntityInfo> MapBlockEntityInfo;

protected override void OnCreate()
{
MapBlockEntityInfo = new NativeMultiHashMap<int, BlockEntityInfo>(0, Allocator.TempJob);

base.OnCreate();
}

protected override void OnDestroy()
{
MapBlockEntityInfo.Dispose();

base.OnDestroy();
}

protected override void OnUpdate()
{
EntityQuery entityQuery = GetEntityQuery(typeof(Translation), typeof(BaseEntity));

MapBlockEntityInfo.Clear();
if (entityQuery.CalculateEntityCount() > MapBlockEntityInfo.Capacity)
{
MapBlockEntityInfo.Capacity = entityQuery.CalculateEntityCount();
}

SetQuadrantDataHashMapJob setQuadrantDataHashMapJob = new SetQuadrantDataHashMapJob()
{
quadrantMultiHashMap = MapBlockEntityInfo.AsParallelWriter()
};

JobHandle jobHandle = JobForEachExtensions.Schedule(setQuadrantDataHashMapJob, entityQuery);
jobHandle.Complete();

DebugDrawQuadrant(Help.GetMouseWorldPosition());
Debug.Log(GetEntityCountInHashMap(MapBlockEntityInfo, Help.CalcPositionBlockId(Help.GetMouseWorldPosition())));
}



private static void DebugDrawQuadrant(float3 position)
{
Vector3 lowerLeft = new Vector3(math.floor(position.x / Help.BLOCK_SIZE) * Help.BLOCK_SIZE, math.floor(position.y / Help.BLOCK_SIZE) * Help.BLOCK_SIZE, 0);
Debug.DrawLine(lowerLeft, lowerLeft + new Vector3(+1, +0) * Help.BLOCK_SIZE);
Debug.DrawLine(lowerLeft, lowerLeft + new Vector3(0, 1) * Help.BLOCK_SIZE);
Debug.DrawLine(lowerLeft + new Vector3(+1, +0) * Help.BLOCK_SIZE, lowerLeft + new Vector3(1, 1) * Help.BLOCK_SIZE);
Debug.DrawLine(lowerLeft + new Vector3(0, 1) * Help.BLOCK_SIZE, lowerLeft + new Vector3(1, 1) * Help.BLOCK_SIZE);
// Debug.Log(GetPositionHashMapKey(position) + " " + position);
}

public static int GetEntityCountInHashMap(NativeMultiHashMap<int, BlockEntityInfo> quadrantMultiHashMap, int hashMapKey)
{
BlockEntityInfo quadrantData;
NativeMultiHashMapIterator<int> nativeMultiHashMapIterator;
int count = 0;
if (quadrantMultiHashMap.TryGetFirstValue(hashMapKey, out quadrantData, out nativeMultiHashMapIterator))
{
do
{
count++;
} while (quadrantMultiHashMap.TryGetNextValue(out quadrantData, ref nativeMultiHashMapIterator));
}

return count;
}

[BurstCompile]
private struct SetQuadrantDataHashMapJob : IJobForEachWithEntity<Translation, BaseEntity>
{
public NativeMultiHashMap<int, BlockEntityInfo>.ParallelWriter quadrantMultiHashMap;

public void Execute(Entity entity, int index, ref Translation translation, ref BaseEntity quadrantEntity)
{
int blockId = Help.CalcPositionBlockId(translation.Value);
quadrantMultiHashMap.Add(blockId, new BlockEntityInfo
{
Entity = entity,
Position = translation.Value,
BaseEntity = quadrantEntity,
});
}
}


}

#if FIND_ENEMY_SYSTEM_SIMPLE
/// <summary>
/// 寻找敌人功能
/// 没有任何的优化
/// </summary>
public class FindEnemySystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.WithNone<HadEnemy>().WithAll<Hero>().ForEach((Entity entity, ref Translation translation) =>
{
float3 playerPos = translation.Value;
Entity closestEnemy = Entity.Null;
float3 closestEnemyPos = float3.zero;
float closestDistSqr = -1;

Entities.WithAll<Enemy>().ForEach((Entity enemy, ref Translation translationEnemy) =>
{
if (closestEnemy == Entity.Null)
{
// 还没有敌人
closestEnemy = enemy;
closestEnemyPos = translationEnemy.Value;
closestDistSqr = math.distancesq(playerPos, closestEnemyPos);
return;
}

float enemyDistSqr = math.distancesq(playerPos, translationEnemy.Value);
if (enemyDistSqr < closestDistSqr)
{
closestEnemy = enemy;
closestEnemyPos = translationEnemy.Value;
closestDistSqr = enemyDistSqr;
}
});

if (closestEnemy != Entity.Null)
{
PostUpdateCommands.AddComponent(entity, new HadEnemy(){Enemy = closestEnemy});
}
});
}
}
#else
/// <summary>
/// 寻找敌人功能
/// job + 敌人分块
/// </summary>
public class FindEnemySystem : JobComponentSystem
{
private struct EntityWithPosition
{
public Entity Entity;
public float3 Position;
}

private EndSimulationEntityCommandBufferSystem commandBufferSystem;

protected override void OnCreate()
{
commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();

base.OnCreate();
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
EntityQuery heroQuery = GetEntityQuery(typeof(Hero), ComponentType.Exclude<HadEnemy>());
NativeArray<Entity> arrHeroClosestEnemy = new NativeArray<Entity>(heroQuery.CalculateEntityCount(), Allocator.TempJob);

FindTargetJobWithBlock findTargetJob = new FindTargetJobWithBlock
{
MapAllEntity = EntityBlockSystem.MapBlockEntityInfo,
ArrHeroClosestEnemy = arrHeroClosestEnemy,
};

JobHandle jobHandle = findTargetJob.Schedule(this, inputDeps);

AddComponentJob addComponentJob = new AddComponentJob {
ArrHeroClosestTarget = arrHeroClosestEnemy,
CommandBuffer = commandBufferSystem.CreateCommandBuffer().ToConcurrent(),
};

jobHandle = addComponentJob.Schedule(this, jobHandle);

commandBufferSystem.AddJobHandleForProducer(jobHandle);

return jobHandle;
}

[RequireComponentTag(typeof(Hero))]
[ExcludeComponent(typeof(HadEnemy))]
[BurstCompile]
private struct FindTargetJob : IJobForEachWithEntity<Translation>
{
[DeallocateOnJobCompletion][ReadOnly]public NativeArray<EntityWithPosition> ArrEnemys;
public NativeArray<Entity> ArrHeroClosestEnemy;

public void Execute(Entity entity, int index, ref Translation translation)
{
float3 heroPos = translation.Value;
Entity closestTarget = Entity.Null;
float3 closestPos = float3.zero;

for (int i = 0; i < ArrEnemys.Length; i++)
{
EntityWithPosition targetInfo = ArrEnemys[i];

if (closestTarget == Entity.Null)
{
// 还没有敌人
closestTarget = targetInfo.Entity;
closestPos = targetInfo.Position;
continue;
}

if (math.distance(heroPos, targetInfo.Position) < math.distance(heroPos, closestPos))
{
closestTarget = targetInfo.Entity;
closestPos = targetInfo.Position;
}
}

ArrHeroClosestEnemy[index] = closestTarget;
}
}

[RequireComponentTag(typeof(Hero))]
[ExcludeComponent(typeof(HadEnemy))]
[BurstCompile]
private struct FindTargetJobWithBlock : IJobForEachWithEntity<Translation, BaseEntity>
{
[ReadOnly] public NativeMultiHashMap<int, BlockEntityInfo> MapAllEntity;
public NativeArray<Entity> ArrHeroClosestEnemy;

public void Execute(Entity entity, int index, [ReadOnly] ref Translation translation,
ref BaseEntity quadrantEntity)
{
float3 heroPos = translation.Value;
Entity closestEnemy = Entity.Null;
float closestDist = float.MaxValue;

int blockId = Help.CalcPositionBlockId(translation.Value);

// 遍历九宫格的敌人
FindEnemy(blockId, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId + 1, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId - 1, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId + Help.BLOCK_ID_FAC_POS_Y, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId - Help.BLOCK_ID_FAC_POS_Y, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId + 1 + Help.BLOCK_ID_FAC_POS_Y, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId - 1 + Help.BLOCK_ID_FAC_POS_Y, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId + 1 - Help.BLOCK_ID_FAC_POS_Y, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);
FindEnemy(blockId - 1 - Help.BLOCK_ID_FAC_POS_Y, heroPos, quadrantEntity, ref closestEnemy, ref closestDist);

ArrHeroClosestEnemy[index] = closestEnemy;
}

/// <summary>
/// 寻找敌人
/// </summary>
private void FindEnemy(int blockId, float3 heroPosition, BaseEntity checkEntity,
ref Entity closestTargetEntity, ref float closestTargetDistance)
{
BlockEntityInfo tmpEntityInfo;
NativeMultiHashMapIterator<int> iteMapInfo;

if (MapAllEntity.TryGetFirstValue(blockId, out tmpEntityInfo, out iteMapInfo))
{
do
{
if (checkEntity.EntityType == tmpEntityInfo.BaseEntity.EntityType)
{
// 同类型的都不算敌人
continue;
}

if (closestTargetEntity == Entity.Null)
{
// 还没有目标
closestTargetEntity = tmpEntityInfo.Entity;
closestTargetDistance = math.distance(heroPosition, tmpEntityInfo.Position);
continue;
}

float tmpDist = math.distance(heroPosition, tmpEntityInfo.Position);
if (tmpDist < closestTargetDistance)
{
closestTargetEntity = tmpEntityInfo.Entity;
closestTargetDistance = tmpDist;

}
} while (MapAllEntity.TryGetNextValue(out tmpEntityInfo, ref iteMapInfo));
}
}
}

[RequireComponentTag(typeof(Hero))]
[ExcludeComponent(typeof(HadEnemy))]
private struct AddComponentJob : IJobForEachWithEntity<Translation>
{
[DeallocateOnJobCompletion] [ReadOnly] public NativeArray<Entity> ArrHeroClosestTarget;
public EntityCommandBuffer.Concurrent CommandBuffer;

public void Execute(Entity entity, int index, ref Translation translation)
{
if (ArrHeroClosestTarget[index] != Entity.Null)
{
CommandBuffer.AddComponent(index, entity, new HadEnemy{Enemy = ArrHeroClosestTarget[index]});
}
}
}
}
#endif

/// <summary>
/// 移动到敌人功能
/// </summary>
public class MoveToEnemySystem : ComponentSystem
{
private EntityManager entityMgr; // ecs实体管理器

protected override void OnCreate()
{
entityMgr = World.Active.EntityManager;

base.OnCreate();
}

protected override void OnUpdate()
{
Entities.ForEach((Entity entity, ref Hero player, ref HadEnemy hadEnemy, ref Translation translation) =>
{
if (!entityMgr.Exists(hadEnemy.Enemy))
{
// 敌人已经不存在了
PostUpdateCommands.RemoveComponent<HadEnemy>(entity);
return;
}

Translation tfEnemy = entityMgr.GetComponentData<Translation>(hadEnemy.Enemy);
float3 moveDir = math.normalize(tfEnemy.Value - translation.Value);

translation.Value += moveDir * player.MoveSpeed * Time.deltaTime;
if (math.distance(translation.Value, tfEnemy.Value) < 0.2f)
{
// 靠近敌人,消灭敌人
PostUpdateCommands.DestroyEntity(hadEnemy.Enemy);
PostUpdateCommands.RemoveComponent<HadEnemy>(entity);
}
});
}
}

/// <summary>
/// 敌人调试功能
/// </summary>
public class EnemyDebugSystem : ComponentSystem
{
private EntityManager entityMgr; // ecs实体管理器

protected override void OnCreate()
{
entityMgr = World.Active.EntityManager;

base.OnCreate();
}

protected override void OnUpdate()
{
Entities.ForEach((Entity entity, ref Translation translation, ref HadEnemy hadEnemy) =>
{
if (!entityMgr.Exists(hadEnemy.Enemy))
{
return;
}

Translation tfTarget = entityMgr.GetComponentData<Translation>(hadEnemy.Enemy);
Debug.DrawLine(translation.Value, tfTarget.Value);
});
}
}
}
Help.cs
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
using Unity.Mathematics;
using UnityEngine;

namespace KillMonster
{
public static class Help
{
public const int BLOCK_SIZE = 5; // 块大小
public const int BLOCK_ID_FAC_POS_Y = 1000; // y坐标计算系数

public static Vector3 GetMouseWorldPosition() {
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
worldPosition.z = 0f;
return worldPosition;
}

/// <summary>
/// 计算坐标的分块id
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public static int CalcPositionBlockId(float3 position)
{
return (int) (math.floor(position.x / BLOCK_SIZE) +
(BLOCK_ID_FAC_POS_Y * math.floor(position.y / BLOCK_SIZE)));
}
}
}

代码地址 https://gitee.com/hahafox_0/unity-tutorial-ecs2019