内容简介:这是侑虎科技第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
该项目的实现主要有几个模块:生成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,速度变快很多。
之前:
之后:
目前可以在我的笔记本上以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会很多。而且数据耦合严重,线程不安全。
ECS的数据基本都是按一维数组形式存储的,很容易一起放进Cache,加快CPU计算速度。要知道内存-L3-L2-L1的数据读取花费的CPU指令周期是近似数量级降低的。用ID形式访问也对多线程比较友好,避免Data-Race。
笔者更关心计算性能的提升,最明显的是对大量物体的逻辑与渲染有帮助,比如Swarm的模拟,游戏中就可以做万人同屏等了,效率提升可能是数量级的。而对非大量物体的游戏中,提升有多少就不好说了。物理模拟可能是比较有帮助的,可以分配到多线程。其他的逻辑呢,可是本来开销就和物理和渲染不在同一个数量级的。
二,精英的一些基本概念
Unity官方有一些介绍:
https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/content/ecs_concepts.mdWorld
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起作用?
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/5b442a89d7f10a201faf67f3uSpringBones,飘带物理运算的ECS版本
这个就没有ComponentSystem,全是Monobehavior开Job更新,Component主要用于更新Transform位置。
https://lab.uwa4d.com/lab/5bf67e9d72745c25a836b5ca官方ECS介绍,Github上上这个比官网的全一些
https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/index.mdVoxelman,有意思的ECS项目
https://lab.uwa4d.com/lab/5b442a89d7f10a201faf67f3uSpringBones,有意思的ECS项目
https://lab.uwa4d.com/lab/5bf67e9d72745c25a836b5caAll 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和开发者在一起!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 微服务参考架构实现
- 如何实现弹性架构
- 『互联网架构』软件架构-dubbo协议底层原理与实现(44)
- 『互联网架构』软件架构-RPC网络传输原理与实现(45)
- 系统架构系列(五):技术架构之高可扩展系统设计与实现
- DNS互联网架构的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
刘大猫的财富之旅
刘欣、刘大猫 / 新华出版社 / 2017-7-21 / 58.00元
作者刘大猫是一名90后的互联网连环创业者,26岁的他通过互联网创业收获到了财富,不仅仅是物质财富,还有认知的财富。 与其他创业类书籍不通的是,这本书非常真实,务实。书中没有任何大道理鸡汤,作者用平实的语言记录了创业以来遇到的种种事情,变化,困境,以及阶段性的成绩,记录了作者务实,鲜活的创业青春。一起来看看 《刘大猫的财富之旅》 这本书的介绍吧!