Collision Events
Listen for collisions on specific entities using entity observers. Events are emitted to both entities involved in a collision.
See World:setCallbacks for details on Box2D collision callbacks.
physics.BeginContact
Emitted when two shapes start touching.
local id = world:spawn(
Transform(100, 100),
physics.Collider.new({ shape = "circle", radius = 16 }),
physics.RigidBody.new({ mass = 1 })
)
world:observe(id, physics.BeginContact, function(e: physics.BeginContact)
print("Collided with entity", e.other)
print("Collision normal:", e.normalX, e.normalY)
end)| Field | Type | Description |
|---|---|---|
other | integer | Entity ID of the other body |
shape | Shape | Our shape |
otherShape | Shape | Their shape |
contact | Contact | Box2D contact object |
normalX | number | Collision normal X |
normalY | number | Collision normal Y |
physics.EndContact
Emitted when two shapes stop touching.
world:observe(id, physics.EndContact, function(e: physics.EndContact)
print("Stopped touching entity", e.other)
end)| Field | Type | Description |
|---|---|---|
other | integer | Entity ID of the other body |
shape | Shape | Our shape |
otherShape | Shape | Their shape |
contact | Contact | Box2D contact object |
normalX | number | Collision normal X |
normalY | number | Collision normal Y |
physics.PreSolve
Emitted before collision response is calculated. Use this to disable contacts conditionally.
world:observe(id, physics.PreSolve, function(e: physics.PreSolve)
-- One-way platform: allow passing through from below
local _, ny = e.contact:getNormal()
if ny > 0 then
e.contact:setEnabled(false)
end
end)| Field | Type | Description |
|---|---|---|
other | integer | Entity ID of the other body |
shape | Shape | Our shape |
otherShape | Shape | Their shape |
contact | Contact | Box2D contact object |
See Contact:setEnabled for disabling contacts.
physics.PostSolve
Emitted after collision response with impulse data. Use this for damage calculations or sound effects based on impact strength.
world:observe(id, physics.PostSolve, function(e: physics.PostSolve)
if e.normalImpulse > 100 then
print("Hard impact!", e.normalImpulse)
end
end)| Field | Type | Description |
|---|---|---|
other | integer | Entity ID of the other body |
shape | Shape | Our shape |
otherShape | Shape | Their shape |
contact | Contact | Box2D contact object |
normalImpulse | number | Impulse along collision normal |
tangentImpulse | number | Impulse along collision tangent |
Collision Filtering
Category Bitmasks
Use physics.categories() to create bitmasks, and set categories and mask on Colliders to control which objects collide:
local PLAYER = physics.categories(1)
local ENEMY = physics.categories(2)
local PROJECTILE = physics.categories(3)
-- Player collides with enemies but not own projectiles
world:spawn(
Transform(100, 100),
physics.Collider.new({
shape = "circle",
radius = 16,
categories = PLAYER,
mask = ENEMY
}),
physics.RigidBody.new({ mass = 1 })
)See Shape:setFilterData for more on collision filtering.
Layer-Based Filtering
The default contact filter rejects collisions between entities on different Transform.layer values. Two entities will only collide if they share the same layer. This works alongside category/mask filtering.
To override this behavior, provide a custom contactFilter in the plugin config. The filter receives both Box2D shapes and their associated ShapeData, and returns true to allow the collision or false to reject it:
world:addPlugin(physics.new({
world = physicsWorld,
contactFilter = function(
shapeA: love.physics.Shape,
shapeB: love.physics.Shape,
dataA: physics.ShapeData,
dataB: physics.ShapeData
): boolean
-- ShapeData fields:
-- .entity (integer) entity ID
-- .layer (integer) Transform layer
-- .categories (integer) collision category bits
-- .mask (integer) collision mask bits
-- .name (string) shape name
-- Example: only collide if on the same layer
return dataA.layer == dataB.layer
end
}))