Unity ECS 架构与交通模拟的实现

栏目: 后端 · 发布时间: 6年前

内容简介:这是侑虎科技第480篇文章,感谢作者Major供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)作者主页:http://ma-yidong.com,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!笔者在尝试做交通和行人的模拟,自然就想到了Unity 2018的新功能ECS + Job System,线性数据+多线程提高模拟速度,因此本文分两部分:ECS和交通模拟,以

这是侑虎科技第480篇文章,感谢作者Major供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

作者主页:http://ma-yidong.com,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!

笔者在尝试做交通和行人的模拟,自然就想到了Unity 2018的新功能ECS + Job System,线性数据+多线程提高模拟速度,因此本文分两部分:ECS和交通模拟,以下为一个WIP的成果,感兴趣的朋友可以通过开源库下载:

https://lab.uwa4d.com/lab/5bdf5ee572745c25a8fab19a

Unity ECS 架构与交通模拟的实现

Unity ECS 架构与交通模拟的实现

该项目的实现主要有几个模块:生成RoadGraph,构造BVH,车辆模拟和行人模拟。

RoadGraph部分,为了Job让里能调用,全都是Array形式存的了,没有指针全是ID来指。构造BVH和行人模拟在笔者另外的博客中讲到了。车辆模拟有三个部分,首先构造BVH,之后每个Vehicle遍历BVH感知周边信息,最后一个更新Vehicle的位置等数据。

最早考虑的是用物理RayCast来做空间查找,让Vehicle感知周边信息。先是想用Pure ECS实现的,写着发现没法加物理,只能变成Hybrid ECS,实例化GameObject加上Collider,还好更新Collider的位置开销不大,主要是RayCast开销大,用于Agent感知周边车辆位置。但之后Profile发现Job里开销最大的是是RayCast物理,就改成了用Job System构造BVH,速度变快很多。

之前:

Unity ECS 架构与交通模拟的实现

之后:

Unity ECS 架构与交通模拟的实现

目前可以在我的笔记本上以30fps的跑25000辆车。

代码都挂在UWA Lab上了,详见OSMTrafficSim:

https://lab.uwa4d.com/lab/5bdf5ee572745c25a8fab19a

下面讲一讲我对于ECS的理解。

一,Unity ECS

ECS是Entity-Component-System的简称,2018推出的新功能。是一种比较新的框架体系,主要优点是:

  • 处理大量物体时性能较好,data-layout对Cache友好,便于多线程计算。
  • 耦合性低,代码清晰.Component只有数据没有逻辑,System只有逻辑没有状态。

Unity传统框架是有Entity-Component思想的,但不是ECS这个Entity-Component。传统Unity中Gameobject是Entity,MonoBehavior是Component挂在GameObject上。这样数据分散在内存多处,用指针获取,这样Cache-Miss会很多。而且数据耦合严重,线程不安全。

Unity ECS 架构与交通模拟的实现

ECS的数据基本都是按一维数组形式存储的,很容易一起放进Cache,加快CPU计算速度。要知道内存-L3-L2-L1的数据读取花费的CPU指令周期是近似数量级降低的。用ID形式访问也对多线程比较友好,避免Data-Race。

笔者更关心计算性能的提升,最明显的是对大量物体的逻辑与渲染有帮助,比如Swarm的模拟,游戏中就可以做万人同屏等了,效率提升可能是数量级的。而对非大量物体的游戏中,提升有多少就不好说了。物理模拟可能是比较有帮助的,可以分配到多线程。其他的逻辑呢,可是本来开销就和物理和渲染不在同一个数量级的。

二,精英的一些基本概念

World

Unreal中也有World,这次Unity也加上了。Play开始后默认创建一个World,你也可以自己创建。每个World有一个EntityManager和很多的ComponentSystem。EntityManager是管所有Entity的,ComponentSystem是System,管行为的,所有的Update都在里面。Component(数据)挂在Entity下面。

ComponentSystem

用于逻辑的,要有Update。注意的是,System是默认进行Update的!Monobehavior需要挂到GameObject上才会Update,ComponentSystem是只要你创建了这个代码就会Update的。另外ComponentSystem不依赖于GameObject,Hierarchy窗口里是找不到ComponentSystem的,但它就一直在Update。

** Entity,EntityManager** 本身什么就没有,就是一个ID。EntityManager里面可以找到每个Entity,在EntityDebug窗口中也能看到。Entity也是不依赖GameObject。World中可以有不挂在GameObject上的Entity,当然每个GameObject可以加GameObjectEntity变成Entity。

一般是用下面两个方法创建Entity。Archetype可以认为是一个类型,包含了需要哪些类型的Component。Entity就是Architype的实例,注入了数据。

1 EntityManager.CreateArchetype
2 EntityManager.CreateEntity

IComponentData

Component类的接口,所有存数据的地方。注意这是个Struct,其元素必须是非托管的,blitable类型的。

比如bool,int[],NativeArray<>都不能在IComponentData中存在。Unity.Mathematics里面的类都可以。另外每个IComponentData一般会声明一个DataWrapper,这个Wrapper才是一个可以挂在Entity上面的Component。

1 [Serializable]
2 public struct RotationSpeed : IComponentData
3 {
4     public float Value;
5 }
6 
7 public class RotationSpeedComponent : ComponentDataWrapper<RotationSpeed> { }

ComponentGroup,Inject,SubtractiveComponent

这几个存在的意义在于:ComponentSystem的Update对于哪些Entity起作用?

https://forum.unity.com/threads/how-the-demo-manages-to-selectively-enable-only-some-system-on-some-scene.531902/#post-3501129

Inject对象声明后会在ComponentSystem的OnCreateManager()之前获取到需要参与计算的Entity。

举个例子,其中SubtractiveComponent代表了,System关心的Entity不能有这些Component。下面这个例子中,group会获取到所有有Position,有Rigidbody,但是没有MeshCollider的Entity。

1class MySystem : ComponentSystem
 2{
 3    public struct Group
 4    {
 5        // ComponentDataArray lets us access IComponentData 
 6        [ReadOnly]
 7        public ComponentDataArray<Position> Position;
 8
 9        // ComponentArray lets us access any of the existing class Component                
10        public ComponentArray<Rigidbody> Rigidbodies;
11
12        // Sometimes it is necessary to not only access the components
13        // but also the Entity ID.
14        public EntityArray Entities;
15
16        // The GameObject Array lets us retrieve the game object.
17        // It also constrains the group to only contain GameObject based entities.                  
18        public GameObjectArray GameObjects;
19
20        // Excludes entities that contain a MeshCollider from the group
21        public SubtractiveComponent<MeshCollider> MeshColliders;
22
23        // The Length can be injected for convenience as well 
24        public int Length;
25    }
26    [Inject] private Group m_Group;
27
28
29    protected override void OnUpdate()
30    {
31        // Iterate over all entities matching the declared ComponentGroup required types
32        for (int i = 0; i != m_Group.Length; i++)
33        {
34            m_Group.Rigidbodies[i].position = m_Group.Position[i].Value;
35
36            Entity entity = m_Group.Entities[i];
37            GameObject go = m_Group.GameObjects[i];
38        }
39    }
40}

如果不用Inject,也可以ComponentGroup方法获得要参与计算的Entity。下面这个例子中,找到了所有包含Position, Rigidbody, SharedGrouping组件的Entity,注意ComponentGroup还有filter方法选择一部分Entity。

1struct SharedGrouping : ISharedComponentData
 2{
 3    public int Group;
 4}
 5
 6class PositionToRigidbodySystem : ComponentSystem
 7{
 8    ComponentGroup m_Group;
 9
10    protected override void OnCreateManager(int capacity)
11    {
12        // GetComponentGroup should always be cached from OnCreateManager, never from OnUpdate
13        // - ComponentGroup allocates GC memory
14        // - Relatively expensive to create
15        // - Component type dependencies of systems need to be declared during OnCreateManager,
16        //   in order to allow automatic ordering of systems
17        m_Group = GetComponentGroup(typeof(Position), typeof(Rigidbody), typeof(SharedGrouping));
18    }
19
20    protected override void OnUpdate()
21    {
22        // Only iterate over entities that have the SharedGrouping data set to 1
23        // (This could for example be used as a form of gamecode LOD)
24        m_Group.SetFilter(new SharedGrouping { Group = 1 });
25
26        var positions = m_Group.GetComponentDataArray<Position>();
27        var rigidbodies = m_Group.GetComponentArray<Rigidbody>();
28
29        for (int i = 0; i != positions.Length; i++)
30            rigidbodies[i].position = positions[i].Value;
31
32        // NOTE: GetAllUniqueSharedComponentDatas can be used to find all unique shared components 
33        //       that are added to entities. 
34        // EntityManager.GetAllUniqueSharedComponentDatas(List<T> shared);
35    }
36}

Job

一般System里更新Entity的数据都是Job的方式,就是一个多线程的任务,方便CPU调度。

native方式,所以非托管的方式就跟写C++一样了,比如Job里想要一个定长int数组:

Malloc一下,最后要Free掉

1 int* _array= (int*)UnsafeUtility.Malloc(100 * sizeof(int), sizeof(int), Allocator.Temp);
2 ......
3 ......
4 UnsafeUtility.Free(_array, Allocator.Temp);
5 _array= null;

所以一个ECS的基本逻辑是:

1 rt(){
2     创建Entity
3     System找到感兴趣的Entity
4 }
5 
6 Update(){
7     System开Job更新Entity的数据
8 }

三,两个有意思的ECS示例项目

Voxelman,跳舞的体素小人

首先已经有了两个骨骼运动的小人,小人的模型会运行时烘焙成MeshCollider。System首先设置好Voxel,然后RayCast那个小人的collider获得Voxel的位置,最后更新Voxel的位置,渲染。

https://lab.uwa4d.com/lab/5b442a89d7f10a201faf67f3

uSpringBones,飘带物理运算的ECS版本

这个就没有ComponentSystem,全是Monobehavior开Job更新,Component主要用于更新Transform位置。

https://lab.uwa4d.com/lab/5bf67e9d72745c25a836b5ca

参考资料

OSMTrafficSim,笔者项目

https://github.com/maajor/OSMTrafficSim

Voxelman,有意思的ECS项目

https://lab.uwa4d.com/lab/5b442a89d7f10a201faf67f3

uSpringBones,有意思的ECS项目

https://lab.uwa4d.com/lab/5bf67e9d72745c25a836b5ca

All of the Unity ECS Job System gotchas so far 很多ECS的小Tips!

https://gametorrahod.com/all-of-the-unitys-ecs-job-system-gotchas-so-far-6ca80d82d19f

文末,再次感谢Major的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)。

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms and Theory of Computation Handbook

Algorithms and Theory of Computation Handbook

Mikhail J. Atallah (Editor) / CRC-Press / 1998-09-30 / USD 94.95

Book Description This comprehensive compendium of algorithms and data structures covers many theoretical issues from a practical perspective. Chapters include information on finite precision issues......一起来看看 《Algorithms and Theory of Computation Handbook》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具