Defold Physics Event Handling

Previously, physics interactions in Defold were handled by broadcasting messages to all components of colliding objects. However, starting with version 1.6.4, Defold offers a more centralized approach through the physics.set_listener() function. This function allows you to set a custom listener to handle all physics interaction events in one place, thereby streamlining your code and improving efficiency.

Setting the Physics World Listener

In Defold, each collection proxy creates its own separate physics world. Therefore, when you are working with multiple collection proxies, it’s essential to manage the distinct physics worlds associated with each. To ensure that physics events are handled correctly in each world, you must set a physics world listener specifically for each collection proxy’s world.

This setup means that the listener for physics events must be set from within the context of the collection that the proxy represents. By doing so, you associate the listener directly with the relevant physics world, enabling it to process physics events accurately.

Here is an example of how to set a physics world listener within a collection proxy:

function init(self)
    -- Assuming this script is attached to a game object within the collection loaded by the proxy
    -- Set the physics world listener for the physics world of this collection proxy
    physics.set_listener(physics_world_listener)
end

By implementing this method, you ensure that each physics world generated by a collection proxy has its dedicated listener. This is crucial for handling physics events effectively in projects that utilize multiple collection proxies.

If a listener is set, physics messages will no longer be sent for the physics world where this listener is set.

Event Data Structure

Each physics event provides a data table containing specific information relevant to the event.

  1. Contact Point Event (contact_point_event): This event reports a contact point between two collision objects. It is useful for detailed collision handling, such as calculating impact forces or custom collision responses.

    • applied_impulse: The impulse resulting from the contact.
    • distance: The penetration distance between the objects.
    • a and b: Objects representing the colliding entities, each containing:
      • position: World position (vector3).
      • id: Instance ID (hash).
      • group: Collision group (hash).
      • relative_velocity: Velocity relative to the other object (vector3).
      • mass: Mass in kilograms (number).
      • normal: Contact normal, pointing from the other object (vector3).
  2. Collision Event (collision_event): This event indicates that a collision has occurred between two objects. It is a more generalized event compared to the contact point event, ideal for detecting collisions without needing detailed information about the contact points.

    • a and b: Objects representing the colliding entities, each containing:
      • position: World position (vector3).
      • id: Instance ID (hash).
      • group: Collision group (hash).
  3. Trigger Event (trigger_event): This event is sent when an object interacts with a trigger object. It’s useful for creating areas in your game that cause something to happen when an object enters or exits.

    • enter: Indicates if the interaction was an entry (true) or an exit (false).
    • a and b: Objects involved in the trigger event, each containing:
      • id: Instance ID (hash).
      • group: Collision group (hash).
  4. Ray Cast Response (ray_cast_response): This event is sent in response to a raycast, providing information about the object hit by the ray.

    • group: Collision group of the hit object (hash).
    • request_id: Identifier of the raycast request (number).
    • position: Hit position (vector3).
    • fraction: The fraction of the ray’s length at which the hit occurred (number).
    • normal: Normal at the hit position (vector3).
    • id: Instance ID of the hit object (hash).
  5. Ray Cast Missed (ray_cast_missed): This event is sent when a raycast does not hit any object.

    • request_id: Identifier of the raycast request that missed (number).

Example Usage

local function physics_world_listener(self, event, data)
    if event == hash("contact_point_event") then
        -- Handle detailed contact point data
        pprint(data)
    elseif event == hash("collision_event") then
        -- Handle general collision data
        pprint(data)
    elseif event == hash("trigger_event") then
        -- Handle trigger interaction data
        pprint(data)
    elseif event == hash("ray_cast_response") then
        -- Handle raycast hit data
        pprint(data)
    elseif event == hash("ray_cast_missed") then
        -- Handle raycast miss data
        pprint(data)
    end
end

function init(self)
    physics.set_listener(physics_world_listener)
end

Limitations

The listener calls synchronously at the moment it occurs. It happens in the middle of a timestep, which means that the physics world is locked. This makes it impossible to use functions that may affect physics world simulations, e.g., physics.create_joint().

Here is a small example of how to avoid these limitations:

local function physics_world_listener(self, event, data)
    if event == hash("contact_point_event") then
        local position_a = event.a.normal * SIZE
        local position_b =  event.b.normal * SIZE
        local url_a = msg.url(nil, event.a.id, "collisionobject")
        local url_b = msg.url(nil, event.b.id, "collisionobject")
        -- fill the message in the same way arguments should be passed to `physics.create_joint()`
        local message = {physics.JOINT_TYPE_FIXED, url_a, "joind_id", position_a, url_b, position_b, {max_length = SIZE}}
        -- send message to the object itself
        msg.post(".", "create_joint", message)
    end
end

function on_message(self, message_id, message)
    if message_id == hash("create_joint") then
        -- unpack message with function arguments
        physics.create_joint(unpack(message))
    end
end

function init(self)
    physics.set_listener(physics_world_listener)
end