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 in 3D 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 3-dimensional games, we handle all of the details of vectors for you using a special class called Vector3. First we need to add a use statement to the top of the program:
We can then set a Vector3 object like this:
The X value represents the direction and amount of force applied horizontally per second. The Y is the same for vertical force and Z is the same for force applied towards or away from the screen. It might be helpful for us to review the Drawing In 3D tutorial to remember how the three axes translate to the screen. This might look familiar to how we set gravity. In fact, gravity is represented in the system using a Vector3. Gravity pushes objects down, so we set the Y value to be a negative number. It does not push objects along the x or z axes, so the X and Z were set to 0. Gravity can be set using a Vector3 object as well:
number X = 0 number Y = -10 number Z = 0 myVector:Set(X , Y, Z)
Vector3 gravity gravity:Set(0 , -10, 0) SetGravity3D(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 Vector3 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, and we do not want movement towards or away from the screen at all. The Y and Z are set to 0. Once we have the Vector3 setup, we can set the linear velocity of the character:
linearVelocity:Set(5, 0, 0) 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:
Vector3 angularVelocity angularVelocity:Set(0, 0, 100) character:SetAngularVelocity(angularVelocity)
Note that angular velocity is set differently in 3D than in 2D. In 2D angular velocity was simply a number, and rotations could be clockwise or counterclockwise depending on the sign of the number. In 3D we can choose which axis we want an item to rotate about as well. Angular velocity is represented using a Vector3, where the x value represents the amount of rotation per second about the x axis. Likewise the y and z values of the Vector3 correspond to rotation about the y and z axes, respectively. Like linear velocity, it is applied to the character continuously, but can be affected by torque, friction, and other physics properties. A negative number for any of the three numbers in the Vector3 means clockwise rotation about that axis while a positive number means counterclockwise rotation.
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 Vector3 appropriately. After we have a Vector3 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.
Vector3 force force:Set(50, 300, 0) itemName:ApplyForceToCenter(force)
The second way is to use the ApplyForce action which takes two Vector3 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 Vector3 object.) The point should be in the form of a displacement from the center of the object the force is being applied to. For example, if we want the force be applied at a spot one to the right and one up from the center of the object, the point’s x and y variables would both be set to one and the point’s z variable would be zero. If we wanted to apply force to the center with this action, we would set the point’s x, y, and z variables to zero. Note that this is different in 2D where the point does not represent how far from the center, but where on the screen the force should be applied from.
Vector3 point point:Set(0, 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:
Vector3 torque torque:Set(0, 0, 100) itemName:ApplyTorque(torque)
Torque is set in a similar way to angular velocity. The x, y, and z components of the Vector3 correspond to rotation about the x, y, and z axes. 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. A negative number for any of the three numbers in the Vector3 means clockwise rotation about that axis while a positive number means counterclockwise rotation.
Movement Outside of PhysicsIf we want to move an item without using physics, and without affecting the physics system, we can use SetPosition(), SetX(), SetY(), SetZ() or Rotate(). These actions are described in more detail in the Animation in 3D tutorial 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.
CodeHere is a zip file containing a Quorum project that uses the code below. Read through, then use the following code to discover forces and linear velocity.
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.Game.Graphics.DirectionalLight use Libraries.Game.Graphics.AmbientLight class Main is Game, KeyboardListener, CollisionListener3D boolean platformMovesUp = false number totalSeconds = 0.0 Model platformRight Model platformLeft Model character Vector3 gravity Vector3 linearVelocity Vector3 jump Color color Audio jumpSound Audio impactSound Audio platformSound action Main StartGame() end action CreateGame DirectionalLight light light:SetColor(0.8, 0.8, 0.8, 1) light:SetDirection(GetCamera3D():GetDirection()) Add(light) AmbientLight ambient ambient:SetColor(0.4, 0.4, 0.4, 1) SetAmbientLight(ambient) AddCollisionListener(me) AddKeyboardListener(me) EnablePhysics3D(true) platformLeft:SetName("platformLeft") platformLeft:LoadBox(7, 1, 3, color:Green()) platformLeft:SetPosition(-5, -2, 0) Add(platformLeft) platformLeft:EnablePhysics(true) platformLeft:SetNonResponsive() platformRight:SetName("platformRight") platformRight:LoadBox(7, 1, 3, color:Green()) platformRight:SetPosition(5, -4, 0) Add(platformRight) platformRight:EnablePhysics(true) platformRight:SetNonResponsive() character:SetName("character") character:LoadSphere(1, 1, 1, "media/HourOfCodeDark.jpg") character:SetPosition(-5, -1, 0) Add(character) character:EnablePhysics(true) character:SetResponsive() linearVelocity:Set(1, 0, 0) character:SetLinearVelocity(linearVelocity) jump:Set(70, 350, 0) gravity:Set(0, -9, 0) SetGravity3D(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() > 1 platformMovesUp = false end if platformRight:GetY() <= -4 platformMovesUp = true end // The platform moves either up or down if totalSeconds >= 0.01 if platformMovesUp platformRight:MoveY(0.05) else platformRight:MoveY(-0.05) 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() >= 5) character:SetPosition(-5, platformRight:GetY() + 1, character:GetZ()) character:SetLinearVelocity(linearVelocity) /* 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(((totalSeconds * 100) mod 6) - 4) if totalSeconds > 10 totalSeconds = 0 end // 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) Vector3 axis // 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 axis:Set(0, 0, 100) character:ApplyTorque(axis) 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 axis:Set(0, 0, -100) character:ApplyTorque(axis) end end action BeginCollision(CollisionEvent3D event) impactSound:Play() end end
In the next tutorial, we will discuss Mass, Friction, and More In 3D, which describes how to use mass, friction and more in a 3D game.