Android Tutorial Game Physics: Gravity

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:
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.
B4X:
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.
B4X:
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:
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

  • Sunset Run.zip
    22.7 KB · Views: 1,229
Last edited:

wonder

Expert
Licensed User
Longtime User
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! ;)
B4X:
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:
Top