赞
踩
当前测试使用的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不能从资源动态加载,路被彻底封死了。
- public class EntitiesAuthoring : MonoBehaviour
- {
- public GameObject m_Prefab;
- public int m_Row;
- public int m_Col;
- }
3. 把步骤2的挂有Authoring脚本的GameObject转换为Entity:
SubScene中挂载的MonoBehaviour不会被执行,必须转换为Entity;
此步骤主要是把Authoring中的参数转接到自己定义的ComponentData;
-
- class EntitiesAuthoringBaker : Baker<EntitiesAuthoring>
- {
- public override void Bake(EntitiesAuthoring authoring)
- {
- var entity = GetEntity(TransformUsageFlags.None);
- AddComponent<EntitiesComponentData>(entity, new EntitiesComponentData
- {
- m_PrefabEntity = GetEntity(authoring.m_Prefab, TransformUsageFlags.Dynamic | TransformUsageFlags.Renderable),
- m_Row = authoring.m_Row,
- m_Col = authoring.m_Col
- });
- }
- }
-
- struct EntitiesComponentData : IComponentData
- {
- public Entity m_PrefabEntity;
- public int m_Row;
- public int m_Col;
- }
4. 编写创建Entity的System脚本:
由于MonoBehaviour不会在SubScene中执行,System就类似MonoBehavior,拥有OnCreate, OnUpdate, OnDestroy生命周期。Entities框架会自动执行所有已经定义的System,若不想自动执行System可以在头部添加[DisableAutoCreation],然后使用state.World.CreateSystem<MoveEntitiesSystem>()手动创建System。
- [BurstCompile]
- partial struct SpawnEntitiesSystem : ISystem
- {
- void OnCreate(ref SystemState state)
- {
- //Application.targetFrameRate = 120;
- state.RequireForUpdate<EntitiesComponentData>();
- }
- void OnDestroy(ref SystemState state)
- {
-
- }
-
- void OnUpdate(ref SystemState state)
- {
- var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
- var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
-
- foreach (var data in SystemAPI.Query<EntitiesComponentData>())
- {
- Unity.Mathematics.Random m_Random = new Unity.Mathematics.Random(1);
- 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);
- var halfSize = new float2(data.m_Col * 0.5f, data.m_Row * 0.5f);
- for (int i = 0; i < data.m_Row; i++)
- {
- for (int j = 0; j < data.m_Col; j++)
- {
- var entity = state.EntityManager.Instantiate(data.m_PrefabEntity);
- ecb.SetComponent(entity, LocalTransform.FromPosition(new float3(j - halfSize.x, 0, i - halfSize.y)));
- 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)) });
- }
- }
- state.Enabled = false;
- }
- }
- }
5. 定义一个System专门控制所有小人的移动:
使用JobSystem并行计算和设置小人的位置:
- [BurstCompile]
- partial struct MoveEntitiesJob : IJobParallelFor
- {
- [ReadOnly]
- public Unity.Mathematics.Random random;
- [ReadOnly]
- public float4 randomRange;
-
- [ReadOnly]
- public float moveSpeed;
-
- [ReadOnly]
- public NativeArray<Entity> entities;
- [NativeDisableParallelForRestriction]
- public EntityManager entityManager;
- [WriteOnly]
- public EntityCommandBuffer.ParallelWriter entityWriter;
-
- [BurstCompile]
- public void Execute(int index)
- {
- var entity = entities[index];
- var tPointData = entityManager.GetComponentData<TargetMovePointData>(entity);
- var tPoint = tPointData.targetPoint;
- var transform = entityManager.GetComponentData<LocalTransform>(entity);
-
- float3 curPoint = transform.Position;
- var offset = tPoint - curPoint;
- if (math.lengthsq(offset) < 0.4f)
- {
- tPointData.targetPoint = new float3(random.NextFloat(randomRange.x, randomRange.y), 0, random.NextFloat(randomRange.z, randomRange.w));
- entityWriter.SetComponent(index, entity, tPointData);
- }
-
- float3 moveDir = math.normalize(tPointData.targetPoint - curPoint);
- transform.Rotation = Quaternion.LookRotation(moveDir);
- transform.Position += moveDir * moveSpeed;
- entityWriter.SetComponent(index, entity, transform);
- }
- }
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万人同屏测试代码:
- using Unity.Burst;
- using Unity.Collections;
- using Unity.Entities;
- using Unity.Jobs;
- using Unity.Mathematics;
- using Unity.Transforms;
- using UnityEngine;
- using UnityEngine.Jobs;
-
- public class EntitiesAuthoring : MonoBehaviour
- {
- public GameObject m_Prefab;
- public int m_Row;
- public int m_Col;
- }
-
- class EntitiesAuthoringBaker : Baker<EntitiesAuthoring>
- {
- public override void Bake(EntitiesAuthoring authoring)
- {
- var entity = GetEntity(TransformUsageFlags.None);
- AddComponent<EntitiesComponentData>(entity, new EntitiesComponentData
- {
- m_PrefabEntity = GetEntity(authoring.m_Prefab, TransformUsageFlags.Dynamic | TransformUsageFlags.Renderable),
- m_Row = authoring.m_Row,
- m_Col = authoring.m_Col
- });
- }
- }
-
- struct EntitiesComponentData : IComponentData
- {
- public Entity m_PrefabEntity;
- public int m_Row;
- public int m_Col;
- }
- [BurstCompile]
- partial struct SpawnEntitiesSystem : ISystem
- {
- void OnCreate(ref SystemState state)
- {
- //Application.targetFrameRate = 120;
- state.RequireForUpdate<EntitiesComponentData>();
- }
- void OnDestroy(ref SystemState state)
- {
-
- }
-
- void OnUpdate(ref SystemState state)
- {
- var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
- var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
-
- foreach (var data in SystemAPI.Query<EntitiesComponentData>())
- {
- Unity.Mathematics.Random m_Random = new Unity.Mathematics.Random(1);
- 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);
- var halfSize = new float2(data.m_Col * 0.5f, data.m_Row * 0.5f);
- for (int i = 0; i < data.m_Row; i++)
- {
- for (int j = 0; j < data.m_Col; j++)
- {
- var entity = state.EntityManager.Instantiate(data.m_PrefabEntity);
- ecb.SetComponent(entity, LocalTransform.FromPosition(new float3(j - halfSize.x, 0, i - halfSize.y)));
- 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)) });
- }
- }
- state.Enabled = false;
-
- }
- }
- [BurstCompile]
- partial struct MoveEntitiesSystem : ISystem
- {
- Unity.Mathematics.Random m_Random;
- float4 m_RandomRange;
- void OnCreate(ref SystemState state)
- {
- state.RequireForUpdate<TargetMovePointData>();
- }
- void OnStartRunning(ref SystemState state)
- {
- if (SystemAPI.TryGetSingleton<EntitiesComponentData>(out var dt))
- {
- m_RandomRange = new float4(-dt.m_Row * 0.5f, dt.m_Row * 0.5f, -dt.m_Col * 0.5f, dt.m_Col * 0.5f);
- }
- else
- {
- m_RandomRange = new float4(-50, 50, -50, 50);
- }
- }
- void OnDestroy(ref SystemState state)
- {
-
- }
-
- void OnUpdate(ref SystemState state)
- {
- EntityCommandBuffer.ParallelWriter ecb = GetEntityCommandBuffer(ref state);
- m_Random = new Unity.Mathematics.Random((uint)Time.frameCount);
- var entityQuery = SystemAPI.QueryBuilder().WithAll<TargetMovePointData>().Build();
- var tempEntities = entityQuery.ToEntityArray(Allocator.TempJob);
-
- var moveJob = new MoveEntitiesJob
- {
- random = m_Random,
- moveSpeed = SystemAPI.Time.DeltaTime * 4,
- randomRange = m_RandomRange,
- entities = tempEntities,
- entityManager = state.EntityManager,
- entityWriter = ecb
- };
- var moveJobHandle = moveJob.Schedule(tempEntities.Length, 64);
- moveJobHandle.Complete();
- }
- private EntityCommandBuffer.ParallelWriter GetEntityCommandBuffer(ref SystemState state)
- {
- var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
- var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
- return ecb.AsParallelWriter();
- }
- }
- struct TargetMovePointData : IComponentData
- {
- public float3 targetPoint;
- }
- [BurstCompile]
- partial struct MoveEntitiesJob : IJobParallelFor
- {
- [ReadOnly]
- public Unity.Mathematics.Random random;
- [ReadOnly]
- public float4 randomRange;
-
- [ReadOnly]
- public float moveSpeed;
-
- [ReadOnly]
- public NativeArray<Entity> entities;
- [NativeDisableParallelForRestriction]
- public EntityManager entityManager;
- [WriteOnly]
- public EntityCommandBuffer.ParallelWriter entityWriter;
-
- [BurstCompile]
- public void Execute(int index)
- {
- var entity = entities[index];
- var tPointData = entityManager.GetComponentData<TargetMovePointData>(entity);
- var tPoint = tPointData.targetPoint;
- var transform = entityManager.GetComponentData<LocalTransform>(entity);
-
- float3 curPoint = transform.Position;
- var offset = tPoint - curPoint;
- if (math.lengthsq(offset) < 0.4f)
- {
- tPointData.targetPoint = new float3(random.NextFloat(randomRange.x, randomRange.y), 0, random.NextFloat(randomRange.z, randomRange.w));
- entityWriter.SetComponent(index, entity, tPointData);
- }
-
- float3 moveDir = math.normalize(tPointData.targetPoint - curPoint);
- transform.Rotation = Quaternion.LookRotation(moveDir);
- transform.Position += moveDir * moveSpeed;
- entityWriter.SetComponent(index, entity, transform);
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。