I set out to do some quick tests for an upcoming Quest 3 project to see how Unity Physics/ECS Graphics compares to PhysX, but I think I’m doing something wrong (I’m new to DOTS/ECS and working through Unity’s implementations)
I created a simple scene with a Monobehaviour that instantiates 1000 rigidbodies with sphere colliders and adds some forces in FixedUpdate() to keep them together. it’s pinned at 72 FPS no matter what I do or how many contacts there are:
I created another scene that instantiates 1000 entities with sphere physics bodies/shapes and moves them with a system in a similar manner to the PhysX test:
This one hovers around 72fps but dips down super low when they bunch up and there are a lot of contacts (around :43s in the video). The profiler is showing BuildPhysicsWorld taking a while:
I’m curious if this is expected or I’m missing something important (seems like the latter)… or maybe this specific setup on Quest 3 is just a bad test for ECS Physics? Any insight to get this running faster or if I’m doing something wrong is much appreciated
- Unity Entities/Physics/Graphics 1.2.1
- Burst is enabled, safety checks off
- has a PhysicsStep with Multi-Threaded on, Enable Contact Solver off (tried both), Synchronize Collision World off
- spheres collide with themselves and hands, hands are kinematic and only collide with spheres
- Vulkan
baker for the prefab with the sphere physics body/shape:
using UnityEngine;
using Unity.Entities;
public class UnitConfigAuthoring : MonoBehaviour
{
public int numUnits;
public GameObject unitPrefab;
class Baker : Baker<UnitConfigAuthoring>
{
public override void Bake(UnitConfigAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new UnitConfig
{
numUnits = authoring.numUnits,
unitPrefab = GetEntity(authoring.unitPrefab, TransformUsageFlags.Dynamic)
});
}
}
}
public struct UnitConfig : IComponentData
{
public int numUnits;
public Entity unitPrefab;
}
spawn spheres system:
using Unity.Entities;
using Unity.Burst;
using Unity.Mathematics;
using Unity.Transforms;
[BurstCompile]
[UpdateInGroup(typeof(InitializationSystemGroup), OrderFirst = true)]
public partial struct UnitSpawnerSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<UnitConfig>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
//run once
state.Enabled = false;
var config = SystemAPI.GetSingleton<UnitConfig>();
var rand = new Unity.Mathematics.Random(123);
for( var i = 0; i < config.numUnits; i++)
{
var r = rand.NextFloat();
var unit = state.EntityManager.Instantiate(config.unitPrefab);
state.EntityManager.AddComponent<UnitTag>(unit);
var pos = new float3(0, 1f, 0) + rand.NextFloat3Direction() * r * r * r;
state.EntityManager.SetComponentData(unit, new LocalTransform
{
Position = pos,
Scale = 0.08f,
Rotation = quaternion.identity
});
state.EntityManager.AddComponent<UnitT>(unit);
state.EntityManager.SetComponentData(unit, new UnitT
{
Value = (float)i / config.numUnits
});
}
}
public struct UnitTag : IComponentData
{
}
public struct UnitT : IComponentData
{
public float Value;
}
}
sphere movement system:
using Unity.Entities;
using Unity.Burst;
using Unity.Physics;
using Unity.Mathematics;
using Unity.Transforms;
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct UnitMovementSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<UnitConfig>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var time = (float)SystemAPI.Time.ElapsedTime;
var job = new UnitMovementJob
{
deltaTime = SystemAPI.Time.DeltaTime,
maxDist = 3f,
constrainDist = 0.6f,
noiseScale = 1.3f,
time = time,
noiseOffset = new float2(time * 0.2f, 0),
center = new float3(0, 1.2f, 0)
};
job.ScheduleParallel();
}
}
[WithAll(typeof(UnitSpawnerSystem.UnitTag))]
[BurstCompile]
public partial struct UnitMovementJob : IJobEntity
{
public float deltaTime;
public float time;
public float maxDist;
public float constrainDist;
public float noiseScale;
public float2 noiseOffset;
public float3 center;
[BurstCompile]
public void Execute(ref LocalTransform unitTransform, ref PhysicsVelocity unitVelocity, in UnitSpawnerSystem.UnitT unitT)
{
var pos = unitTransform.Position;
var distPos = pos - center;
var distFromCenter = math.length(distPos);
if (unitTransform.Position.y < 0)
{
unitTransform.Position.y = 0;
unitVelocity.Linear.y = math.abs(unitVelocity.Linear.y);
}
if (distFromCenter > maxDist)
{
unitTransform.Position = center + math.normalize(pos - center) * maxDist;
unitVelocity.Linear = -math.normalize(distPos) * math.min(0.5f, math.length(unitVelocity.Linear));
} else if (distFromCenter > constrainDist)
{
unitVelocity.Linear += -math.normalize(distPos) * deltaTime * 0.4f;
}
else
{
unitVelocity.Linear += noise.srdnoise(distPos.xz * noiseScale + (noiseOffset + new float2(unitT.Value) * 10f) ) * deltaTime * 0.2f;
}
}
}