| name | unity-physicscore2d-collision |
| description | Collision detection, contact manifolds, collision responses, determinism, and character collision handling |
Unity PhysicsCore2D Collision Expert
You are now acting as a Unity PhysicsCore2D collision expert, specialized in collision detection, responses, and character movement.
Overview
PhysicsCore2D provides comprehensive collision detection and response capabilities including:
- Contact point generation and manifolds
- Collision callbacks and events (via IContactCallback)
- Character controller collision handling
- Deterministic collision behavior
- Bounce and restitution control
PhysicsCallbacks.IContactCallback
API Reference: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Unity.U2D.Physics.PhysicsCallbacks.IContactCallback.html
PhysicsCallbacks.IContactCallback is the interface for receiving collision events during physics simulation. Callbacks are assigned per-shape by setting the PhysicsShape.callbackTarget property.
Callback Methods
The interface provides two callback methods:
public interface IContactCallback
{
void OnContactBegin2D(PhysicsEvents.ContactBeginEvent beginEvent);
void OnContactEnd2D(PhysicsEvents.ContactEndEvent endEvent);
}
Event struct members (both ContactBeginEvent and ContactEndEvent):
| Member | Type | Description |
|---|
shapeA | PhysicsShape | One of the shapes; may be invalid if destroyed in an earlier callback |
shapeB | PhysicsShape | The other shape; same caveat |
contactId | PhysicsShape.ContactId | Volatile contact handle — call .contact to get the PhysicsShape.Contact |
Accessing the contact manifold from a begin event:
var contact = beginEvent.contactId.contact;
var manifold = contact.manifold;
How Callbacks Work
- Create a class that implements
PhysicsCallbacks.IContactCallback
- Set the
callbackTarget property on each PhysicsShape you want to monitor
- When that shape begins or ends contact with another shape, the callbacks are invoked during
PhysicsWorld.Step()
Implementation Example
using Unity.Collections;
using UnityEngine;
using Unity.U2D.Physics;
public class CollisionHandler : MonoBehaviour, PhysicsCallbacks.IContactCallback
{
private PhysicsWorld m_PhysicsWorld;
private void OnEnable()
{
m_PhysicsWorld = PhysicsWorld.Create();
m_PhysicsWorld.autoContactCallbacks = true;
var shapeDef = new PhysicsShapeDefinition
{
contactFilter = new PhysicsShape.ContactFilter { categories = PhysicsMask.One, contacts = PhysicsMask.All },
surfaceMaterial = new PhysicsShape.SurfaceMaterial { bounciness = 0.8f },
contactEvents = true,
};
var body1 = m_PhysicsWorld.CreateBody(new PhysicsBodyDefinition
{ type = PhysicsBody.BodyType.Dynamic, position = new Vector2(-0.25f, 2f) });
var body2 = m_PhysicsWorld.CreateBody(new PhysicsBodyDefinition
{ type = PhysicsBody.BodyType.Dynamic, position = new Vector2(0.25f, 4f) });
var shape1 = body1.CreateShape(CircleGeometry.defaultGeometry, shapeDef);
var shape2 = body2.CreateShape(CircleGeometry.defaultGeometry, shapeDef);
shape1.callbackTarget = ;
shape2.callbackTarget = ;
}
{
m_PhysicsWorld.Destroy();
}
{
(!beginEvent.shapeA.isValid || !beginEvent.shapeB.isValid)
;
contact = beginEvent.contactId.contact;
manifold = contact.manifold;
Debug.Log();
( mp manifold)
{
m_PhysicsWorld.DrawPoint(mp.point, radius: , color: Color.yellow, lifetime: );
}
}
{
(!endEvent.shapeA.isValid || !endEvent.shapeB.isValid)
;
Debug.Log();
}
}
Practical Example: Space Invaders Bullet Collision
using Unity.Collections;
using UnityEngine;
using Unity.U2D.Physics;
public class SpaceInvadersCollision : MonoBehaviour, PhysicsCallbacks.IContactCallback
{
private readonly PhysicsMask m_BulletMask = new(1);
private readonly PhysicsMask m_EnemyMask = new(2);
private readonly PhysicsMask m_WallMask = new(3);
private const float BulletSpeed = 20f;
private const float BulletRadius = 0.15f;
private PhysicsWorld m_World;
private Vector2 m_CannonPosition = new(0f, -4f);
private void OnEnable()
{
m_World = PhysicsWorld.defaultWorld;
m_World.autoContactCallbacks = ;
SpawnEnemies();
SpawnWalls();
}
{
bulletBody = m_World.CreateBody( PhysicsBodyDefinition
{
type = PhysicsBody.BodyType.Dynamic,
gravityScale = ,
fastCollisionsAllowed = ,
position = m_CannonPosition + Vector2.up * (BulletRadius + ),
linearVelocity = Vector2.up * BulletSpeed,
});
bulletShapeDef = PhysicsShapeDefinition
{
contactFilter = PhysicsShape.ContactFilter
{
categories = m_BulletMask,
contacts = m_EnemyMask | m_WallMask,
},
contactEvents = ,
};
bulletShape = bulletBody.CreateShape(
CircleGeometry { radius = BulletRadius }, bulletShapeDef);
bulletShape.callbackTarget = ;
}
{
(!beginEvent.shapeA.isValid || !beginEvent.shapeB.isValid)
;
catA = beginEvent.shapeA.contactFilter.categories;
catB = beginEvent.shapeB.contactFilter.categories;
(catA == m_WallMask || catB == m_WallMask)
{
bulletShape = catA == m_BulletMask ? beginEvent.shapeA : beginEvent.shapeB;
(bulletShape.isValid)
bulletShape.body.Destroy();
;
}
(catA == m_EnemyMask || catB == m_EnemyMask)
{
contact = beginEvent.contactId.contact;
hitPoint = contact.manifold.points[].point;
hitNormal = contact.manifold.normal;
Debug.Log();
bulletBody = catA == m_BulletMask ? beginEvent.shapeA.body : beginEvent.shapeB.body;
enemyBody = catA == m_EnemyMask ? beginEvent.shapeA.body : beginEvent.shapeB.body;
(bulletBody.isValid) bulletBody.Destroy();
(enemyBody.isValid) enemyBody.Destroy();
}
}
{
}
{
enemyShapeDef = PhysicsShapeDefinition
{
contactFilter = PhysicsShape.ContactFilter
{
categories = m_EnemyMask,
contacts = m_BulletMask,
},
contactEvents = ,
};
( col = ; col < ; col++)
( row = ; row < ; row++)
{
body = m_World.CreateBody( PhysicsBodyDefinition
{
type = PhysicsBody.BodyType.Static,
position = Vector2(col * - , row * + ),
});
shape = body.CreateShape(
PolygonGeometry.CreateBox( Vector2(, )), enemyShapeDef);
shape.callbackTarget = ;
}
}
{
wallShapeDef = PhysicsShapeDefinition
{
contactFilter = PhysicsShape.ContactFilter
{
categories = m_WallMask,
contacts = m_BulletMask,
},
};
wallBody = m_World.CreateBody();
wallBody.CreateShape(PolygonGeometry.CreateBox( Vector2(, ),
offset: Vector2(, )), wallShapeDef);
wallBody.CreateShape(PolygonGeometry.CreateBox( Vector2(, ),
offset: Vector2( , )), wallShapeDef);
}
}
Key reminder: shape.contactEvents = true (or in the PhysicsShapeDefinition) must be set on at least one shape in the pair, and world.autoContactCallbacks = true must be set, or no callbacks will be dispatched. Callbacks fire on the main thread after PhysicsWorld.Step() completes.
Important Notes
Setting Callback Targets:
- Set
PhysicsShape.callbackTarget property to an object implementing IContactCallback
- Each shape can have its own callback target
- Multiple shapes can share the same callback target object
- Callbacks are invoked during
PhysicsWorld.Step()
CRITICAL - Always Check Shape Validity:
Multiple callbacks can fire in the same frame. If an earlier callback destroys a PhysicsBody, subsequent callbacks involving that body will have invalid shapes. Always check shape.isValid before accessing shape properties:
public void OnContactBegin2D(PhysicsEvents.ContactBeginEvent contactEvent)
{
if (!contactEvent.shapeA.isValid || !contactEvent.shapeB.isValid)
return;
var bodyA = contactEvent.shapeA.body;
var bodyB = contactEvent.shapeB.body;
}
public void OnContactEnd2D(PhysicsEvents.ContactEndEvent contactEvent)
{
if (!contactEvent.shapeA.isValid || !contactEvent.shapeB.isValid)
return;
}
CRITICAL - Dynamic Body Requirement:
- Callbacks only fire for collisions involving at least one Dynamic body
- Kinematic vs Kinematic collisions DO NOT trigger callbacks
- Static vs Static collisions DO NOT trigger callbacks
- If using only Kinematic bodies (like in Space Invaders), you must either:
- Change at least one body type to Dynamic (e.g., make bullets Dynamic), OR
- Use manual overlap queries (from unity-physicscore2d-queries skill) instead
Performance:
- Callbacks are called during physics simulation, so keep them lightweight
- Avoid allocating memory in callbacks
- Cache frequently accessed data
Thread Safety:
- Contact callbacks may be called from the physics simulation thread
- Be careful when accessing Unity components or GameObjects
- Consider queuing actions to execute on the main thread if needed
When to Use IContactCallback vs Manual Queries
Use IContactCallback when:
- You have at least one Dynamic body involved in collisions
- You need precise collision timing during physics simulation
- You want automatic collision detection with minimal overhead
- You need Begin/End lifecycle events
Use Manual Queries (OverlapGeometry, etc.) when:
- You're using only Kinematic bodies that don't trigger callbacks
- You need to check for potential collisions before they happen
- You need fine control over when collision checks occur
- You're implementing custom character controllers with kinematic bodies
Repository Examples
Reference examples from the PhysicsExamples2D repository:
Key Collision Topics
Contact Manifolds
Contact manifolds contain information about collision contact points between two shapes:
- Contact point positions
- Contact normals
- Penetration depths
- Contact IDs for tracking persistent contacts
Character Movement
For character controllers that need collision detection:
- Use kinematic bodies for direct position control
- Query for overlaps before moving
- Resolve collisions manually for fine control
- Handle slopes and steps appropriately
Determinism
PhysicsCore2D is deterministic when:
- Same initial conditions are used
- Same sequence of operations is performed
- Floating point precision is consistent
- Random seeds are controlled
Collision Callbacks via IContactCallback
Monitor collisions by implementing PhysicsCallbacks.IContactCallback interface:
- OnContactBegin2D - when collision starts (first contact between shapes)
- OnContactEnd2D - when collision ends (shapes separate)
See the "PhysicsCallbacks.IContactCallback" section above for detailed implementation examples.
Important: Callbacks only fire for collisions involving at least one Dynamic body. Kinematic-only collisions require manual overlap queries.
Best Practices
- Always check collision results before acting on them
- Use appropriate collision layers and filtering
- Consider using continuous collision detection for fast-moving objects
- Cache collision queries when performing multiple checks
- For character controllers, prefer swept queries over simple overlaps
- Set
PhysicsShape.callbackTarget only on shapes that need collision notifications
Related Skills
When users need information about:
- Collision filtering - Use unity-physicscore2d-filtering
- Shape properties - Use unity-physicscore2d-shapes-advanced
- Physics queries - Use unity-physicscore2d-queries
- Materials and friction - Use unity-physicscore2d-materials