|
ECS Sample - Boids演示
引言
最近在学习A*寻路算法时,无意间发现Unity在2019年推出了一个新的框架ECS。了解片刻后,被这个框架带来的极大的性能提升所震撼,于是打算认真的学一下这个框架。这篇文章简单总结一下我近一周的学习心得,本人技术有限,如果文章有所疏漏也烦请指正!
ECS也就是Entity Component System的缩写,ECS采用了面向数据编程的思想,将之前笨重的GameObject拆分成值类型的Entity和Component,然后再使用System进行统一管理和运行。该框架亦引入了多线程编程技术,这样大大利用了设备CPU的性能。
这篇文章会包含如下内容:
为什么Unity要开发ECSECS是如何带来性能提升的什么情况要使用ECSECS实例
为什么Unity要开发ECS
芯片性能增速变缓
根据Unity自己的说法,目前芯片的性能迭代早已不遵循摩尔定律,即每18个月芯片性能提高一倍。所以最大化的利用的芯片性能是之后是开发者需要关注的重中之重。
在我看来,其实Unity早就应该提出这一套开发框架了。。本身C#的性能就比C++低不少,而且Unity的MonoBehavior class也非常笨重,这一次的框架更新真的可以给未来基于Unity的游戏带来更多的可能。
ECS是如何带来性能提升的
根据Unity的文档解释,和我个人的理解,我认为有两点。
1. 提高了内存读写速度
但我们运行游戏内的逻辑时,CPU需要在内存里读取数据并放到缓存中等待读取,可是通常数据在内存里的存放顺序是随机的,所以这会很大降低内存寻址的效率。
数据在内存上的分布是随机的
但如果数据是连续存放的,那么内存寻址时的错误率会大大降低,从而减少CPU等待内存寻址的时间,进而提高了CPU的利用率。Unity ECS引入了Archetype和Chuck两个概念,Archetype即为Entity对应的所有组件的一个组合,然后多个Archetypes会打包成一个个Archetype chunk,按照顺序放在内存里,当一个chunck满了,会在接下来的内存上的位置创建一个新的chunk。因此,这样的设计在CPU寻址时就会更容易找到Entity相关的component。
Archetype示意图
Chunk示意图
2. 多线程的Job system提高了CPU利用率
这个比较好理解,Job system会把entities的分发到CPU的线程上,每个线程都会处理一系列任务。
什么情况要使用ECS
我个人认为并不是所有的项目/人都适合采用ECS架构,首先ECS的学习曲线较为曲折,太多新的方法,另外编程思想也不一样,整体学习的体验像是学一门新的语言,因此并不建议新手一上来就学ECS架构。
其次ECS主要解决的是CPU瓶颈,如果你的项目更多的是GPU瓶颈,那么没有必要把项目完全改成ECS框架,请考虑采用图形学方面的优化。
ECS比较适用于场景中有大量独立的agent逻辑的场景,比如RTS,模拟经营这类游戏就很适合用ECS进行优化。
ECS实例
实例下载:https://github.com/moecia/UnityECS
前言
在开始之前提一下刚刚没有讲到的2个ECS用的工具。
1. Burst Complier:Burst会把C#代码编译成更高效的机器语言,提高游戏运行速度
2. JobSystem:Unity开发的无痛多线程代码运行库。在最新的ECS 0.17.0版本中,似乎不需要再额外写专门的Job struct来实现多线程,只需要让我们的System类继承SystemBase class,并在OnUpdate的Entites.ForEach loop后,加上一个extension method即可实现多线程运行。
因为Unity ECS的更新比较频繁,所以在我学习过程中发现不少教程的方法都已经deprecate了。。所以我这个教程在1,2年后也难免变得不可用。
目前无论是ComponentSystem还是JobSystem,都被Unity宣布弃用。(不少老教程还在用这两个Class)所以我这里一律会使用SystemBase。
SystemBase的和ComponentSystem和JobSystem的主要区别是,在OnUpdate的Entites.ForEach loop后,必须要选择一个extension method,主要有3个常用的extension method,这里目前我还不太完全理解3个的具体区别(因为性能上表现比较一致)所以我这里仅复制粘贴文档,不做解释...
1. Run() : Runs the job immediately on the current thread.
2. Schedul(): Adds an IJobEntityBatchWithIndex instance to the job scheduler queue for sequential (non-parallel) execution.
3. ScheduleParallel(): Adds an IJobEntityBatchWithIndex instance to the job scheduler queue for parallel execution.
代码部分:
MoveSpeed.cs
using Unity.Entities;
namespace EcsSample
{
public struct MoveSpeedComponent : IComponentData
{
public float MoveSpeed;
}
}MoveSystem.cs
using Unity.Entities;
using Unity.Transforms;
namespace EcsSample
{
public class MoveSystem : SystemBase
{
protected override void OnUpdate()
{
var dt = Time.DeltaTime;
Entities.ForEach((ref Translation translation, ref MoveSpeedComponent moveSpeedComponent) =>
{
translation.Value.y += moveSpeedComponent.MoveSpeed * dt;
if (translation.Value.y > 5f || translation.Value.y < -5f)
{
moveSpeedComponent.MoveSpeed *= -1;
}
}).ScheduleParallel();
}
}
}TestController.cs
这里额外提下用到的SetSharedComponentData()方法。这个方法用于共享的component,然后会把所有share component打包到一个chunk,这样当需要更新share component时,因为它们内存中的存放位置时连续的,所以访问速度会更快。
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using Random = UnityEngine.Random;
namespace EcsSample
{
public class TestController : MonoBehaviour
{
[SerializeField] private Mesh mesh;
[SerializeField] private Material material;
void Start()
{
var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
// Add entity achetype
var entityArchetype = entityManager.CreateArchetype(
typeof(MoveSpeedComponent),
typeof(Translation),
typeof(RenderMesh),
typeof(LocalToWorld),
typeof(RenderBounds));
var entityArray = new NativeArray<Entity>(500000, Allocator.Temp);
// Instantiate entities
entityManager.CreateEntity(entityArchetype, entityArray);
for (int i = 0; i < entityArray.Length; i++)
{
var entity = entityArray;
entityManager.SetComponentData(entity, new MoveSpeedComponent { MoveSpeed = Random.Range(1f, 2f) });
entityManager.SetComponentData(entity, new Translation { Value = new float3(Random.Range(-8f, 8f), Random.Range(-5f, 5f), 0) });
entityManager.SetSharedComponentData(entity, new RenderMesh
{
mesh = this.mesh,
material = this.material
});
}
entityArray.Dispose();
}
}
}代码结构比较简单,MoveSpeed.cs就是一个移动速度的Component class,第二个MoveSystem就是控制Entity移动的,当entity移动到屏幕地图顶上就会往下移动,反之到了底部就会往上移动。最后的TestController.cs就是用于生成Entity。
欢迎关注我的博客和B站:
B站:BN的日记的个人空间_哔哩哔哩_Bilibili
博客:https://moecia.github.io/
参考资料
官方文档:
- https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/index.html
官方入门教学系列:
- https://learn.unity.com/tutorial/editor-scripting
官方样例:
- https://github.com/Unity-Technologies/EntityComponentSystemSamples
中文入门文档:
- http://dingxiaowei.cn/2020/02/09/
- https://www.lfzxb.top/unity-dots-ecs-burst-complier-jobsystem/ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|