Game Physics: Gravity
In this tutorial I will explain and exemplify through a simple "runner" game how to apply physics to any B4X object.
May it be a Panel, a Label, an ImageView, a ListView or a Button, Newtonian physics can be applied to anything that has an X, Y coordinate.
If you like this tutorial, please consider a donation. No matter how small, it will be highly appreciated.
Acceleration:
Velocity:
Despite my long explanation, in the end we need nothing more than this:
If you liked this tutorial, please consider a donation. No matter how small, it will be highly appreciated.
Now that we know how gravity works in a virtual world, let's see it in action in an oversimplified runner game:
In this tutorial I will explain and exemplify through a simple "runner" game how to apply physics to any B4X object.
May it be a Panel, a Label, an ImageView, a ListView or a Button, Newtonian physics can be applied to anything that has an X, Y coordinate.
If you like this tutorial, please consider a donation. No matter how small, it will be highly appreciated.
Acceleration:
By definition, the strength of the gravitational field is numerically equal to the acceleration of objects under its influence. So the first thing we have to do is to define an acceleration value and assign it to a gravity variable. In the real world, this would be measured in meters per second squared, but in our little virtual world, things are slightly different.
Our unit of time will be the time interval between the last frame and the current frame.
Optimally, our game engine will be updated 60 times per second, meaning that interval between two frames would be 16.6(6) milliseconds.
Measuring this interval is fairly easy:
Having this in consideration, I've defined the gravity acceleration value to be 8 times the screen's height in landscape mode, therefore 800%y.
You are encouraged to experiment with different values, but remember that any value you choose should always be a percentage, since we all have different screen resolutions / dpi.
Ideally, we should create our own world with our own coordinates and create a "translator" subroutine which would convert such coordinates and values into screen coordinates and values, but that's a subject for another time and is not covered in this tutorial.
Our unit of time will be the time interval between the last frame and the current frame.
Optimally, our game engine will be updated 60 times per second, meaning that interval between two frames would be 16.6(6) milliseconds.
Measuring this interval is fairly easy:
B4X:
Sub Get_Delta_Time
If Frame_Timestamp <> 0 Then Delta_Time = (DateTime.Now - Frame_Timestamp) / 1000
Frame_Timestamp = DateTime.Now
End Sub
Having this in consideration, I've defined the gravity acceleration value to be 8 times the screen's height in landscape mode, therefore 800%y.
You are encouraged to experiment with different values, but remember that any value you choose should always be a percentage, since we all have different screen resolutions / dpi.
B4X:
Dim World_Gravity = 800%y As Float
Ideally, we should create our own world with our own coordinates and create a "translator" subroutine which would convert such coordinates and values into screen coordinates and values, but that's a subject for another time and is not covered in this tutorial.
Velocity:
Now that we have a gravity value defined, all we need to do is to apply it to a velocity.
My screen's height in landscape mode is 720 pixels, therefore 800%y will be 5760.
As explained above, Delta_Time is the time interval between the last frame and the current frame, optimally 0.016 seconds.
Let's see what happens frame by frame in my Asus tablet:
Frame 0: New Velocity Y = 0 + (5760 * 0.016) <=> New Velocity Y = 92.16 pixels * delta_time
Frame 1: New Velocity Y = 92.16 + (5760 * 0.016) <=> New Velocity Y = 184.32 pixels * delta_time
Frame 2: New Velocity Y = 184.32 + (5760 * 0.016) <=> New Velocity Y = 276.48 pixels * delta_time
Position:
B4X:
Player.Velocity_Y = Player.Velocity_Y + (World_Gravity * Delta_Time)
As explained above, Delta_Time is the time interval between the last frame and the current frame, optimally 0.016 seconds.
Let's see what happens frame by frame in my Asus tablet:
Frame 0: New Velocity Y = 0 + (5760 * 0.016) <=> New Velocity Y = 92.16 pixels * delta_time
Frame 1: New Velocity Y = 92.16 + (5760 * 0.016) <=> New Velocity Y = 184.32 pixels * delta_time
Frame 2: New Velocity Y = 184.32 + (5760 * 0.016) <=> New Velocity Y = 276.48 pixels * delta_time
It's time now to move the object on screen according to its velocity.
If you place an object at the top of the screen (0%y), let's see what happens:
Frame 0: New Position Y = 0 + (92.16 * 0.016) <=> New Position Y = 1.47 pixels
Frame 1: New Position Y = 1.47 + (184.32 * 0.016) <=> New Position Y = 4.41 pixels
Frame 2: New Position Y = 4.41 + (276.48 * 0.016) <=> New Position Y = 8.83 pixels
B4X:
Player.Y = Player.Y + (Player.Velocity_Y * Delta_Time)
Frame 0: New Position Y = 0 + (92.16 * 0.016) <=> New Position Y = 1.47 pixels
Frame 1: New Position Y = 1.47 + (184.32 * 0.016) <=> New Position Y = 4.41 pixels
Frame 2: New Position Y = 4.41 + (276.48 * 0.016) <=> New Position Y = 8.83 pixels
Despite my long explanation, in the end we need nothing more than this:
B4X:
Sub Apply_Physics
Player.Velocity_Y = Player.Velocity_Y + (World_Gravity * Delta_Time)
Player.Y = Player.Y + (Player.Velocity_Y * Delta_Time)
End Sub
If you liked this tutorial, please consider a donation. No matter how small, it will be highly appreciated.
Now that we know how gravity works in a virtual world, let's see it in action in an oversimplified runner game:
B4X:
#Region Project Attributes
#ApplicationLabel: Sunset Run
#VersionCode: 1
#VersionName:
'SupportedOrientations possible values: unspecified, landscape or portrait.
#SupportedOrientations: Landscape
#CanInstallToExternalStorage: False
#End Region
#Region Activity Attributes
#FullScreen: True
#IncludeTitle: False
#End Region
Sub Process_Globals
Type Physical_Object(X As Float, Y As Float, Old_X As Float, Old_Y As Float, _
Width As Float, Height As Float, Angle As Float, Velocity As Float, _
Velocity_X As Float, Velocity_Y As Float, Jump_Velocity As Float, _
Grounded As Boolean, Mass As Float, Elasticity As Float, Collision As Boolean)
Dim Main_Cycle As Timer
End Sub
Sub Globals
'Game Engine
Dim Target_FPS = 60 As Int
Dim Delta_Time = 1 / Target_FPS As Float
Dim Frame_Timestamp As Long
'World
Dim World_Gravity As Float
'Output Text
Dim Score As Int
Dim Score_Done As Boolean
Private Output As Label
'Objects
Private pnlGround As Panel
Private pnlObstacle As Panel
Private pnlPlayer As Panel
Private pnlTouch As Panel
'Object Physical Properties
Dim Ground As Physical_Object
Dim Obstacle As Physical_Object
Dim Player As Physical_Object
End Sub
Sub Activity_Create(FirstTime As Boolean)
'Load Layout
Activity.LoadLayout("Layout1")
'Setup Output Text
Output.Left = 0%x
Output.Top = 0%y
Output.Width = 100%x
Output.Height = 100%y
'Setup User Input
pnlTouch.Left = 0%x
pnlTouch.Top = 0%y
pnlTouch.Width = 100%x
pnlTouch.Height = 100%y
'Setup Initial Game Conditions
Setup_Initial_Conditions
'Apply everything above to our panels
Obj2Pnl
'Make sure the Touch Panel is the front most one
pnlTouch.BringToFront
'Startup the Main Cycle
Main_Cycle.Initialize("Main_Cycle", Delta_Time * 1000)
Main_Cycle.Enabled = True
End Sub
Sub Setup_Initial_Conditions
'Setup Object dimensions and location on screen
Ground.Width = 100%x
Ground.Height = 60%y
Ground.X = 0%x
Ground.Y = 80%y
Obstacle.Width = 1%x * Rnd(6, 26)
Obstacle.Height = Round(Obstacle.Width * (Rnd(100, 151) / 100))
Obstacle.X = 100%x
Obstacle.Y = Ground.Y - Obstacle.Height
Obstacle.Velocity_X = -60%x
Player.Width = 4%x
Player.Height = Player.Width
Player.X = 50%x - (Player.Width / 2)
Player.Y = Ground.Y - Player.Height
Player.Jump_Velocity = 200%x
'Setup World Properties
World_Gravity = 800%y
End Sub
Sub Activity_Resume
End Sub
Sub Activity_Pause (UserClosed As Boolean)
End Sub
Sub Main_Cycle_Tick
Backup_Object_Positions
Get_Delta_Time
Run_Run_Run
Apply_Physics
Collision_Detector
Score_Control
Obstacle_Respawn_Control
Obj2Pnl
Display_Score
End Sub
Sub Backup_Object_Positions
Player.Old_X = Player.X
Player.Old_Y = Player.Y
Obstacle.Old_X = Obstacle.X
Obstacle.Old_Y = Obstacle.Y
End Sub
Sub Get_Delta_Time
If Frame_Timestamp <> 0 Then Delta_Time = (DateTime.Now - Frame_Timestamp) / 1000
Frame_Timestamp = DateTime.Now
End Sub
Sub Run_Run_Run
Obstacle.X = Obstacle.X + (Obstacle.Velocity_X * Delta_Time)
If Not(Game_Over) Then Obstacle.Velocity_X = Max(Obstacle.Velocity_X - (2%x * Delta_Time), -250%x)
End Sub
Sub Apply_Physics
Player.Velocity_Y = Player.Velocity_Y + (World_Gravity * Delta_Time)
Player.Y = Player.Y + (Player.Velocity_Y * Delta_Time)
Obstacle.Velocity_Y = Obstacle.Velocity_Y + (World_Gravity * Delta_Time)
Obstacle.Y = Obstacle.Y + (Obstacle.Velocity_Y * Delta_Time)
End Sub
Sub Collision_Detector
'Detect Ground Collisions
If Player.Y + Player.Height >= Ground.Y Then Player.Y = Ground.Y - Player.Height
If Obstacle.Y + Obstacle.Height >= Ground.Y Then Obstacle.Y = Ground.Y - Obstacle.Height
'Detect Player / Obstacle Collisions
Dim which_side As String
Dim top = Obstacle.Y As Float
Dim bottom = Obstacle.Y + Obstacle.Height As Float
Dim left = Obstacle.X As Float
Dim right = Obstacle.X + Obstacle.Width As Float
If (Player.X + Player.Width) >= Obstacle.X AND Player.X < (Obstacle.X + Obstacle.Width ) _
AND (Player.Y + Player.Height) >= Obstacle.Y AND Player.Y < (Obstacle.Y + Obstacle.Height) Then
If Player.Collision = False Then
If Player.Velocity_Y < 0 Then Player.Velocity_Y = 0
Player.Collision = True
End If
If Player.Old_x < left AND Player.X + Player.Width > left Then which_side = "LEFT"
If Player.Old_x + Player.Width > right AND Player.X < right Then which_side = "RIGHT"
If Player.Old_y < top AND Player.Y + Player.Height > top Then which_side = "TOP"
If Player.Old_y + Player.Height > bottom AND Player.Y < bottom Then which_side = "BOTTOM"
If which_side = "TOP" Then
Player.Grounded = True
Player.Y = top - Player.Height
Else If which_side = "BOTTOM" Then
Player.Grounded = False
Player.Y = bottom
Else If which_side = "LEFT" Then
Player.Grounded = False
Player.X = left - Player.Width
Else If which_side = "RIGHT" Then
Player.Grounded = False
Player.X = right
Else
Player.Grounded = False
End If
Else
Player.Grounded = False
End If
End Sub
Sub Player_Jump(Jump_Velocity_Y As Float)
Player.Velocity_Y = Jump_Velocity_Y
End Sub
Sub Player_On_The_Ground As Boolean
If (Player.Y + Player.Height >= Ground.Y) OR Player.Grounded Then
Return True
Else
Return False
End If
End Sub
Sub Display_Score
If Not(Game_Over) Then
Output.Text = CRLF & "SCORE: " & Score
Else
Output.Text = CRLF & "GAME OVER" & CRLF & "SCORE: " & Score
End If
End Sub
Sub Score_Control
If Not(Game_Over) AND Player.X > Obstacle.X AND Score_Done = False Then
Score = Score + 1
Score_Done = True
End If
End Sub
Sub Game_Over As Boolean
If Player.X + Player.Width < 0%x Then
Return True
Else
Return False
End If
End Sub
Sub Obstacle_Respawn_Control
If Obstacle.X + Obstacle.Width < 0%x Then
Obstacle.Width = 1%x * Rnd(6, 26)
Obstacle.Height = Round(Obstacle.Width * (Rnd(100, 151) / 100))
Obstacle.X = 100%x
Obstacle.Y = Ground.Y - Obstacle.Height
Score_Done = False
Player.Collision = False
End If
End Sub
Sub Obj2Pnl
pnlGround.Width = Ground.Width
pnlGround.Height = Ground.Height
pnlGround.Left = Ground.X
pnlGround.Top = Ground.Y
pnlObstacle.Width = Obstacle.Width
pnlObstacle.Height = Obstacle.Height
pnlObstacle.Left = Obstacle.X
pnlObstacle.Top = Obstacle.Y
pnlPlayer.Width = Player.Width
pnlPlayer.Height = Player.Height
pnlPlayer.Left = Player.X
pnlPlayer.Top = Player.Y
End Sub
Sub pnlTouch_Touch (Action As Int, X As Float, Y As Float)
If Not(Game_Over) Then
Select Action
Case Activity.ACTION_DOWN
If Player_On_The_Ground Then
Player.Grounded = False
Player_Jump(-Player.Jump_Velocity)
End If
Case Activity.ACTION_MOVE
Case Activity.ACTION_UP
If Player.Velocity_Y < (-Player.Jump_Velocity * 0.33) Then
Player_Jump(-Player.Jump_Velocity * 0.33)
End If
End Select
Else
Score = 0
Setup_Initial_Conditions
End If
End Sub
Attachments
Last edited: