-
-
Save mattdymott/556888e4c804d0b26ad60034942fa7ee to your computer and use it in GitHub Desktop.
// CollisionResponse option on PhysicsShape must be set to CollideRaiseCollisionEvents. | |
[UpdateInGroup(typeof(PhysicsSystemGroup))] | |
[UpdateAfter(typeof(PhysicsSimulationGroup))] | |
public partial struct CalculateDetailsTest_PhysicsEventSystem : ISystem | |
{ | |
[BurstCompile] | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<SimulationSingleton>(); | |
state.RequireForUpdate<PhysicsWorldSingleton>(); | |
} | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
var physicsWorldSingleton = SystemAPI.GetSingleton<PhysicsWorldSingleton>(); | |
state.Dependency = new CollisionJob() | |
{ | |
PhysicsWorldSingleton = physicsWorldSingleton | |
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency); | |
} | |
[BurstCompile] | |
private struct CollisionJob : ICollisionEventsJob | |
{ | |
// Its important that this is marked as [ReadOnly], otherwise you will get errors. | |
[ReadOnly] public PhysicsWorldSingleton PhysicsWorldSingleton; | |
public void Execute(CollisionEvent collisionEvent) | |
{ | |
var collisionDetails = collisionEvent.CalculateDetails(ref PhysicsWorldSingleton.PhysicsWorld); | |
var avgContactPointPosition = collisionDetails.AverageContactPointPosition; | |
Debug.Log($"A: {collisionEvent.EntityA}, B: {collisionEvent.EntityB}, {avgContactPointPosition}"); | |
foreach(var contactPosition in collisionDetails.EstimatedContactPointPositions) | |
{ | |
Debug.Log($"{contactPosition}"); | |
} | |
} | |
} | |
} |
[UpdateInGroup(typeof(PhysicsSystemGroup))] | |
[UpdateAfter(typeof(PhysicsSimulationGroup))] | |
public partial struct TestPhysicsEventSystem : ISystem | |
{ | |
[BurstCompile] | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<SimulationSingleton>(); | |
} | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
state.Dependency = new CollisionJob().Schedule( | |
SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency); | |
} | |
[BurstCompile] | |
private struct CollisionJob : ICollisionEventsJob | |
{ | |
public void Execute(CollisionEvent collisionEvent) | |
{ | |
Debug.Log($"A: {collisionEvent.EntityA}, B: {collisionEvent.EntityB}"); | |
} | |
} | |
} |
public struct Touch : IComponentData | |
{} | |
public readonly struct Touched : IComponentData | |
{ | |
public readonly Entity Who; | |
public readonly float3 Normal; | |
public Touched(Entity who, float3 normal) | |
{ | |
Who = who; | |
Normal = normal; | |
} | |
} | |
[UpdateInGroup(typeof(PhysicsSystemGroup))] | |
[UpdateAfter(typeof(PhysicsSimulationGroup))] | |
public partial struct TouchSystem : ISystem | |
{ | |
[BurstCompile] | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<SimulationSingleton>(); | |
state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>(); | |
} | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
state.Dependency = new CollisionJob() | |
{ | |
TouchLookup = SystemAPI.GetComponentLookup<Touch>(true), | |
VelocityLookup = SystemAPI.GetComponentLookup<PhysicsVelocity>(true), | |
CommandBuffer = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>() | |
.CreateCommandBuffer(state.WorldUnmanaged) | |
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency); | |
} | |
[BurstCompile] | |
private struct CollisionJob : ICollisionEventsJob | |
{ | |
[ReadOnly] public ComponentLookup<Touch> TouchLookup; | |
[ReadOnly] public ComponentLookup<PhysicsVelocity> VelocityLookup; | |
public EntityCommandBuffer CommandBuffer; | |
private bool IsDynamic(Entity entity) => VelocityLookup.HasComponent(entity); | |
private bool IsTouchable(Entity entity) => TouchLookup.HasComponent(entity); | |
public void Execute(CollisionEvent collisionEvent) | |
{ | |
var entityA = collisionEvent.EntityA; | |
var entityB = collisionEvent.EntityB; | |
if(IsTouchable(entityA) && IsDynamic(entityB)) | |
{ | |
CommandBuffer.AddComponent(entityA, new Touched(entityB, collisionEvent.Normal)); | |
} | |
else | |
if(IsTouchable(entityB) && IsDynamic(entityA)) | |
{ | |
CommandBuffer.AddComponent(entityB, new Touched(entityA, collisionEvent.Normal)); | |
} | |
} | |
} | |
} |
I’m at work at the moment but will have a look later.
What does this do? How does it work?
It's just an example of how to use CollisionEvents in Unity.Physics.
Let me clarify. Apologies if this sounds too obvious since I'm a bit new to the ECS workflow (I know the basics, but still not an expert).
How does it work? What does SimulationSingleton do? I tried the TestPhysicsEventSystem and nothing happened. I tried the tuochsystem, nothing happened. And why do we have to Add a new component on each collision job call?
Ah I see, I disabled Collision Response on Physics Shape component 🤦🏽♂️
SimulationSingleton is part of Unity.Physics and is required for ICollisionEventsJob (which is again part of Unity.Physics, what they do with it behind the scenes I dont know, but you have to pass it into the Schedule method)
TestPhysicsEventSystem is just an example of the minimum you need to be able to do to receive CollisionEvents. It justs logs the 2 entities involved in a collision. And yes you need to enable CollisionResponse on the PhysicsShape.
TouchSystem just expands on this and is a simple example of tagging entities involved in a collision. For an entity to be involved in a collision (within this system) it would need a Touch component (so its 'Touchable'). It's just a way of filtering different types of collisions, you could use whatever component type you wanted (ie. Enemy, Player, Wall, etc).
The reason for adding a new component is just so you can take information out of the system and process it elsewhere (Who is the entity you collided with and Normal is the collision normal). You can do whatever you want instead, it was just an example.
The Gist isn't meant to be a fully featured system, I just refer back to it when I need reminding of the steps I need to take to get it working. You can use this for TriggerEvents as well. But nowadays I use the Stateful version which Unity.Physics provides in the Examples repository which gives you Enter, Stay and Exit state information.
Even better is the version tertle provides (https://github.com/tertle/com.bovinelabs.core) under PhysicsStates
Thank you so much! This is super helpful..
This example save my life 😁 Thank you so much !
Any ideas how to get the contact point? Using monobehavior it would be something like collision.GetContact(0).point.... or collider.ClosestPoint(vector).
I saw a hint suggesting to use AverageContactPointPosition in collisionEvent.CalculateDetails.... but I don't know how to reference Physics World without getting console errors
added new example to do this.
Awesome 😎
Thank you very much!
I had to pass in ref new PhysicsWorld() which worked for some reason. But I'll change it to your version.
Hello. Thanks immensely for this example. However, StepPhysicsWorld no longer exists in latest version (among other issues). Could it be possible for the code to be updated ?? Your example is extremely helpful !!!!