  Android Tutorial Game Physics: Gravity

Discussion in 'Tutorials & Examples' started by wonder, May 10, 2015.

1. 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:
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:
Code:
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.
Code:
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.
Code:
Player.Velocity_Y = Player.Velocity_Y + (World_Gravity * Delta_Time)
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:
It's time now to move the object on screen according to its velocity.
Code:
Player.Y = Player.Y + (Player.Velocity_Y * Delta_Time)
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

Despite my long explanation, in the end we need nothing more than this:
Code:
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:
Code:
#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)

'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(626)
Obstacle.Height      =
Round(Obstacle.Width * (Rnd(100151) / 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(626)
Obstacle.Height =
Round(Obstacle.Width * (Rnd(100151) / 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.33Then
Player_Jump(-Player.Jump_Velocity *
0.33)

End If

End Select

Else
Score =
0
Setup_Initial_Conditions

End If
End Sub

Attached Files:

• Sunset Run.zip
File size:
22.7 KB
Views:
618
Last edited: May 10, 2015
2. So so so simple but so good with nice clean clear clear code to follow, cheers for sharing Bruno wonder likes this.
3. Like you say in your signature, "Simplicity is the key"! Cableguy likes this.
4. nice... (put all subs in one tick event is clever )

wonder likes this.
5. When establishing a fixed value for the Delta_Time we assume that the timer will perform optimally, which is not always the case.
So like good science men that we are, we should not assume things, we should measure them! Code:
Sub Get_Delta_Time

If Frame_Timestamp <> 0 Then Delta_Time = (DateTime.Now - Frame_Timestamp) / 1000
Frame_Timestamp =
DateTime.Now
End Sub
I've updated the tutorial, code and zip file on the first post.

Last edited: May 10, 2015
spairo and thedesolatesoul like this.
6. inakigarmWell-Known MemberLicensed User

Great job !! seems simple when reading the code but create from scratch would be very hard for me

Thanks for sharing !!

Raka Kuswanto, Phayao and wonder like this.