Overview

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

Here is a zip file containing a Quorum project using the code below. Read through, then use the following code 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

Next Tutorial

In the next tutorial, we will discuss Joints In 2D, which describes how to use joints in a 2D game.