
大概学习一下。
传统方式:
问题:(1)数据分散

stuff过于混乱
(2)存在冗余数据
当move的时候,只需要speed,position和rotation,但transform含有lot of stuff
(3)串行方式
所以带来了JobSystem
- CPU利用率提高,一般游戏都是2core
- 传统多线程的问题:race condition, context switching
- thread safe
ECS需要改变思考方式。
Entity:不是game object,只是reference to data,purely date
System:functionality, 关键是一个Filter
例如,一个Render System有一个Render和Position的Filter,那么就会找到所有包含Render和Position的data component构成的Entity
ECS的优势:
- 不容易写出表现差的代码,原生带有良好的性能
- 可重用性高
- 能更充分的利用CPU
- achetypes在内存中紧密打包,访问速度提高
下面的一个job:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [ComputeJobOptimization] public struct MovementJob : IJobParallelForTransform { public float moveSpeed; public float topBound; public float bottomBound; public float deltaTime; // index: id, TransformAccess包含Transform的元数据,除去引用数据 public Execute(int index, TransformAccess transform) { // 逻辑差距不打 Vector3 pos = transform.position; pos += moveSpeed * deltaTime * (transform.rotation * new Vector3(0,0,1)); if (pos.z < bottomBound) pos.z = topBound; transform.position = pos; } }
|
GameManager:
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
| public class GameManager : MonoBehavior { #region GAME_MANAGER_STUFF //... #endregion TransformAccessArray transforms; MovementJob moveJob; JobHandle moveHandle; void OnDisable() { moveHandle.Complete(); transforms.Dispose(); // 手动GC } void Update() { moveHandle.Complete(); //等待完成 moveJob = new MovementJob() { // 创建信息 moveSpeed = enemySpeed, topBound = topBound, bottomBound = bottomBound, deltaTime = Time.deltaTime }; moveHandle = moveJob.Schedule(transforms); // 传入参数,传递给handle JobHandle.ScheduleBatchedJobs(); // 执行任务 } void AddShips(int amount) { moveHandle.Complete(); transform.capacity = transform.length + amount; for (int i = 0; i < amount; ++i) { float xV = Random.Range(leftBound, rightBound); float zV = Random.Range(0f, 10f); Vector3 pos = new Vector3(xVal, 0f, zV + topBound); Quanternion rot = Quanternion.Euler(0f, 180f, 0f); var obj = Instantiate<GameObject>(prefab, pos, rat); transforms.Add(obj.transform); } count += amount; fps.SetElementCount(count); } }
|
这种做法基本保持了原有的Unity方案。但我们希望用完整的ECS策略,还需要把Entity分出来。
1 2 3 4
| [Serializable] public struct MoveSpeed : IComponentData { public float Value; } public class MoveSpeedComponent : ComponentDataWrapper<MoveSpeed> {}
|

接下来创建一个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
| public class MovementSystem : JobComponentSystem { [ComputeJobOptimization] public struct MovementJob : IJobParallelForTransform<Position, Rotation, MoveSpeed> { // data inject public float topBound; public float bottomBound; public float deltaTime; public Execute(ref Position position, [ReadOnly] ref Rotation rotation, [ReadOnly] ref MoveSpeed speed) { // 逻辑差距不大 float3 pos = position.Value; pos += speed.Value * deltaTime * math.forward(rotation.Value); if (pos.z < bottomBound) pos.z = topBound; position.Value = pos; } } protected override JobHandle OnUpdate(JobHandle inputDeps) { MovementJob moveJob = new MovementJob { topBound = GameManager.GM.topBound, bottomBound = GameManager.GM.bottomBound, deltaTime = Time.deltaTime }; JobHandle moveHandle = moveJob.Schedule(this, 64, inputDeps); return moveHandle; } }
|
在GameManager中:
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
| public class GameManager : MonoBehavior { #region GAME_MANAGER_STUFF //... #endregion EntityManager manager; void OnDisable() { moveHandle.Complete(); transforms.Dispose(); // 手动GC } void Start() { fps = GetComponent<FPS>(); manager = World.Active.GetOrCreateManager<EntityManager>(); AddShips(enemyShitCount); } void Update() { if (Input.GetKeyDown("space")) AddShips(enemyShipIncrement); } void AddShips(int amount) { NativeArray<Entity> entities = new NativeArray<Entity>(amount, Allocator.Temp); // new type collections manager.Instantiate(enemyShipPrefab, entities); // create entities with component data for (int i = 0; i < amount; ++i) { float xV = Random.Range(leftBound, rightBound); float zV = Random.Range(0f, 10f); manager.SetComponentData(entities[i], new Position {Value = new float3(xVal, 0f, topBound + zVal)}); manager.SetComponentData(entities[i], new Rotation { Value = new quanternion(0,1,0,0) }); manager.SetComponentData(entities[i], new MoveSpeed { Value = enemySpeed }); } entities.Dispose(); // GC count += amount; fps.SetElementCount(count); } }
|
如果使用burst compiler,很多运算会交给GPU去进行,运算可以进一步优化。