Creating a Visual Basic Pong Game

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

You should probably know by now that I am not the biggest gamer. Fact is: I simply do not have ample enough time to sit and physically play a game. Working three jobs only allows for so much free time. Although I do not play enough games, I still love making them! That is as far as my tango with gaming goes. Today, I will show you how to make a pong game.

Pong

Pong is a game similar to table tennis where two “players” hit a ball back and forth from one side to another. The difference comes in the fact that the ball can bounce from any of the sides. To write a pong program, you have to keep track of the following:

  • Each bat’s position
  • The continuous position of the ball
  • The position of the ball in the vicinity of the bats

This is where the fun comes in, and what makes the game tick!

I have structured the application in the following way:

  • Graphics and animation
  • Bats
  • Ball
  • Scoring
  • Form

These are all separate classes because it makes the game more compartmentalized and organized. Let’s start.

Design

Open Visual Studio and create a new Visual Basic Windows Forms project. Add the following objects to your form:

  • 3 Pictureboxes. These are used for the two bats and the ball.
  • 2 Labels. These will be used for the scores.

You have to keep in mind that my objects, both on and off the form, may be named differently than yours.

Add a new class. This class will be used for the position animation:

Imports System.Drawing

Public Class clsSprite

   Public pVelocity As PointF
   Public pLocation As PointF
   Public sSize As SizeF

   Public Sub New(ByVal x As Single, ByVal y As Single, _
      ByVal Width As Single, ByVal Height As Single)

      pLocation.X = x
      pLocation.Y = y
      sSize.Width = Width
      sSize.Height = Height

   End Sub

   Protected Sub New()
      ' TODO: Complete member initialization'
   End Sub

   Public Overridable Sub UpdateTime(ByVal dblGame _
      As Double, ByVal dblElapsed As Double)

      pLocation.X += pVelocity.X * CSng(dblElapsed)
      pLocation.Y += pVelocity.Y * CSng(dblElapsed)

   End Sub

   Public Overridable Sub Draw()

   End Sub

End Class

clsSprite keeps track of the ball’s position. Add the next class:

Imports System.Drawing

Public Class clsControlSprite
   Inherits clsSprite

   Public ctrControl As Control

   Public Sub New(ByVal ctrl As Control)
      MyBase.New()
      ctrControl = ctrl

      SetDimensions()

   End Sub

   Public Overrides Sub Draw()

      ctrControl.Location = New Point(CInt(Fix(pLocation.X _
         + 0.5F)), CInt(Fix(pLocation.Y + 0.5F)))

      ctrControl.Refresh()

   End Sub

   Protected Sub SetDimensions()

      sSize.Height = ctrControl.Height
      sSize.Width = ctrControl.Width

   End Sub

End Class

This class sets the dimensions of the object to be drawn and draws the object at the designated location.

Let’s move on to the Ball class. Add the following namespaces:

Imports System.Drawing
Imports System.Media

Here is more information on the preceding Namespaces:

Ensure that your Ball class inherits from clsControlSprite. Then, add the next variables:

   Private Const intRadius As Integer = 20
   Private Const dblSpeed As Double = 200
   Private Shared clrColor As Color = Color.Black

   Private rndRandom As Random = New Random()
   Private sngMaxY As Single
   Private sngMinY As Single
   Private sngMaxX As Single
   Private sngMinX As Single
   Private bBat1 As clsBat
   Private bBat2 As clsBat
   Private gsGameState As clsScore

Because we are dealing with a round object, the ball, I have created variables to set the radius and size of the ball along with its color and desired speed. The bottom three are Bat objects as well as a Scoring object. We will create these classes later.

   Public Sub New(ByVal control As Control, _
         ByVal minX As Single, ByVal maxX As Single, _
         ByVal minY As Single, ByVal maxY As Single, _
         ByVal player1 As clsBat, _
         ByVal player2 As clsBat, _
         ByVal score As clsScore)
      MyBase.New(control)

      gsGameState = score

      sngMinX = minX
      sngMaxX = maxX
      sngMinY = minY
      sngMaxY = maxY

      Clear()

      bBat1 = player1
      bBat2 = player2


      control.BackColor = clrColor
      control.Width = intRadius
      control.Height = intRadius

      SetDimensions()

   End Sub

The Constructor initializes all the variables and sets the default settings. Let’s add the Clear sub called in the Constructor now:

   Private Sub Clear()

      pLocation = New PointF((sngMaxX - sngMinX) / 2, _
         (sngMaxY - sngMinY) / 2 + rndRandom.Next(300) - 150)
      pVelocity = New PointF(CSng(dblSpeed) * _
         ((rndRandom.Next(2) * 2) - 1), CSng(dblSpeed) * _
         ((rndRandom.Next(2) * 2) - 1))
   End Sub

This clears the current location of the ball. Add the rest of the Ball class:

   Public Overrides Sub UpdateTime(ByVal dblGame As Double, _
      ByVal dblElapsed As Double)

      If (pLocation.Y < sngMinY AndAlso pVelocity.Y < 0) _
            OrElse (pLocation.Y > sngMaxY - intRadius AndAlso _
            pVelocity.Y > 0) Then
         pVelocity.Y = -pVelocity.Y
      End If

      If (pVelocity.X < 0 AndAlso collision(bBat1)) OrElse _
         (pVelocity.X > 0 AndAlso collision(bBat2)) Then

         pVelocity.X = -pVelocity.X

      End If

      If pLocation.X > sngMaxX - intRadius Then

         gsGameState.intPlayer1 += 1

         Clear()

      End If

      If pLocation.X < sngMinX Then

         gsGameState.intPlayer2 += 1

         Clear()
      End If

      UpdateTime(dblGame, dblElapsed)
   End Sub


   Private Function collision(ByVal bat As clsBat) As Boolean

      Return Not (pLocation.X > bat.pLocation.X + bat.sSize.Width _
         OrElse pLocation.X + sSize.Width < bat.pLocation.X _
         OrElse pLocation.Y > bat.pLocation.Y + bat.sSize.Height _
         OrElse pLocation.Y + sSize.Height < bat.pLocation.Y)

   End Function

UpdateTime and Collision determine whether or not the ball has collided with either of the Bat objects or any of the walls. If no collision was detected, the other player, in this case, the computer’s or the human player’s score gets updated.

Add the Bats class.

Ensure that the Bat class inherits from the ControlSprite class and add the following variables and Constructor:

   Private Const sngBallDistance As Single = 250
   Private sngEstYLoc As Single = -1

   Public Const intWidth As Integer = 20
   Public Const intHeight As Integer = 75
   Private Shared clrColor As Color = Color.White
   Private Const dblSpeed As Double = 500

   Private kUp As Keys
   Private kDown As Keys
   Private blnUp As Boolean
   Private blnDown As Boolean

   Public sngMax As Single
   Public sngMin As Single
   Public cBall As clsBall
   Private blnHuman As Boolean
   Private Shared rndRandom As Random = New Random()

   Public Sub New(ByVal ctrl As Control, _
         ByVal x As Integer, ByVal MinPos As Single, _
         ByVal MaxPos As Single)
      MyBase.New(ctrl)
      Reset(ctrl, x, MinPos, MaxPos)

      blnHuman = False
   End Sub

   Public Sub New(ByVal ctrl As Control, _
         ByVal x As Integer, ByVal kUp As Keys, _
         ByVal kDown As Keys, ByVal MinPos As Single, _
         ByVal MaxPos As Single)
      MyBase.New(ctrl)
      Reset(ctrl, x, MinPos, MaxPos)

      kUp = kUp
      kDown = kDow

      blnHuman = True
   End Sub

The Bat class needs user interaction and the Bat class needs two separate instances (the players) and need to cater for it. The rest of the variables construct the look and feel of the two bats and sets up their locations.

Add the following:

   Private Sub Reset(ByVal ctrl As Control, _
         ByVal x As Integer, _
         ByVal MinPos As Single, ByVal MaxPos As Single)
      sngMin = MinPos
      sngMax = MaxPos

      pLocation.X = x

      pLocation.Y = (sngMax - sngMin - intHeight) / 2

      ctrl.BackColor = clrColor
      ctrl.Width = intWidth
      ctrl.Height = intHeight

      SetDimensions()

   End Sub

   Public Overrides Sub UpdateTime(ByVal dblGame As Double, _
         ByVal dblElapsed As Double)

      If blnHuman Then

         HumanPlayer()

      Else

         ComputerPlayer()

      End If

      UpdateTime(dblGame, dblElapsed)

      If pLocation.Y < sngMin Then

         pLocation.Y = sngMin

      End If

      If pLocation.Y > sngMax - intHeight Then

         pLocation.Y = sngMax - intHeight

      End If

   End Sub

Reset resets all the objects to their initial settings. UpdateTime will run continuously and keep track of user movements as well as the ball’s movements. Add the rest of the Bat class:

   Private Sub ComputerPlayer()

      If cBall.pVelocity.X > 0 Then

         If pLocation.X - cBall.pLocation.X < sngBallDistance Then

            If sngEstYLoc > -1 Then

               If (Math.Sign(pVelocity.Y) > 0 AndAlso pLocation.Y _
                     >= sngEstYLoc) OrElse (Math.Sign(pVelocity.Y) _
                     < 0 AndAlso pLocation.Y <= sngEstYLoc) Then

                  pVelocity.Y = 0
                  sngEstYLoc = -1

               End If

            Else

               If pLocation.X - cBall.pLocation.X > 50 Then

                  sngEstYLoc = cBall.pLocation.Y + _
                     (pLocation.X - cBall.pLocation.X) * _
                     Math.Sign(cBall.pVelocity.Y) + _
                     rndRandom.Next(100) - 50

                  If sngEstYLoc > sngMax - intHeight Then

                     sngEstYLoc = sngMax - intHeight

                  End If

                  If sngEstYLoc < sngMin Then

                     sngEstYLoc = sngMin

                  End If

                  If sngEstYLoc > pLocation.Y Then

                     pVelocity.Y = CSng(dblSpeed)

                  Else

                     pVelocity.Y = -CSng(dblSpeed)

                  End If

               End If

            End If

         Else

            pVelocity.Y = 0
            sngEstYLoc = -1

         End If

      Else

         pVelocity.Y = 0
         sngEstYLoc = -1

      End If

   End Sub

   Private Sub HumanPlayer()

      Dim workingVelocity As Double = 0.0

      If blnUp Then

         workingVelocity += -dblSpeed

      End If

      If blnDown Then

         workingVelocity += dblSpeed

      End If

      pVelocity.Y = CSng(workingVelocity)

   End Sub

   Public Sub KeyDown(ByVal k As Keys)

      If k = kUp Then

         blnUp = True

      End If

      If k = kDown Then

         blnDown = True

      End If

   End Sub

   Public Sub KeyUp(ByVal k As Keys)

      If k = kUp Then

         blnUp = False

      End If

      If k = kDown Then

         blnDown = False

      End If

   End Sub

Each player’s actions gets recorded and the ball’s activity keeps getting monitored until a collision (against any of the walls or Bat objects) occurs.

Add the Scoring class:

Public Class clsScore

   Public intPlayer1 As Integer
   Public intPlayer2 As Integer

End Class

That’s it! This simply keeps track of the players’ scores through the two scoring variables inside. Now, add the necessary variables and constructor to your Form’s code:

   Private swStopWatch As Stopwatch = New Stopwatch()
   Private dblTime As Double
   Private lngFrames As Long

   Private bPlayer1 As clsBat
   Private bPlayer2 As clsBat

   Private bBall As clsBall
   Private Score As clsScore

   Public Sub New()

      InitializeComponent()

      SetStyle(ControlStyles.AllPaintingInWmPaint Or _
         ControlStyles.UserPaint Or _
         ControlStyles.OptimizedDoubleBuffer, True)

      Score = New clsScore()

      bPlayer1 = New clsBat(player1Bat, 30, Keys.Q, Keys.A, _
         0, ClientSize.Height)

      bPlayer2 = New clsBat(player2Bat, ClientSize.Width _
         - 30 - 70, 0, ClientSize.Height)

      bBall = New clsBall(ballControl, 0, ClientSize.Width, _
         0, ClientSize.Height, bPlayer1, bPlayer2, Score)

      bPlayer2.cBall = bBall

      dblTime = 0.0

      swStopWatch.Start()

   End Sub

In the constructor, I initialize all the playing objects to their default values so that the game can start. Add the KeyUp and KeyDown events:

   Private Sub frmPong_KeyDown(sender As Object, _
         e As KeyEventArgs) Handles Me.KeyDown

      bPlayer1.KeyDown(e.KeyCode)
      bPlayer2.KeyDown(e.KeyCode)

   End Sub

   Private Sub frmPong_KeyUp(sender As Object, _
         e As KeyEventArgs) Handles Me.KeyUp

      bPlayer1.KeyUp(e.KeyCode)
      bPlayer2.KeyUp(e.KeyCode)

   End Sub

Finally, add the Form’s Paint event to do all the drawing:

   Private Sub frmPong_Paint(sender As Object, _
         e As PaintEventArgs) Handles Me.Paint

      Dim dblGame As Double = _
         swStopWatch.ElapsedMilliseconds / 1000.0
      Dim dblElapsed As Double = dblGame - dblTime

      dblTime = dblGame
      lngFrames += 1

      bPlayer1.UpdateTime(dblGame, dblElapsed)
      bPlayer2.UpdateTime(dblGame, dblElapsed)
      bBall.UpdateTime(dblGame, dblElapsed)

      player1Score.Text = Score.intPlayer1.ToString()
      player1Score.Refresh()

      player2Score.Text = Score.intPlayer2.ToString()
      player2Score.Refresh()

      bPlayer1.Draw()
      bPlayer2.Draw()
      bBall.Draw()

      Invalidate()

   End Sub

Conclusion

Not too complicated. As with most of my games, I deal mostly with logic, and as you can probably tell, the logic involved is usually not too complex, as long as you structure your application well enough.

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read