Android Tutorial Game Physics: Gravity

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

  1. wonder

    wonder Expert Licensed User

    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)

        
    '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(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:

    Last edited: May 10, 2015
  2. Peter Simpson

    Peter Simpson Expert Licensed User

    So so so simple but so good with nice clean clear clear code to follow, cheers for sharing Bruno :)
     
    wonder likes this.
  3. wonder

    wonder Expert Licensed User

    Like you say in your signature, "Simplicity is the key"! :D
     
    Cableguy likes this.
  4. ilan

    ilan Expert Licensed User

    nice... (put all subs in one tick event is clever ;))
     
    wonder likes this.
  5. wonder

    wonder Expert Licensed 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! ;)
    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. inakigarm

    inakigarm Well-Known Member Licensed 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.
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice