Overview

In this tutorial, we will discover new physics concepts by making a platformer game. Our goal is to propel a character represented by a ball forward through a series of platforms. We will do this using the concept of force which we can control in various ways. We will also need linear velocity. Linear velocity propels our character forward and forces can be used for jumping. To make platforms, we will use a concept that we call NonResponsiveness, which was mentioned in the gravity tutorial. To use these ideas, we need a special concept from math: vectors.

Vectors

In math, a vector is like a line starting at a point and continuing out in one direction. It has a magnitude (length) and a direction. When we are using these for forces, we can look at the direction as being the direction we want the force to push towards and the magnitude as how strong the force is. When we are using these for linear velocity, we can look at the direction as being the direction the object will move in and the magnitude as how fast it will move. In Quorum for 2-dimensional games, we handle all of the details of vectors for you using a special class called Vector2. First we need to add a use statement to the top of the program:

use Libraries.Compute.Vector2

We can then set a Vector2 object like this:

number X = 0
number Y = -10
myVector:Set(X , Y)

This might look familiar to how we set gravity. In fact, gravity is represented in the system using a Vector2. The X value represents the direction and amount of force applied horizontally per second. The Y is the same for vertical force. An example of how to think about this is gravity. Gravity pushes objects down, so we set the Y value to be a negative number. It does not push objects horizontally, so the X was set to 0. Gravity can be set using a Vector2 object as well:

Vector2 gravity
gravity:Set(0 , -10)
SetGravity2D(gravity)

Linear Velocity

To give the character a continuous push forward, we need to set their linear velocity, and to do this, we need to create and set a Vector2 object. In the example code here, we want the character to move horizontally across the screen from left to right, so the X is set to a positive number. We don’t want them to have vertical movement unless it’s from the force of a jump or gravity. The Y is set to 0. Once we have the Vector2 setup, we can set the linear velocity of the character:

Vector2 linearVelocity
number X = 5
number Y = 0
linearVelocity:Set(X, Y)
character:SetLinearVelocity(linearVelocity)

Now the linear velocity will be applied to the character. We can think of this as how much the character will move each second. Note that the linear velocity is affected by forces, friction, gravity, and other physics properties.

Angular Velocity

If we want an item to rotate, we can do this with angular velocity. We can set it using:

number angularVelocity = 20
character:SetAngularVelocity(angularVelocity)

angularVelocity is a number representing the amount of rotation per second. Like linear velocity, it is applied to the character continuously, but can be affected by torque, friction, and other physics properties. If angularVelocity is a positive number, the spin will be counterclockwise, and if it is a negative number, the spin will be clockwise. Note that the rotations are about the item’s center.

Force

We used force previously when we added gravity, but forces can be used for much more than gravity. Forces can be made to push responsive objects in any direction with varying degrees of strength by setting the Vector2 appropriately. After we have a Vector2 object created and set, we can apply force in two ways. The first way to apply a force to an object is using the ApplyForceToCenter action. This action applies all of the force to the center of the object.

Vector2 force
force:Set(200, 5100)
itemName:ApplyForceToCenter(force)

The second way is to use the ApplyForce action which takes two Vector2 objects instead of one. The first is the force vector, and the second is the point the force will be applied to (represented by a Vector2 object.) The point should be a position on the screen that we want to apply the force from. For example, if we want to use this action to apply force to the center of the object we need to set the x and y variables of the point to be the same as the position of the center of the object. Note that this is different in 3D physics where the point represents displacement from the center of the object.

Vector2 point
point:Set(0, 0)
itemName:ApplyForce(force, point)

This action is useful when the force needs to hit the object at a particular spot. Additionally, if a force is not applied to the center of the object, a torque will be introduced, causing the object to rotate.

Torque

Torque is the angular component of a force, which means applying it will make the item spin. We can apply torque manually using:

number torque = 2000
itemName:ApplyTorque(torque)

torque is a number representing how much spin we want to apply to an item. The difference between torque and angular velocity is the same as the difference between force and linear velocity. Force is a one time push, while linear velocity is the current speed. Applying torque is like giving the item a one time push to start it spinning, while angular velocity is the current amount of rotations per second applied continuously. If torque is a positive number, the item will spin counterclockwise, and if torque is a negative number, the item will spin clockwise.

Movement Outside of Physics

If we want to move an item without using physics, and without affecting the physics system, we can use Move(), SetRotation(), or SetPosition(). These actions are described in more detail in the Animation in 2D tutorial. Using these will leave velocities and forces intact upon arrival at the designated spot, but will also bypass the collision detection system and forces like gravity on the way.

Orthographic Camera

The orthographic camera is a 2D camera that may be helpful with physics. In 3D, this camera would take a 3D space and convert it to 2D, but it is not necessary to worry about this aspect in 2D. So far we have done everything using pixels to determine distance. With the orthographic camera, we can zoom the screen into however many pixels we want, with these pixels spread over the size of the game window. It might be helpful to go over the Camera Tutorial before trying this. First, we need to add a use statement for the proper library.

use Libraries.Game.Graphics.OrthographicCamera

Now in the CreateGame() action, we need to: create an OrthographicCamera object, set the camera to put the screen in X and Y number of pixels, then use SetCamera2D to set the camera to the one we made.

OrthographicCamera camera
number X = 30
number Y = 30
camera:SetToOrthographic(X , Y)
SetCamera2D(camera)

Since the orthographic camera works by zooming in the screen to the number of pixels (X and Y) that we set, this will affect our vectors and shapes, because they will be zoomed in. Notably, circles will need to be scaled down from a larger size to a size that makes sense on the screen to maintain the same resolution or picture quality.

character:LoadFilledCircle(30, color:Blue())
character:Scale(1.5/30)

Using the orthographic camera can help make the numbers a little more intuitive. For example with a 30 by 30 pixel orthographic camera, we can think of these 30 pixels (expanded to fit the typical game screen) as each representing one meter. The point of this is to make the numbers we set for things like shapes and force a little easier to think about.

Code

Here is a zip file with a Quorum project using this code. Read through, then use the following code to discover forces and linear velocity.

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

class Main is Game, KeyboardListener, CollisionListener2D

   boolean platformMovesUp = false
   number totalSeconds = 0.0

   Drawable platformRight
   Drawable platformLeft
   Drawable character
   Vector2 gravity
   Vector2 linearVelocity
   Vector2 jump
   Color color

   Audio jumpSound
   Audio impactSound
   Audio platformSound

   action Main
       StartGame()
   end

   action CreateGame
    // We want to think of the screen as a 30 by 30 meter area.
       OrthographicCamera camera
       camera:SetToOrthographic(30, 30)
       SetCamera2D(camera)

       AddCollisionListener(me)
       AddKeyboardListener(me)
       EnablePhysics2D(true)

       platformLeft:SetName("platformLeft")
       platformLeft:LoadFilledRectangle(13, 2, color:Green())
       platformLeft:SetPosition(0, 9)
       Add(platformLeft)
       platformLeft:EnablePhysics(true)
       platformLeft:SetNonResponsive()

       platformRight:SetName("platformRight")
       platformRight:LoadFilledRectangle(13, 2, color:Green())
       platformRight:SetPosition(17, 10)
       Add(platformRight)
       platformRight:EnablePhysics(true)
       platformRight:SetNonResponsive()

       character:SetName("character")
       character:LoadFilledCircle(30, color:Blue())
       character:Scale(1.5/30)
       character:SetPosition(0, 11)
       Add(character)
       character:EnablePhysics(true)
       character:SetResponsive()

       linearVelocity:Set(5, 0)
       character:SetLinearVelocity(linearVelocity)

       jump:Set(50, 800)
       gravity:Set(0, -9)
       SetGravity2D(gravity)

       jumpSound:Load("media/Fwip.ogg")
       impactSound:Load("media/Boing.ogg")
       platformSound:Load("media/Bing.ogg")

       jumpSound:SetVolume(0.5)
       impactSound:SetVolume(0.4)
   end

   action Update(number seconds)
       totalSeconds = totalSeconds + seconds
   // These if statements make a threshold of how far up and down the platform can travel
       if platformRight:GetY() > 24
           platformMovesUp = false
       end
       if platformRight:GetY() <= 3
           platformMovesUp = true
       end
   // The platform moves either up or down
       if totalSeconds  >= 0.01
           if platformMovesUp
               platformRight:MoveY(0.08)
           else
               platformRight:MoveY(-0.08)
           end
           totalSeconds = 0
       end
   /*
       To make the game endless, we move our character back to the left side 
       of the screen after he goes off the right side. 
   */
       if (character:GetX() >= 30)
           character:SetPosition(-1, character:GetY())
   /*
       We set the height of the left platform to be the right platform's height so that it 
       seems like our character is continuing on the same platform as the one they were on
       when they went off the right side. 
   */
           platformLeft:SetY(platformRight:GetY())
    //  This math causes the height of the right platform to cycle through some different options.
           platformRight:SetY((platformRight:GetY()mod 19) + 11)
           totalSeconds = 0
    // The platform switches directions when we go to a new screen. 
           platformMovesUp = true not= platformMovesUp
       end
    // The platform sound changes depending on how close the two platforms are to each other. 
       platformSound:SetY(platformRight:GetY() - platformLeft:GetY())
       platformSound:Play()
   end

   action PressedKey(KeyboardEvent event)
        // When space is pressed, the circle jumps!
       if event:keyCode = event:SPACE
           character:ApplyForceToCenter(jump)
           jumpSound:Play()
       end
       // The ball will spin counterclockwise when the left arrow key is pressed.
       if event:keyCode = event:LEFT
           character:ApplyTorque(2000)
       end
   /*
       The ball will spin clockwise when the right arrow key is pressed. If the ball was already 
       spinning counterclockwise, then pressing the right arrow key will cause it to slow its spinning.
   */
       if event:keyCode = event:RIGHT
           character:ApplyTorque(-2000)
       end
   end

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

Next Tutorial

In the next tutorial, we will discuss Mass, Friction, and More In 2D, which describes how to use mass, friction and more in a 2D game.