OverviewIn 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.
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:
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)
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 VelocityIf 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.
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.
TorqueTorque 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 PhysicsIf 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 CameraThe 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.
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.
CodeHere 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
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.