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

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

内容简介:这是侑虎科技第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和开发者在一起!


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

查看所有标签

猜你喜欢:

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

刘大猫的财富之旅

刘大猫的财富之旅

刘欣、刘大猫 / 新华出版社 / 2017-7-21 / 58.00元

作者刘大猫是一名90后的互联网连环创业者,26岁的他通过互联网创业收获到了财富,不仅仅是物质财富,还有认知的财富。 与其他创业类书籍不通的是,这本书非常真实,务实。书中没有任何大道理鸡汤,作者用平实的语言记录了创业以来遇到的种种事情,变化,困境,以及阶段性的成绩,记录了作者务实,鲜活的创业青春。一起来看看 《刘大猫的财富之旅》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具