Skip to content

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.

teal
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)
FieldTypeDescription
otherintegerEntity ID of the other body
shapeShapeOur shape
otherShapeShapeTheir shape
contactContactBox2D contact object
normalXnumberCollision normal X
normalYnumberCollision normal Y

physics.EndContact

Emitted when two shapes stop touching.

teal
world:observe(id, physics.EndContact, function(e: physics.EndContact)
    print("Stopped touching entity", e.other)
end)
FieldTypeDescription
otherintegerEntity ID of the other body
shapeShapeOur shape
otherShapeShapeTheir shape
contactContactBox2D contact object
normalXnumberCollision normal X
normalYnumberCollision normal Y

physics.PreSolve

Emitted before collision response is calculated. Use this to disable contacts conditionally.

teal
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)
FieldTypeDescription
otherintegerEntity ID of the other body
shapeShapeOur shape
otherShapeShapeTheir shape
contactContactBox2D 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.

teal
world:observe(id, physics.PostSolve, function(e: physics.PostSolve)
    if e.normalImpulse > 100 then
        print("Hard impact!", e.normalImpulse)
    end
end)
FieldTypeDescription
otherintegerEntity ID of the other body
shapeShapeOur shape
otherShapeShapeTheir shape
contactContactBox2D contact object
normalImpulsenumberImpulse along collision normal
tangentImpulsenumberImpulse 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:

teal
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:

teal
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
}))