当前位置:   article > 正文

【Unity】万人同屏, 从入门到放弃之——Entities 1.0.16性能测试_ecs 1.0.16

ecs 1.0.16

当前测试使用的Entities版本为1.0.16

Unity Entities 1.0.16使用方法:

Create a component for the spawner example | Entities | 1.0.16

1. 创建SubScene

2. 在SubScene下创建挂载Authoring脚本:

Authoring是MonoBehaviour脚本,主要用来序列化配置需要创建的实体prefab资源或参数;

因为Entities目前不支持用资源名动态加载资源!没错,AssetsBundle或Addressables都不能用于Entities;也就意味着现阶段不能用Entities开发DLC或热更游戏。

Entities必须使用SubScene,而SubScene不能从资源动态加载,路被彻底封死了。

  1. public class EntitiesAuthoring : MonoBehaviour
  2. {
  3. public GameObject m_Prefab;
  4. public int m_Row;
  5. public int m_Col;
  6. }

 3. 把步骤2的挂有Authoring脚本的GameObject转换为Entity:

SubScene中挂载的MonoBehaviour不会被执行,必须转换为Entity;

此步骤主要是把Authoring中的参数转接到自己定义的ComponentData;

  1. class EntitiesAuthoringBaker : Baker<EntitiesAuthoring>
  2. {
  3. public override void Bake(EntitiesAuthoring authoring)
  4. {
  5. var entity = GetEntity(TransformUsageFlags.None);
  6. AddComponent<EntitiesComponentData>(entity, new EntitiesComponentData
  7. {
  8. m_PrefabEntity = GetEntity(authoring.m_Prefab, TransformUsageFlags.Dynamic | TransformUsageFlags.Renderable),
  9. m_Row = authoring.m_Row,
  10. m_Col = authoring.m_Col
  11. });
  12. }
  13. }
  14. struct EntitiesComponentData : IComponentData
  15. {
  16. public Entity m_PrefabEntity;
  17. public int m_Row;
  18. public int m_Col;
  19. }

 4. 编写创建Entity的System脚本:

由于MonoBehaviour不会在SubScene中执行,System就类似MonoBehavior,拥有OnCreate, OnUpdate, OnDestroy生命周期。Entities框架会自动执行所有已经定义的System,若不想自动执行System可以在头部添加[DisableAutoCreation],然后使用state.World.CreateSystem<MoveEntitiesSystem>()手动创建System。

  1. [BurstCompile]
  2. partial struct SpawnEntitiesSystem : ISystem
  3. {
  4. void OnCreate(ref SystemState state)
  5. {
  6. //Application.targetFrameRate = 120;
  7. state.RequireForUpdate<EntitiesComponentData>();
  8. }
  9. void OnDestroy(ref SystemState state)
  10. {
  11. }
  12. void OnUpdate(ref SystemState state)
  13. {
  14. var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
  15. var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
  16. foreach (var data in SystemAPI.Query<EntitiesComponentData>())
  17. {
  18. Unity.Mathematics.Random m_Random = new Unity.Mathematics.Random(1);
  19. var m_RandomRange = new float4(-data.m_Row * 0.5f, data.m_Row * 0.5f, -data.m_Col * 0.5f, data.m_Col * 0.5f);
  20. var halfSize = new float2(data.m_Col * 0.5f, data.m_Row * 0.5f);
  21. for (int i = 0; i < data.m_Row; i++)
  22. {
  23. for (int j = 0; j < data.m_Col; j++)
  24. {
  25. var entity = state.EntityManager.Instantiate(data.m_PrefabEntity);
  26. ecb.SetComponent(entity, LocalTransform.FromPosition(new float3(j - halfSize.x, 0, i - halfSize.y)));
  27. ecb.AddComponent<TargetMovePointData>(entity, new TargetMovePointData() { targetPoint = new float3(m_Random.NextFloat(m_RandomRange.x, m_RandomRange.y), 0, m_Random.NextFloat(m_RandomRange.z, m_RandomRange.w)) });
  28. }
  29. }
  30. state.Enabled = false;
  31. }
  32. }
  33. }

 5. 定义一个System专门控制所有小人的移动:

使用JobSystem并行计算和设置小人的位置:

  1. [BurstCompile]
  2. partial struct MoveEntitiesJob : IJobParallelFor
  3. {
  4. [ReadOnly]
  5. public Unity.Mathematics.Random random;
  6. [ReadOnly]
  7. public float4 randomRange;
  8. [ReadOnly]
  9. public float moveSpeed;
  10. [ReadOnly]
  11. public NativeArray<Entity> entities;
  12. [NativeDisableParallelForRestriction]
  13. public EntityManager entityManager;
  14. [WriteOnly]
  15. public EntityCommandBuffer.ParallelWriter entityWriter;
  16. [BurstCompile]
  17. public void Execute(int index)
  18. {
  19. var entity = entities[index];
  20. var tPointData = entityManager.GetComponentData<TargetMovePointData>(entity);
  21. var tPoint = tPointData.targetPoint;
  22. var transform = entityManager.GetComponentData<LocalTransform>(entity);
  23. float3 curPoint = transform.Position;
  24. var offset = tPoint - curPoint;
  25. if (math.lengthsq(offset) < 0.4f)
  26. {
  27. tPointData.targetPoint = new float3(random.NextFloat(randomRange.x, randomRange.y), 0, random.NextFloat(randomRange.z, randomRange.w));
  28. entityWriter.SetComponent(index, entity, tPointData);
  29. }
  30. float3 moveDir = math.normalize(tPointData.targetPoint - curPoint);
  31. transform.Rotation = Quaternion.LookRotation(moveDir);
  32. transform.Position += moveDir * moveSpeed;
  33. entityWriter.SetComponent(index, entity, transform);
  34. }
  35. }

 Entities测试结果:

10万个移动的小人,140多帧,同等数量级比自定义BatchRendererGroup帧数高出20+,那是因为Entities Graphics内部实现了BatchRendererGroup的剔除Culling,相机视口外的物体会被Culling。自定义BatchRendererGroup只要在OnPerformCulling回调方法中实现Culling也能达到相同的性能:

 其实到这里测试已经没有意义了,GPU端Entities Graphics结局已经注定,所谓Entities ECS结构也只是对CPU的性能提升,但海量物体的渲染GPU是最大的瓶颈。

使用Entities 1.0.16安卓端性能:

1万个小人,12帧;

 查了查URP的渲染实现,其实内部也是Batch Renderer Group, 所以这也注定了无论是自定义Batch Renderer Group还是使用Entities,在移动端都是一样的性能。

扩展讨论:

Unity是支持配置Android端多线程策略的,Android CPU大小核性能差距巨大,大核性能强劲但耗电量大,小核弱鸡但省电,如果Jobs分配到小核执行会比主线程还慢,就会导致负优化。

既然万人同屏延迟主要在渲染线程,那么能否通过配置只允许渲染线程用大核心呢?(或者Unity默认策略就是这样的);

可以参考:Unity - Manual: Android thread configuration对Android线程策略进行配置。

结论:

目前为止(Entities 1.0.16)有太多致命局限性,也只是对PC平台做了性能优化,并且有开挂般的大幅提升;

但是对于移动平台收效甚微,JobSystem在移动平台并行线程数迷之变少,并且暂无开放接口设置。图形库必须为OpenGLES3及以上或Vulkan,与使用限制和开发门槛相比,ECS结构微弱的性能提升不值一提。

移动平台万人同屏方案尝试失败,弃坑。不过苍蝇再小也是肉,单线程的Unity编程方式早已跟不上如今的硬件发展,虽然HybridCLR不支持Burst加速,只能以解释方式执行JobSystem,但是安全的多线程对大量数据的计算仍然有很大的意义。

期待Entities的资源系统早日面世...

最后附上Entities万人同屏测试代码:

  1. using Unity.Burst;
  2. using Unity.Collections;
  3. using Unity.Entities;
  4. using Unity.Jobs;
  5. using Unity.Mathematics;
  6. using Unity.Transforms;
  7. using UnityEngine;
  8. using UnityEngine.Jobs;
  9. public class EntitiesAuthoring : MonoBehaviour
  10. {
  11. public GameObject m_Prefab;
  12. public int m_Row;
  13. public int m_Col;
  14. }
  15. class EntitiesAuthoringBaker : Baker<EntitiesAuthoring>
  16. {
  17. public override void Bake(EntitiesAuthoring authoring)
  18. {
  19. var entity = GetEntity(TransformUsageFlags.None);
  20. AddComponent<EntitiesComponentData>(entity, new EntitiesComponentData
  21. {
  22. m_PrefabEntity = GetEntity(authoring.m_Prefab, TransformUsageFlags.Dynamic | TransformUsageFlags.Renderable),
  23. m_Row = authoring.m_Row,
  24. m_Col = authoring.m_Col
  25. });
  26. }
  27. }
  28. struct EntitiesComponentData : IComponentData
  29. {
  30. public Entity m_PrefabEntity;
  31. public int m_Row;
  32. public int m_Col;
  33. }
  34. [BurstCompile]
  35. partial struct SpawnEntitiesSystem : ISystem
  36. {
  37. void OnCreate(ref SystemState state)
  38. {
  39. //Application.targetFrameRate = 120;
  40. state.RequireForUpdate<EntitiesComponentData>();
  41. }
  42. void OnDestroy(ref SystemState state)
  43. {
  44. }
  45. void OnUpdate(ref SystemState state)
  46. {
  47. var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
  48. var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
  49. foreach (var data in SystemAPI.Query<EntitiesComponentData>())
  50. {
  51. Unity.Mathematics.Random m_Random = new Unity.Mathematics.Random(1);
  52. var m_RandomRange = new float4(-data.m_Row * 0.5f, data.m_Row * 0.5f, -data.m_Col * 0.5f, data.m_Col * 0.5f);
  53. var halfSize = new float2(data.m_Col * 0.5f, data.m_Row * 0.5f);
  54. for (int i = 0; i < data.m_Row; i++)
  55. {
  56. for (int j = 0; j < data.m_Col; j++)
  57. {
  58. var entity = state.EntityManager.Instantiate(data.m_PrefabEntity);
  59. ecb.SetComponent(entity, LocalTransform.FromPosition(new float3(j - halfSize.x, 0, i - halfSize.y)));
  60. ecb.AddComponent<TargetMovePointData>(entity, new TargetMovePointData() { targetPoint = new float3(m_Random.NextFloat(m_RandomRange.x, m_RandomRange.y), 0, m_Random.NextFloat(m_RandomRange.z, m_RandomRange.w)) });
  61. }
  62. }
  63. state.Enabled = false;
  64. }
  65. }
  66. [BurstCompile]
  67. partial struct MoveEntitiesSystem : ISystem
  68. {
  69. Unity.Mathematics.Random m_Random;
  70. float4 m_RandomRange;
  71. void OnCreate(ref SystemState state)
  72. {
  73. state.RequireForUpdate<TargetMovePointData>();
  74. }
  75. void OnStartRunning(ref SystemState state)
  76. {
  77. if (SystemAPI.TryGetSingleton<EntitiesComponentData>(out var dt))
  78. {
  79. m_RandomRange = new float4(-dt.m_Row * 0.5f, dt.m_Row * 0.5f, -dt.m_Col * 0.5f, dt.m_Col * 0.5f);
  80. }
  81. else
  82. {
  83. m_RandomRange = new float4(-50, 50, -50, 50);
  84. }
  85. }
  86. void OnDestroy(ref SystemState state)
  87. {
  88. }
  89. void OnUpdate(ref SystemState state)
  90. {
  91. EntityCommandBuffer.ParallelWriter ecb = GetEntityCommandBuffer(ref state);
  92. m_Random = new Unity.Mathematics.Random((uint)Time.frameCount);
  93. var entityQuery = SystemAPI.QueryBuilder().WithAll<TargetMovePointData>().Build();
  94. var tempEntities = entityQuery.ToEntityArray(Allocator.TempJob);
  95. var moveJob = new MoveEntitiesJob
  96. {
  97. random = m_Random,
  98. moveSpeed = SystemAPI.Time.DeltaTime * 4,
  99. randomRange = m_RandomRange,
  100. entities = tempEntities,
  101. entityManager = state.EntityManager,
  102. entityWriter = ecb
  103. };
  104. var moveJobHandle = moveJob.Schedule(tempEntities.Length, 64);
  105. moveJobHandle.Complete();
  106. }
  107. private EntityCommandBuffer.ParallelWriter GetEntityCommandBuffer(ref SystemState state)
  108. {
  109. var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
  110. var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
  111. return ecb.AsParallelWriter();
  112. }
  113. }
  114. struct TargetMovePointData : IComponentData
  115. {
  116. public float3 targetPoint;
  117. }
  118. [BurstCompile]
  119. partial struct MoveEntitiesJob : IJobParallelFor
  120. {
  121. [ReadOnly]
  122. public Unity.Mathematics.Random random;
  123. [ReadOnly]
  124. public float4 randomRange;
  125. [ReadOnly]
  126. public float moveSpeed;
  127. [ReadOnly]
  128. public NativeArray<Entity> entities;
  129. [NativeDisableParallelForRestriction]
  130. public EntityManager entityManager;
  131. [WriteOnly]
  132. public EntityCommandBuffer.ParallelWriter entityWriter;
  133. [BurstCompile]
  134. public void Execute(int index)
  135. {
  136. var entity = entities[index];
  137. var tPointData = entityManager.GetComponentData<TargetMovePointData>(entity);
  138. var tPoint = tPointData.targetPoint;
  139. var transform = entityManager.GetComponentData<LocalTransform>(entity);
  140. float3 curPoint = transform.Position;
  141. var offset = tPoint - curPoint;
  142. if (math.lengthsq(offset) < 0.4f)
  143. {
  144. tPointData.targetPoint = new float3(random.NextFloat(randomRange.x, randomRange.y), 0, random.NextFloat(randomRange.z, randomRange.w));
  145. entityWriter.SetComponent(index, entity, tPointData);
  146. }
  147. float3 moveDir = math.normalize(tPointData.targetPoint - curPoint);
  148. transform.Rotation = Quaternion.LookRotation(moveDir);
  149. transform.Position += moveDir * moveSpeed;
  150. entityWriter.SetComponent(index, entity, transform);
  151. }
  152. }
  153. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/86212?site
推荐阅读
相关标签
  

闽ICP备14008679号