Skip to content

Instantly share code, notes, and snippets.

@mattdymott
Last active April 30, 2024 15:28
Show Gist options
  • Save mattdymott/556888e4c804d0b26ad60034942fa7ee to your computer and use it in GitHub Desktop.
Save mattdymott/556888e4c804d0b26ad60034942fa7ee to your computer and use it in GitHub Desktop.
Example of how to use ICollisionEventsJob from Unity.Physics
// 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));
}
}
}
}
@mattdymott
Copy link
Author

It's just an example of how to use CollisionEvents in Unity.Physics.

@LeMetamax
Copy link

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?

@LeMetamax
Copy link

Ah I see, I disabled Collision Response on Physics Shape component 🤦🏽‍♂️

@mattdymott
Copy link
Author

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.

@mattdymott
Copy link
Author

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.

@mattdymott
Copy link
Author

Even better is the version tertle provides (https://github.com/tertle/com.bovinelabs.core) under PhysicsStates

@LeMetamax
Copy link

Thank you so much! This is super helpful..

@MatthieuG9
Copy link

This example save my life 😁 Thank you so much !

@LeMetamax
Copy link

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

@mattdymott
Copy link
Author

added new example to do this.

@LeMetamax
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment