Physics Mass, Friction and More In 2D
Understanding mass, friction, and more in a 2D physics enabled Quorum gameOverview
In this tutorial, we will create a game where a Quorum bunny crashes into a tower of boxes with its deadly hops. Along the way, we will learn about new physics properties and concepts. The first concepts necessary for the bunny to knock over the boxes are mass and density.
Mass and Density
Up until now, all physics objects’ masses have been determined using a default mass of 1 kilogram. We can change this by setting either mass or density. Mass is how much stuff something is made of. It is related to weight. As mass increases, so does weight. Weight also relies on gravity (think of astronauts on the moon and how they weigh less there even though they have not changed themselves.) We can think of mass as how much our objects weigh as long as we remember that changing gravity will also affect their weight. Mass is set per item using this action:
number mass = 5
itemName:SetMass(mass)
Here mass is a number representing how many kilograms an item is. To help us understand the units of mass better, consider a few examples. The mass of a typical person is about 70 kg. The mass of a car might be between 1000 to 2000 kg, and the mass of a commercial airliner might be between about 40,000 kg and 75,000 kg. When mass is set this way, density is automatically calculated. Density is another way to think about mass. It is how much mass an item has per units squared, where a unit is determined by the camera. Normally a unit would be a pixel, but this might be different if we use the orthographic camera, for example. In that case, a unit is one, whatever that means to the camera. By units squared, we mean a one unit by one unit square. We can set density for an item using:
number density = 5
itemName:SetDensity(density)
density is a number representing the number of kilograms per units squared. If density is set like this, mass will be automatically calculated for the item.
Friction
In the previous tutorial on force and velocity, the character was able to move across the platforms without slowing down. The physics property that provides resistance to movement across something is called friction. Sliding on ice or a puck moving in air hockey are examples of movement with very little friction. Pushing furniture on heavy carpet is an example of movement with high friction. To set an item’s friction we use:
number friction = 0.5
itemName:SetFriction(friction)
Here friction is a number between zero and one. Zero means no friction and one is the highest amount of friction. If we set friction to be a number higher than one, the system will treat it the same as if we set it to one.
Restitution
We want our bunny to be relatively bouncy. To achieve this, we need a new physics property called restitution. Restitution can be thought of as the bounciness of an item. Like friction, restitution is set per item using:
number restitution = 0.4
itemName:SetRestitution(restitution)
Here restitution is a number representing how much bounce is preserved on impact. To understand this fully, we need a new concept: energy. When an item falls, the movement is a result of gaining kinetic energy. Kinetic means movement, basically. When the item is at the height of its fall before starting to move downward, it has no kinetic energy (it is not moving). Instead it has potential energy. During the fall, the item trades its potential energy for kinetic energy. When the item starts to bounce back up, it is trading its kinetic energy for potential energy. The restitution value, then, is multiplied by the energy at impact to affect how much kinetic energy the item has when it starts to bounce up. For example, a restitution of one means that the item retains all of its kinetic energy when it bounces. A restitution of zero means that the item loses all its kinetic energy, and therefore does not bounce. Realistic restitution, then, would be somewhere between zero and one, but it is possible to have a restitution greater than one.
Code
Read the following code, then use it below to discover mass, friction, and restitution.
use Libraries.Game.Game
use Libraries.Game.Graphics.Drawable
use Libraries.Compute.Vector2
use Libraries.Game.Graphics.Color
use Libraries.Interface.Events.KeyboardListener
use Libraries.Interface.Events.KeyboardEvent
use Libraries.Game.Graphics.OrthographicCamera
use Libraries.Interface.Events.CollisionListener2D
use Libraries.Interface.Events.CollisionEvent2D
use Libraries.Sound.Audio
use Libraries.Compute.Math
class Main is Game, KeyboardListener, CollisionListener2D
Math math
Drawable ground
Drawable bunny
Vector2 gravity
Vector2 character
Color color
Audio jumpSound
Audio impactSound
Audio movingSound
action Main
StartGame()
end
action CreateGame
AddCollisionListener(me)
AddKeyboardListener(me)
EnablePhysics2D(true)
OrthographicCamera camera
camera:SetToOrthographic(20, 20)
SetCamera2D(camera)
ground:SetName("ground")
ground:LoadFilledRectangle(20, 1)
ground:SetPosition(0, 0)
ground:SetColor(color:Green())
Add(ground)
ground:EnablePhysics(true)
ground:SetUnmovable()
ground:SetFriction(1)
bunny:SetName("bunny")
bunny:Load("media/HourOfCode.png")
bunny:SetPosition(1, 1)
bunny:Scale(1.0/80)
Add(bunny)
bunny:EnablePhysics(true)
bunny:SetResponsive()
bunny:SetFriction(0.2)
bunny:SetRestitution(0.75)
bunny:SetMass(10)
gravity:Set(0, -9.8)
SetGravity2D(gravity)
// We make invisible walls so that everything stays on screen.
CreateInvisibleWall(-2, 0)
CreateInvisibleWall(21, 0)
CreateInvisibleWall(0, 21)
// We create a tower of boxes.
GenerateBox(0.6,1,color:Red())
GenerateBox(-0.6,1,color:Red())
GenerateBox(0,2, color:Orange())
GenerateBox(0.6,3,color:Yellow())
GenerateBox(-0.6,3,color:Yellow())
GenerateBox(0,4, color:Green())
GenerateBox(0.6,5,color:Blue())
GenerateBox(-0.6,5,color:Blue())
GenerateBox(0,6, color:Purple())
jumpSound:Load("media/Fwip.ogg")
impactSound:Load("media/Boing.ogg")
movingSound:Load("media/Bing.ogg")
jumpSound:SetVolume(0.5)
impactSound:SetVolume(0.3)
end
action Update(number seconds)
// If the bunny is noticably moving, we play a sound to represent the bunny's position.
if math:AbsoluteValue(bunny:GetLinearVelocity():GetX()) > 1 or math:AbsoluteValue(bunny:GetLinearVelocity():GetY()) > 1
movingSound:SetBalance(bunny:GetX())
movingSound:SetPitch(bunny:GetY())
movingSound:SetVolume(0.5)
movingSound:Play()
end
end
action PressedKey(KeyboardEvent event)
if event:keyCode = event:SPACE
Vector2 force
force:Set(12800, 10000)
bunny:ApplyForceToCenter(force)
jumpSound:Play()
end
end
action BeginCollision(CollisionEvent2D event)
impactSound:Play()
end
/*
To balance the tower of boxes, we make every other level have longer than tall boxes.
The displaceFromGround parameter tells us how many levels of boxes up to place a box.
For example, if it equals 2, then the box is placed two box heights up from the bottom of the screen.
displaceFromCenter tells us how many box lengths from x = 11.5 we want the box to be placed horizontally.
For example, if we want two boxes centered ontop of a box at center (x = 11.5) then we can use
displaceFromCenter = 0.6 for one and -0.6 for the other. This will place them so that the gap between them
is 0.8 box lengths centered at 11.5.
*/
action GenerateBox(number displaceFromCenter, integer displaceFromGround, Color color)
Drawable box
integer boxSize = 2
if displaceFromGround mod 2 = 0
box:LoadFilledRectangle(boxSize + 1, boxSize)
box:SetPosition(11.5 + displaceFromCenter*(boxSize), (boxSize)*displaceFromGround)
else
box:LoadFilledRectangle(boxSize, boxSize)
box:SetPosition(12 + displaceFromCenter*(boxSize), (boxSize)*displaceFromGround)
end
box:SetColor(color)
Add(box)
box:EnablePhysics(true)
box:SetFriction(1)
box:SetResponsive()
box:SetMass(5)
end
action CreateInvisibleWall(integer x, integer y)
Drawable invisibleWall
/*
We need two invisible walls for the sides that are taller than long, and one for the top that is longer than tall.
The side walls' positions both have y = 0, so that is how we can tell which type is necessary.
*/
if y <= 0
invisibleWall:LoadRectangle(1, 22)
else
invisibleWall:LoadRectangle(21, 1)
end
invisibleWall:SetPosition(x , y)
Add(invisibleWall)
invisibleWall:EnablePhysics(true)
invisibleWall:SetUnmovable()
end
end
Try it Yourself!
Press the blue run button to execute the code in the code editor. Press the red stop button to end the program. Your program will work when the console outputs "Build Successful!"
If we run this game, we should have a Quorum bunny sitting on the left side of the screen on the ground and a stack of boxes on the right side of the screen. Pressing the spacebar will cause the bunny to jump across the screen, hitting the stack of boxes. The boxes should be knocked off the stack in response to the collision.
Next Tutorial
In the next tutorial, we will discuss Joints In 2D, which describes how to use joints in a 2D game.