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 extends 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 object:
These do not react in themselves, but any other object that collides with static objects will react. These are very useful (and cheap performance-wise) for building level geometry (i.e. ground and walls) that does not move. You cannot move or otherwise change static objects.
The physics engine solves all collisions for you and applies resulting forces. These are good for objects that should behave realistically, with the caveat that the only way you can manually control them is by applying forces to them.
These types of objects will collide with other physics objects, but the job of resolving the collisions (or ignoring them) is yours. Kinematic objects are good when you need objects that collide and you want fine grained control over all reactions.
Triggers are objects that register simple collisions. These are good for game logic where you want some event to happen when some other object (i.e. the player character) reaches a specific spot.
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: "Dynamic", "Kinematic", "Static" or "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.
Friction makes it possible to make object 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, the friction values of both objects are combined by the geometric mean:
combined_friction = math.sqrt(shape1_friction * shape2_friction)
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 objects are combined using the following formula:
combined_restitution = math.max(shape1_restitution, shape2_restitution)
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 that only occurs with 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 is 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:
The name of the collison group the object should belong to. You can have 16 different groups and you name them as you see fit for your application/game. For example "players", "bullets", "enemies" and "world".
The other groups this object should collide with. You can name one group or specify multiple groups in a comma separated list. If you leave the Mask field empty, the object will collide with nothing.
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:
Group: "players", Mask: "world, enemies"
Group: "bullets", Mask: "enemies"
Group: "enemies", Mask: "players, bullets"
Group: "world", Mask: "players, enemies"
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.
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 id of the instance the collision object collided with (hash)
the world position of the instance the collision object collided with (vec3)
the collision group of the other collision object (hash)
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:
world position of the contact point (vector3)
normal in world space of the contact point, which points from the other object towards the current object (vector3)
the relative velocity of the collision object as observed from the other object (vector3)
the penetration distance between the objects—always positive (number)
the impulse the contact resulted in (number)
life time of the contact (not currently used!) (number)
the mass of the current collision object in kg (number)
the mass of the other collision object in kg (number)
the id of the instance the collision object is in contact with (hash)
the world position of the other collision object (vector3)
the collision group of the other collision object (hash)
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 message 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:
the id of the instance the collision object collided with (hash)
true if the interaction was an entry into the trigger, false if 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
Trigger collision objects are sometimes too limited. Suppose you want a trigger that controls the intensity of a sound—the further the player moves into the trigger, the more intense the sound. This scenario requires a trigger that provides the penetration distance in the trigger. For this, a plain trigger collision object won’t do. Instead, you can set up a Kinematic object and never performing any separation of collisions but instead only registering them and use the collision data.
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 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 being 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 allows you to really 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)