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.