Defold includes a modified version of the Box2D physics engine for 2D physics simulations. It allows you to simulate Newtonian physics interactions between different types of collision objects. This manual explains how this works.
A collision object is a component you use to extend a game object with physical behaviour. A collision object has different physical properties (like weight, restitution and friction) and its spatial extension is defined by one or more shapes that you attach to the component. Defold supports the following types of collision objects:
The editor allows you to easily attach a collision object to any game object:
A collision object is constructed out of one or more physics shapes:
You add these shapes and can use the ordinary editor transform tools to scale, rotate and position them. Each collision object has a number of properties:
The Collision Shape property is used for tile map geometry that does not use ordinary primitive shapes. We’ll look at that below.
The Type property is used to set the type of collision object:
Trigger. If you set the object to
Dynamic you must set the Mass property to a non zero value. For dynamic or static objects you should also set the Friction and Restitution values.
If you set the type to
Dynamic and forget to set the mass to non zero you will get a compilation error:
"ERROR:GAMESYS: Invalid mass 0.000000 for shape type 0"
Friction makes it possible for objects to slide realistically against each other. The friction value is usually set between
0 (no friction at all—a very slippery object) and
1 (strong friction—an abrasive object). However, any positive value is valid.
The friction strength is proportional to the normal force (this is called Coulomb friction). When the friction force is computed between two shapes (
B), the friction values of both objects are combined by the geometric mean:
This means that if one of the objects has zero friction then the contact between them will have zero friction.
The restitution value sets the bounciness of the object. The value is usually between 0 (inelastic collision—the object does not bounce at all) and 1 (perfectly elastic collision—the object’s velocity will be exactly reflected in the bounce)
Restitution values between two shapes (
B) are combined using the following formula:
When a shape develops multiple contacts, restitution is simulated approximately because Box2D uses an iterative solver. Box2D also uses inelastic collisions when the collision velocity is small to prevent bounce-jitter
Damping reduces the linear and angular velocities of the body. It is different from friction, which only occurs during contact, and can be used to give objects a floaty appearance, like they are moving through something thicker than air. Valid values for both linear and angular damping are between 0 and 1.
Box2D approximates damping for stability and performance. At small values, the damping effect is independent of the time step while at larger damping values, the damping effect varies with the time step. If you run your game with a fixed time step, this never becomes an issue.
Setting this property totally disables rotation on the collision object, no matter what forces are brought to it.
It is often desirable to be able to filter collision so that some types of objects collide with some other type, but not with a third kind. For instance, in a multiplayer shooter game you might want:
The physics engine allows you to group your physics objects and filter how they should collide. This is handled by named collision groups. For each collision object you create two properties control how the object collides with other objects:
Note that each collision involves two objects and it is important that both objects mutually specify each other’s groups in their mask fields.
To achieve the collision scheme outlined for the hypothetical shooter game above, the following setup would work:
players, Mask =
The Defold editor has a tool that allows you to quickly generate physics shapes for a tilesource. In the tilesource editor, simply choose the image you wish to use as basis for collision. The editor will automatically generate a convex shape around each tile, taking into account pixels that are not 100% transparent:
The editor draws the shapes in the color of the collision group you have assigned to the tile. You can have multiple collision groups per tilesource and the choice of group color is done automatically by the editor:
To set a tile physics shape to a specific collision group, select the group and click the shape. To remove a tile shape from its collision group, select the “Tile source” root and click the tile.
To use the collision shapes from the Tile Source, create a collisionobject in the desired game object and select the Tile Source as its Collision Shape.
Make sure the Tile Source you connected to the tile map has an image set for collisions. They should be rendered as lines on top of the texture in the Editor view.
Make sure you have painted collision groups in the Tile Source for the tiles to support collision.
Set the added Collision Object component’s type to Static.
Set the Group and Mask properties.
Make sure that the Group and Mask properties of the Collision Objects you want colliding with the tiles are set accordingly.
When two objects collide, the engine will broadcast messages to all components in both objects:
This message is sent for all collision objects. It has the following fields set:
The collision_response message is only adequate to resolve collisions where you don’t need any details on the actual intersection of the objects, for example if you want to detect if a bullet hits an enemy. There is only one of these messages sent for any colliding pair of objects each frame.
This message is sent when one of the colliding objects is Dynamic or Kinematic. It has the following fields set:
For a game or application where you need to separate objects perfectly, the
contact_point_response message gives you all information you need. However, note that for any given collision pair, a number of
contact_point_response messages can be received each frame.
Triggers are light weight collision objects. In a trigger collision
collision_response messages are sent. In addition, triggers also send a special
trigger_response message when the collision begins and end. The message has the following fields:
trueif the interaction was an entry into the trigger,
falseif it was an exit. (
With Kinematic collision objects you have to resolve collisions yourself and move the objects apart from any penetration. A naive implementation of object separation looks like this:
function on_message(self, message_id, message, sender) -- Handle collision if message_id == hash("contact_point_response") then local newpos = go.get_position() + message.normal * message.distance go.set_position(newpos) end end
This code will separate your Kinematic object from other physics object it penetrates, but the separation often overshoots and you will see jitter in many cases. To properly take a Kinematic object out of a collision we need to consider cases like the following:
Here we will get two contact point messages, one for Object A and one for Object B. With the naive separation process above, the penetration vectors would just be added one after the other, resulting in the following separation:
Instead, we should iterate through the contact points and check if previous separations have already wholly or partially solved the separation we are about to make. In the collision example above we start by separation our character shape from Object A:
We move our object along the normal of the Object A the distance of the penetration (the dotted red vector/arrow above). Now it is easy to see that the final movement we need to perform along the normal of Object B has already been partially covered by the first separation against Object A. We only need to separate along the filled green vector instead of the original one (dotted green).
It is straightforward to calculate the distance the previous movement out of Object B covered for us:
The distance covered can be found if we project the penetration vector of Object A against the one of Object B. To find the final movement we need to perform we just subtract +l+ from our original penetration vector. For an arbitrary number of penetrations, we can accumulate the actual movements into a vector and project against each penetration vector in turn. The full implementation looks like this:
function on_message(self, message_id, message, sender) -- Handle collision if message_id == hash("contact_point_response") then -- Get the info needed to move out of collision. We might -- get several contact points back and have to calculate -- how to move out of all accumulatively: if message.distance > 0 then -- First, project the penetration vector on -- accumulated correction local proj = vmath.project(self.correction, message.normal * message.distance) if proj < 1 then -- Only care for projections that does not overshoot. local comp = (message.distance - message.distance * proj) * message.normal -- Apply compensation go.set_position(go.get_position() + comp) -- Accumulate the corrections done self.correction = self.correction + comp end end end end
Kinematic objects are more expensive than triggers, so use them wisely.
If you are making a game with a player character (of some sort) that you maneuver through a level, it might seem like a good idea to create the player character as a Dynamic physics object and the world as a Static physics object. Player input is then handled by applying various forces on the player object.
Going down that path is possible, but it is extremely hard to achieve great results. Your game controls will likely feel generic—like thousands of other games, since it is implemented the same way on the same physics engine. The problem boils down to the fact that the Box2D physics simulation is a realistic Newtonian simulation whereas a platformer is usually fundamentally different. You will therefore have to fight hard to make a Newtonian simulation behave in a non-Newtonian fashion.
One immediate problem is what should happen at edges. With a dynamic simulation running, the player physics object (here set up as a box) behaves like a realistic box and will tip over any edges.
This particular problem can be solved by setting the “Locked Rotation” property in the character’s collision object. However, the example illustrates the core of the problem which is that the behavior of the character should be under the control of you, the designer/programmer and not be directly controlled by a physics simulation over which you have very limited control.
So it is highly recommended that you implement your player character as a Kinematic physics object. Use the physics engine to detect collisions and deal with collisions and object separations as you need. Such an approach will initially require more work, but will allow you to design and fine-tune the player experience into something really good and unique.
(Some of the graphic assets used are made by Kenney: http://kenney.nl/assets)