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 cubed, where a unit is a length of one. By units cubed, we mean a one unit by one unit by one unit cube. 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 cubed. 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 that uses the code below. Read through, then use the following code to discover mass, friction, and restitution.

use Libraries.Game.Game
use Libraries.Game.Graphics.Model
use Libraries.Compute.Vector3
use Libraries.Game.Graphics.Color
use Libraries.Interface.Events.KeyboardListener
use Libraries.Interface.Events.KeyboardEvent
use Libraries.Interface.Events.CollisionListener3D
use Libraries.Interface.Events.CollisionEvent3D
use Libraries.Sound.Audio
use Libraries.Compute.Math

class Main is Game, KeyboardListener, CollisionListener3D

   Math math
   Model ground
   Model bunny

   number depth = 8
   number length = 20
   number height = -3

   Vector3 gravity
   Vector3 character
   Color color

   Audio jumpSound
   Audio impactSound
   Audio movingSound

   action Main
       StartGame()
   end

   action CreateGame
       AddCollisionListener(me)
       AddKeyboardListener(me)
       EnablePhysics3D(true)

       ground:SetName("ground")
       ground:LoadBox(length, 1, length, color:Green())
       ground:SetPosition(0, height, depth)
       Add(ground)
       ground:EnablePhysics(true)
       ground:SetUnmovable()
       ground:SetFriction(1)

       bunny:SetName("bunny")
       bunny:LoadSphere(1, 1, 1, "media/HourOfCodeDark.jpg")
       bunny:SetPosition(-5, height + 0.5, 0)
       Add(bunny)
       bunny:EnablePhysics(true)
       bunny:SetResponsive()

       bunny:SetFriction(0.2)
       bunny:SetRestitution(0.75)
       bunny:SetMass(2)

       gravity:Set(0, -10, 0)
       SetGravity3D(gravity)

    // We make boundary walls so that everything stays on screen. 
       CreateBoundaryWall(-length/2, height, depth)
       CreateBoundaryWall(length/2, height, depth)
       CreateBoundaryWall(0, height, length/2  + depth/2)
       CreateBoundaryWall(0, length + 1, depth)

    // We create a tower of boxes. 
       GenerateBox(0.7,1,color:Red())
       GenerateBox(-0.7,1,color:Red())
       GenerateBox(0,2, color:Yellow())
       GenerateBox(0.7,3,color:Blue())
       GenerateBox(-0.7,3,color:Blue())
       GenerateBox(0,4, 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()) > 0.5 or math:AbsoluteValue(bunny:GetLinearVelocity():GetY()) > 0.5
           movingSound:SetBalance(bunny:GetX())
           movingSound:SetPitch(bunny:GetY())
           movingSound:Play()
       end
   end

   action PressedKey(KeyboardEvent event)
       if event:keyCode = event:SPACE
           Vector3 force
           force:Set(450, 860, 450)
           bunny:ApplyForceToCenter(force)
           jumpSound:Play()
       end
   end

   action BeginCollision(CollisionEvent3D event)
       impactSound:Play()
   end

   /*
   To balance the tower of boxes, we make every other level have wider 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 the center we want 
   the box to be placed horizontally. For example, if we want two boxes centered ontop of 
   a box then we can use displaceFromCenter = 0.7 for one and -0.7 for the other. 
   This will place them so that the gap between them is 0.7 box lengths from the center. 
   */
   action GenerateBox(number displaceFromCenter, integer displaceFromGround, Color color)
       Model box
       number boxSize = 1.5
       number center = 3

       if displaceFromGround mod 2 = 0
           box:LoadBox(boxSize * 3.5, boxSize, boxSize * 1.5, color)
           box:SetPosition(center + displaceFromCenter*(boxSize * 1.5), (boxSize )*displaceFromGround + height + boxSize/2, 7)
       else
           box:LoadBox(boxSize * 1.5, boxSize, boxSize * 1.5, color)
           box:SetPosition(center + displaceFromCenter*(boxSize * 1.5), (boxSize)*displaceFromGround + height + boxSize/2, 7)
       end
       Add(box)
       box:EnablePhysics(true)
       box:SetFriction(1)
       box:SetResponsive()
       box:SetMass(0.3)
   end

   action CreateBoundaryWall(number x, number y, number z)
       Model boundaryWall
   /*
   We need two invisible walls for the sides that are taller than long, one for the top that is longer than tall,
   and one in the back that is about equally long and tall. The side walls' positions both have y = 0, and the back 
   wall is the only one with z > depth. That is how we can tell which type is called for. 
   */
       if z > depth
           boundaryWall:LoadBox(length + 2, length, 1, color:White())
       elseif y <= 0
           boundaryWall:LoadBox(1, length, length + 1, color:White())
       else
           boundaryWall:LoadBox(length, 1, length + 1, color:White())
       end
       boundaryWall:SetPosition(x , y, z)
       Add(boundaryWall)
       boundaryWall:EnablePhysics(true)
       boundaryWall:SetUnmovable()
   end
end

Next Tutorial

In the next tutorial, we will discuss Getting Started, which describes how to works with lego robots.