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.