Corona Reference: Physics
Setting Up the Physics World
Include the following line at the beginning of your code to enable the physics library:
local physics = require( "physics" )
Next, include the following line to begin all physics simulations:
physics.start()
Gravity
By default, the physics engine will simulate gravity on Earth, with no horizontal gravity and 9.8 m/secĀ² vertical gravity, so objects will fall from the top of the scene to the bottom. You can adjust the gravity however you like by changing the x and y components of the setGravity
function:
physics.setGravity( x, y )
For instance, to make objects fall towards the lower right corner of the screen, you would set both values to a positive number:
physics.setGravity( 4, 8 )
and to pull objects toward the upper right corner, you would have a positive x value and a negative y value:
physics.setGravity( 6, -5 )
To remove all effects of gravity, set both values to 0.
Pausing and Restarting Physics
To temporarily suspend physics simulations, use the pause method:
physics.pause()
This line would typically be included in a function. To stop all physics simulations and "destroy" the physics world, use
physics.stop()
Other Properties
If your physics enabled app does not seem to behave properly, there are some other settings you may try to adjust. These are all best left at the default option, unless your app specifically requires a change.
setScale
By default, the pixel-per-meter ratio used between display objects and physics simulations is set to 30, meaning that a 90 pixel object would be considered 3 meters wide. This is the best setting for display objects less than 300 pixels wide. If you're using a lot of larger physics objects, you might notice the objects appearing too "heavy" or reacting slowly in a collision. In this case, try changing the scale value to a larger value:
physics.setScale( 60 )
Note that this is not necessary or suggested if the majority of your physics objects are less than 300 pixels wide or high.
setContinuous
By default, the physics engine will continuously check for collisions to ensure proper behavior. This helps prevent objects from "tunneling" through small static objects, but may cause unwanted effects with certain physics joints. To turn off continual collision detection, set
physics.setContinuous( false )
Note that collisions may appear sluggish and static bodies will need to be larger than usual to prevent tunneling in this case.
setPositionIterations
During animations, the physics engine will compute the position of physics objects 8 times per frame, per object. Occasionally, you might notice two physics bodies momentarily overlapping during a collision in between these calculations. To increase the number of position approximations per frame, adjust the number to a higher value
physics.setPositionIterations( 16 )
Note that this may greatly decrease performance, and is rarely necessary.
Draw Modes
The physics engine allows three draw modes that can be used to understand how your physics bodies are interacting. By default, the rendering mode is "normal", meaning that the simulator will display exactly what the device would display. For debugging purposes, it is useful to change this mode to either "hybrid" or "debug".
In hybrid mode, physics bodies will be displayed along with an overlay of collision outlines. Include the following line at the beginning of your code (but after you've included the physics library) to view hybrid mode:
physics.setDrawMode( "hybrid" )
To view only the collision outlines, set the draw mode to "debug":
physics.setDrawMode( "debug" )
The color of the outline depends on the type of physics body and its current state:
- Dynamic bodies will be outlined in orange
- Kinematic bodies will be outlined in dark blue
- Static bodies will be outlined in green
- Sleeping bodies will be outlined in grey
- Physics joints will be outlined in light blue
In addition to displaying the collision outlines and states, the hybrid and debug modes also show a small marker at the physics body's center of mass if the body is dynamic or kinematic.
The images below show a physics-enabled scene rendered with normal, hybrid, and debug modes, respectively:
Physics Bodies
Display Objects to Physics Bodies
Any display object (simple shape, imported image, or sprite) can be turned into a physics body. After enabling physics and defining your display object, use the addBody function to make your object react to gravity and collisions. A complete but simple example would be
local physics = require( "physics" )
physics.start()
local myCircle = display.newCircle( 100, 100, 50 )
physics.addBody( myCircle )
If you run the code above in the simulator, you'll see the circle immediately fall to the bottom of the screen, and continue falling until it is no longer visible. This is because the body is reacting to the default gravity setting, and there is no object at the bottom of the screen (acting as the ground) to stop its motion.
All display properties are still available to physics bodies, like fill color. Physics bodies can still be repositioned by changing their x and y properties and rotated as usual, but they will have many other properties that will control their motion and reaction to various forces in a physics simulation.
Note: While it is possible to scale a physics body to change its size on the screen, this is not recommended. For imported images, physics simulations will perform much better if the size of the object is changed PRIOR to importing it, using an external editor.
Body Types
There are three major types of physics bodies, all of which react differently to collisions and other events:
- Dynamic Bodies This is the default type for a physics body; all bodies are dynamic unless changed to another type. These bodies react to gravity and collisions with all other body types.
- Kinematic Bodies Kinematic bodies do not react to gravity. They will react to collisions with dynamic bodies, but not other kinematic bodies or static bodies.
- Static Bodies Static bodies do not react to gravity and are not moved by collisions. Examples would include the ground, a wall, a tree, or any other object that your dynamic or kinematic objects should "lose" a collision with.
The body type of an object can be specified when adding it:
local myCircle = display.newCircle( 100, 100, 50 )
physics.addBody( myCircle, "kinematic" )
or after the object is defined, by changing the bodyType property (note, this property cannot be changed during a collision):
local myCircle = display.newCircle( 100, 100, 50 )
physics.addBody( myCircle )
myCircle.bodyType = "kinematic"
Body Properties
Any physics body, regardless of type, has three properties that determine how it reacts with other objects. These are similar to properties you're used to, but note that an app is a two dimensional world. This means that "volume" is replaced by "area".
- Density This property controls the mass of an object (density x area = mass). The default density is 1.0, the density of water. Values greater than 1 will result in "heavier" objects, while lower density values will result in "lighter" objects. The overall appearance of mass will also depend on the gravity of your scene and the physics scale.
- Friction This property controls how "slippery" the objects are. The default value is 0.3. Smaller values (must be at least 0) result in an object with a lower coefficient of friction, while higher values will seem to have a rougher texture. A value of 1 would be a strong coefficient of friction, while a value of 0.1 would be appropriate for a texture such as ice.
- Bounce This value decides how much of the object's velocity is returned after a collision. A value of 0 will result in no "bounce" - the object will simply stop when it collides with a static object. A value of 1 will cause the object to return to its original position, while any value greater than 1 will cause the object to gain velocity after a collision (in the opposite direction). The default value is 0.2.
If you'd like to change any of the body properties to something other than their default value, you can do so when adding the body:
local myCircle = display.newCircle( 100, 100, 50 )
physics.addBody( myCircle, { density = 0.6, friction = 0.6, bounce = 0.3 } )
Waking and Sleeping
The start function takes an optional argument. By default, all physics bodies are set to "sleep" after a few seconds of not being involved in a collision. This is meant to improve performance, and works well for most apps. Sleeping bodies will automatically "wake" when a collision is detected. However, sleeping bodies do not wake in response to a change in gravity. For instance, if you're creating an app where the gravity changes in response to the accelerometer (if the user tilts the device), you'll need to force all bodies to stay awake. To do so, pass the argument true to the start function:
physics.start( true )
To keep only certain bodies awake, set the
isSleepingAllowed property of the body itself to false:
myBody.isSleepingAllowed = false
The read-only
isAwake property returns true if the body is awake, and false otherwise.
Additional Body Properties
There are several properties available to control how (or if) a physics body interacts with other objects:
Collision Outlines
Rectangular
By default, a physics body will be given a rectangular collision outline. This is fine for objects that actually are rectangular, and for static objects that can be closely approximated by a rectangle, like the ground or a wall. The rectangular outline will surround the entire object, and include any transparent pixels around the image if it is not actually rectangular.
The penguin and tree in the scene below both have a default rectangular outline, as shown in hybrid draw mode.
This isn't a bad approximation for the penguin, but the tree is much more problematic. Any object that comes near the tree will collide with the rectangle, which will look like the object collides with empty space near the tree, instead of the tree itself.
Circular
For objects that are closer to circular, you can apply a circular collision outline with a specified radius. This is done by providing the radius, in pixels, in the option table when defining the object:
physics.addBody( myObject, { radius = 80, bounce = 0.5 } )
The penguin and tree in the scene below both have a circular outline, as shown in hybrid draw mode. On the left, the outline around the penguin includes his wings, but will make the penguin appear to hover above the ground. On the right, the radius is set smaller so he'll sit on the ground, but his wings will not be included in a collision event. Again, there is a lot of room around the tree that we probably don't want to include in a collision.
This isn't a bad approximation for the penguin, but the tree is much more problematic. Any object that comes near the tree will collide with the rectangle, which will look like the object collides with empty space near the tree, instead of the tree itself.
Polygonal
To get a more accurate collision outline for shapes that cannot be approximated by a rectangle or a circle, you can provide a table of coordinates for a polygon that better matches the body's shape. This can be labor intensive, but the results are worth it.
The tree has now been surrounded with a polygonal outline. It's still not perfect, but most collisions with the tree will seem relatively realistic.
Creating a polygonal outline is similar to creating a polygonal display object. Just as with these objects, a polygonal outline cannot be convex. This means we can't add additional points under the tree to better fit against the trunk, or additional points to fit around the branches.
To create a polygonal outline, you'll first need to define a table of vertices of the outline itself.
local myVertices = { x_1, y_1, x_2, y_2, ... , x_n, y_n }
You might want to open your image in an image editor to help determine the best points to choose. The location of the object on screen doesn't need to be taken into account; the outline will automatically be centered over the physics body. The name of this table is then specified as the
shape
when defining the object.
physics.addBody( myObject, { shape = myVertices, bounce = 0.5 } )
The code to create the tree outline and add it to the tree object is as follows:
local tree = display.newImage( "tree.png" )
tree.x = 430
tree.y = 830
local treeShape = { -140,150,-50,200,50,200,140,150,0,-200 }
physics.addBody( tree, "kinematic", { shape = treeShape } )
Note that the vertices do not correspond to the actual location of the outline on the screen. They just specify the shape of the tree if it were centered at the origin, though even this isn't necessary - it's only important that the points are located in the correct place, relative to each other, and that they match the size of the object. The position of the tree is set by its x and y coordinates, and tree and the outline are centered at this location.
Automatic
The best solution for irregular objects is to let Corona draw the outline for you. This will result in an outline that closely surrounds the object, without having to figure out vertices for a polygonal outline, and even if the shape is convex. There is one down side - this method uses the Graphics 2.0 library, which is only available to paid subscribers (as of October 2014). However, as a starter subscriber you can still use this method, you just won't be able to build for devices without upgrading your subscription. You'll also get a message asking you to upgrade each time you open your app in the simulator.
To use the automatic outline method, use the newOutline
function of the graphics library. This function takes two arguments. One is a numeric value that essentially specifies how close of an outline you'd like to use. This value represents the coarseness of the outline, so higher values result in poorer outlines but better performance. Lower values give a better outline, but if you have a lot of objects or action in your scene, you may notice some lag. The second argument is the name of the file you'd like outlined. You'll need to name this outline so you can pass it to the physics body you'll create next:
local treeOutline = graphics.newOutline( 3, "tree.png" )
Now, create the physics body as usual, and pass the name of your outline to the
outline
option when defining it. Note we still need to import the image itself.
local treeOutline = graphics.newOutline( 3, "tree.png" )
local tree = display.newImage( "tree.png" )
physics.addBody( tree, "kinematic", { outline = treeOutline } )
The result of adding outlines to the penguin and the tree, in hybrid mode shows how closely the both shapes are represented by their collision outlines:
Complex Bodies
It is also possible to create bodies that are divided into several regions, each with different physical properties. Each of these regions is defined by a polygon table, and must be convex. The body is then created with multiple option tables in its definition.
The bicycle horn below is made from two materials - a metal piece and a rubber piece - that will have distinct physical properties.
We'll create two polygons around the distinct parts of the bicycle horn - one around the metal horn, and one around the rubber ball.
local hornTable = { -93,-40,34,-16,34,4,24,28,17,36,-45,39,-93,36,-100,-2 }
local ballTable = { 34,-16,71,-34,89,-28,99,-13,99,6,89,17,71,23,34,4 }
Next, to create the physics object, we import the image and use it to define the physics body. We'll give different physical properties to each section of the horn:
local bikehorn = display.newImage("bikehorn.png")
physics.addBody( bikehorn, "dynamic",
{ shape = ballTable, density = 0.9, friction = 2, bounce = 0.8 },
{ shape = hornTable, density = 6, friction = 0.5, bounce = 0.1 }
)
Motion and Forces
Linear Motion
Linear motion refers to an objects change in position, as opposed to angular motion, which refers to the object's spin around its center of mass. Without any additional forces on the object, linear motion would be along a straight line. However, the trajectory of an object may seem curved if it is pulled by gravity or pushed by other forces.
- Linear Damping To adjust how quickly an object appears to slow to a full stop (or return to its usual linear velocity) after a collision, adjust its
linearDamping
property:
myBody.linearDamping = 1
A higher value results is greater damping, and the default is 0.
- Linear Velocity To force an object to move in a particular direction, specify the x and y components of its linear velocity as follows:
myBody:setLinearVelocity( 1, 5 )
The line above would case the object called "myBody" to move 1 pixel per second to the right, and 5 pixels per second down, resulting in a diagonal motion towards the lower right area of the screen. Either component can be positive or negative, so objects can be made to travel in any direction.
- Constant Linear Force A consistent linear force can be applied to an object by specifying a target point (of the object) that will receive the force, and the x and y components of the force itself. Typically, the target point would be the object's center of mass. The first two arguments of the
applyForce
method are the directional components of the force (horizontal followed by vertical), and the second two are the target point.
myBody:applyForce( 60, 300, myObject.x, myObject.y )
- Brief Linear Force A momentary push can also be given, as opposed to the consistent push defined above. A single linear impulse can be applied with the
applyLinearImpulse
method, which also takes four arguments, defined in the same was as the linear force above.
myBody:applyLinearImpulse( 60, 300, myObject.x, myObject.y )
Angular Motion
Angular motion refers to how an object moves or spins around its center of mass.
- Angular Damping To adjust how quickly an object stops spinning about its center of mass after a collision, adjust the
angularDamping
property.
myBody.angularDamping = 3
A higher value results is greater damping, and the default is 0.
- Prevent Rotation To completely prevent an object from rotating during a collision or due to other forces, set the
isFixedRotation
property to true.
myBody.isFixedRotation = true
This property is also useful to prevent unwanted behavior when objects are connected by joints.
- Angular Velocity To force an object to spin about its center of mass, even in the absence of a collision, change the
angularVelocity
property to any nonzero value.
myBody.angularVelocity = 1
Note that the object's spin will still react to collisions, but after recovering from a collision it will return to the set velocity.
- Constant Rotational Force To apply a consistent rotational force to an object, supply a nonzero value to the
applyTorque
method. Positive values will cause the object to spin in a clockwise direction, and negative will result in a counterclockwise rotation.
myBody:applyTorque( 2 )
The resulting rotation will be about the object's center of mass.
- Brief Rotational Force To apply a brief rotational force to an object, supply a nonzero value to the
applyAngularImpulse
method. Positive values will cause the object to spin in a clockwise direction, and negative will result in a counterclockwise rotation.
myBody:applyAngularImpulse( 10 )
The resulting rotation will be about the object's center of mass.
Joints
Joint Troubleshooting
Collisions
One of the most useful features of the physics engine is its ability to detect collisions. Many of these are handled without any effort on your part: if a dynamic ball hits a static wall, it will bounce off by itself. If you want some other events to occur when two physics bodies come into contact with each other, you can listen for a collision event and have a specific function run when one is detected.
To make it easier to see the difference between these functions, we'll use the following setup throughout all examples. The code below will create a scene with four static "walls" surrounding the display area, and four "balls" that will move randomly around the screen. We'll use a runtime
enterFrame
function to make sure the balls keep floating. The remaining examples will all use modifications of this code.
local physics = require( "physics" )
physics.start()
physics:setGravity(0,0)
local w = display.contentWidth
local h = display.contentHeight
local leftWall = display.newRect( 0, h/2, 2, h )
physics.addBody( leftWall, "static" )
leftWall.name = "wall"
leftWall.id = "left wall"
local rightWall = display.newRect( w, h/2, 2, h )
physics.addBody( rightWall, "static" )
rightWall.name = "wall"
rightWall.id = "right wall"
local ceiling = display.newRect( w/2, 0, w, 2 )
physics.addBody( ceiling, "static" )
ceiling.name = "wall"
ceiling.id = "ceiling"
local floor = display.newRect( w/2, h, w, 2 )
physics.addBody( floor, "static" )
floor.name = "wall"
floor.id = "floor"
local ball1 = display.newCircle( 50, 50, 60 )
ball1:setFillColor( 1, 0, 0 )
ball1.name = "ball"
ball1.id = "red ball"
ball1.alpha = 0.2
physics.addBody( ball1 )
local ball2 = display.newCircle( 50, 50, 60 )
ball2:setFillColor( 0, 1, 0 )
ball2.name = "ball"
ball2.id = "green ball"
ball2.alpha = 0.2
physics.addBody( ball2 )
local ball3 = display.newCircle( 50, 50, 60 )
ball3:setFillColor( 0, 0, 1 )
ball3.name = "ball"
ball3.id = "blue ball"
ball3.alpha = 0.2
physics.addBody( ball3 )
local function moveBalls( event )
ball1:applyForce( math.random(-20,20), math.random(-20,20), ball1.x, ball1.y )
ball2:applyForce( math.random(-20,20), math.random(-20,20), ball2.x, ball2.y )
ball3:applyForce( math.random(-20,20), math.random(-20,20), ball3.x, ball3.y )
end
Runtime:addEventListener( "enterFrame", moveBalls )
Notice that we've given a "name" property to all physics bodies - we'll use these in our collision handlers. The balls will be difficult to see at first due to the low alpha values.
Global Collisions
If you'd like an event handler to fire whenever ANY two objects on screen collide, you can use a global collision listener. Assuming you want a function called "onCollision" to run whenever two physics bodies collide, your code will include
local function onCollision( event )
if (event.phase == "began") then
-- code to run when objects first make contact
elseif (event.phase == "ended") then
-- code to run when objects break contact
end
end
Runtime:addEventListener( "collision", onCollision )
The handler can have two phases: "began" and "ended". You are not required to include actions for both phases, but you should specify a phase for anything you would like to happen. Not specifying a phase can result in actions firing twice: once when objects first touch, and again when they break contact.
Unlike the touch event handlers we saw earlier, there is no "event.target" with global collision listeners. Instead, the two objects that are involved in the collision are calle
event.object1
and
event.object2
. Assigning id's to your objects is recommended, because they allow you to identify which objects are actually touching.
To continue the floating ball example from earlier, we'll create a listener that detects ALL collisions on screen; that is, the handler will be fired anytime two balls collide, or if a ball hits a wall, or even if two walls are colliding with each other (even though they're static, if they're overlapping, they're colliding). So in the handler, we'll use if-then statements to check which objects are actually colliding. In the example here, we'll make the balls temporarily 'glow' when they come into contact with another ball. We'll also print a message to the terminal with the id of the objects that were involved in the collision.
local function onCollision( event )
if (event.phase == "began") then
if event.object1.name == "ball" and event.object2.name == "ball" then
transition.to( event.object1, {alpha = 1, time = 300})
transition.to( event.object1, {alpha = 0.2, time = 300, delay = 300})
transition.to( event.object2, {alpha = 1, time = 300})
transition.to( event.object2, {alpha = 0.2, time = 300, delay = 300})
end
elseif (event.phase == "ended") then
print( event.object1.id.." and "..event.object2.id.." are done colliding!" )
end
end
Runtime:addEventListener( "collision", onCollision )
Paste the code above at the end of the code at the top of this section to see the result.
Local Collisions
Rather than listen for every collision happening within a scene, you can use a local collision listener to check if a particular object has come into contact with another physics body. Assuming you want a function called "onCollision" to run whenever an object called "myObject" collides with another object, your code will include
local function onCollision( self, event )
if (event.phase == "began") then
-- code to run when objects first make contact
elseif (event.phase == "ended") then
-- code to run when objects break contact
end
end
myObject.collision = onCollision
myObject:addEventListener( "collision", myObject )
The handler still has two phases, "began" and "ended", just as for a global collision. The object with the listener attached to it can be referred to as
self
within the handler. The other object is referred to as
event.other
. Also note the pair of arguments taken by the handler.
To continue the floating ball example from earlier, we'll create a listener on the red ball. If it collides with a wall, it will temporarily glow. If it collides with another ball, there will be a message printed to the terminal. All of these will occur as soon as the ball makes contact with another object; the event "ended" phase is left empty here.
local function onCollision( self, event )
if (event.phase == "began") then
if event.other.name == "wall" then
transition.to( self, {alpha = 1, time = 300})
transition.to( self, {alpha = 0.2, time = 300, delay = 300})
elseif event.other.name == "ball" then
print( "The red ball just hit the "..event.other.id.."!" )
end
elseif (event.phase == "ended") then
end
end
ball1.collision = onCollision
ball1:addEventListener( "collision", ball1 )
Paste the code above at the end of the code at the top of this section to see the result (remove the previous global collision listener and handler before doing so!).
Sensor Collisions
As we've seen, we can create physics bodies that are defined to be sensors. These objects can listen for a collision with other objects, but there is no visual effect of the collision. That is, a dynamic ball colliding with a sensor rectangle will simply pass over the rectangle, instead of bouncing off.
To demonstrate, we'll create a rectangle to add to our floating ball code, which we'll define to be a sensor object. If one of the floating balls enters the sensor area, it will glow, and fade again when the ball leaves the sensor. Add the code below to the floating ball example to see the effect.
local sensorArea = display.newRect( w/2, h/2, w/2, h/2 )
sensorArea.isVisible = false
physics.addBody( sensorArea )
sensorArea.isSensor = true
local function onCollision( self, event )
if (event.phase == "began") then
transition.to( event.other, {alpha = 1, time = 300})
elseif (event.phase == "ended") then
transition.to( event.other, {alpha = 0.2, time = 300})
end
end
sensorArea.collision = onCollision
sensorArea:addEventListener( "collision", sensorArea )
Example: Flick Hockey
The code below creates a hockey game. Tap the "ice" to create a puck. Then, grab the puck and flick it toward the goal. If the puck moves into the goal sensor, you'll score a point. Otherwise, the score will not change.
local physics = require("physics")
physics.start()
physics.setGravity( 0, 0 )
local bg, goal, goalLine, goalText, score, post1, post2
local w, h = display.contentWidth, display.contentHeight
bg = display.newRect( w/2, h/2, w, h )
goal = display.newRect( w/2, 100, 400, 200 )
goal:setFillColor( 0.9, 0.9, 0.9 )
physics.addBody( goal, { isSensor = true } )
goalLine = display.newLine( 0, 200, w, 200 )
goalLine.width = 6
goalLine:setStrokeColor( 1, 0, 0 )
post1 = display.newRoundedRect( w/2 - 206, 100, 10, 230, 5)
post1:setFillColor( 0, 0, 0 )
physics.addBody( post1, "static" )
post2 = display.newRoundedRect( w/2 + 206, 100, 10, 230, 5)
post2:setFillColor( 0, 0, 0 )
physics.addBody( post2, "static" )
score = 0
goalText = display.newText( "Goals: 0", w/2, h - 50, Arial, 40 )
goalText:setFillColor( 0, 0, 0 )
local function dragBody( event )
local body = event.target
local phase = event.phase
local stage = display.getCurrentStage()
if "began" == phase then
stage:setFocus( body, event.id )
body.isFocus = true
body.tempJoint = physics.newJoint( "touch", body, event.x, event.y )
elseif body.isFocus then
if "moved" == phase then
body.tempJoint:setTarget( event.x, event.y )
elseif "ended" == phase or "cancelled" == phase then
stage:setFocus( body, nil )
body.isFocus = false
body.tempJoint:removeSelf()
end
end
return true
end
function makePuck( event )
newPuck = display.newCircle( event.x, event.y, 35 )
newPuck:setFillColor( 0, 0, 0 )
newPuck.strokeWidth = 6
newPuck:setStrokeColor( 0.2, 0.2, 0.2 )
physics.addBody( newPuck, { density = 0.5, friction = 0.1, bounce = 0.2, radius = 35 })
newPuck:addEventListener( "touch", dragBody )
return true
end
bg:addEventListener( "tap", makePuck )
local function onCollision( event )
if event.phase == "began" then
if event.object1 == goal or event.object2 == goal then score = score + 1 end
goalText.text = "Goals: "..score
end
return true
end
Runtime:addEventListener( "collision", onCollision )