Introduction
I'm a big fan of first-person shooter games and Targets is my attempt at creating something in WPF with the feel of those genre of games. Targets is no Ghost Recon or Call of Duty but it does a bit of justice to the world of 2D WPF games. Its plot is simple. There is no mission proper where you have to kill multiple enemies, with an array of weapons at your disposal. In Targets, you simply have to shoot at multiple targets, which explains the origin of its name, and score a certain number of points, within a specific time period, to proceed to the next level. The jungle is your 'battlefield' and your 'weapon' is a reliable Glock 17.
There are only three levels, because I'm sure you have better things to do with your time, and going through those levels is easy or hard depending on how fast you can move your mouse and how fast you can click your mouse buttons, those targets pop up-and-down pretty quickly.
Requirements
To open the solution provided from the download link above, you require either of the following:
- Visual Studio 2010
- Expression Blend
NB: If you're using the Express Edition of Visual Studio, ensure that you open the solution using Visual Basic Express.
Targets
How it Works
When playing Targets, the two major actions are firing and reloading your weapon.
- Firing: Press the left mouse button to fire your weapon. In Level 1, you'll have 10 rounds per magazine at your disposal, 17 rounds in Level 2, and 19 rounds in Level 3.
- Reloading: To reload your weapon, you have to click the right mouse button twice. The first click is to 'eject' the spent clip while the second click is to 'load' a new magazine. A fast double click will do you some good. You can only reload once you have spent all the rounds in a magazine.
Your objective is to reach a certain number of points at each level in a time of 40secs:
- Level 1: Get 450+ points in 40secs to proceed to the next level
- Level 2: Get 550+ points in 40secs to proceed to the next level
- Level 3: Get 600+ points in 40secs to be declared Targets' Champion
As you can see, the logic is quite simple though the simplicity will not be aided by the targets which, as I mentioned earlier, show up and disappear very quickly.
Design and Layout
I designed most of Targets' elements in Expression Design and added/designed some extra elements in Expression Blend. The following image shows some of the elements of interest:
JungleCanvas
contains two Image
controls. The first Image
control, JungleImage
, contains a full image of some jungle while the second Image
control, OverlayImage
, contains an image that is a cut-out of a section of the image in JungleImage
. The following image shows the cut-out image on top of the original image:
The overlay image merely serves to create the illusion that some of the targets are appearing from behind some bushes and a fallen branch, more precisely from beneath the ground and behind some bushes and a fallen branch. The Target
s are placed between the two Image
controls.
The CrossHairs
in the game is nothing more than a ViewBox
containing several Path
objects. Its IsHitTestVisible
property is set to false
.
Target
The targets have the challenging task of showering with virtual lead are Target
UserControl
s. If you open up Target.xaml in Expression Blend, you won't see the elements that show-up in the game. This is because the ClipToBounds
property of LayoutRoot
is set to true
.
Unchecking the checkbox
reveals the visual elements:
The numbered regions are ViewBox
es, containing several Path
objects, while the whitish area is just a Path
object:
Target
contains a Storyboard
named TargetStoryboard
that causes the apparent popping-up and down of the target in the game.
You can adjust the Storyboard
to your liking if you feel like it.
NB: The RepeatBehavior
of TargetStoryboard
is set to 1x.
Dent
The dents that show up on the targets when you fire at them are courtesy of the Dent
UserControl
. The dent you see on a target is made up of a single elliptical Path
object with a radial gradient. The following is an enlarged view of the Dent
UserControl
.
GlockRound
The rounds of ammo that you see at the top of the window are courtesy of the GlockRound
UserControl
. The following image shows an enlarged view of the UserControl
:
To progress through the game, you will interact with a few dialog boxes that allow you to move to the next level or restart a specific level. These dialog boxes are just Grid
s containing several elements. The following image shows Level_1_Grid
and LevelFailedGrid
.
The Code
Target
In the code for the Target UserControl
, there are five event handlers for the MouseLeftButtonDown
events of the four Viewbox
es and the Path
that make up the visual elements of the control.
Private Sub MainArea_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles MainArea.MouseLeftButtonDown
HitTarget(e)
End Sub
Private Sub GreenZone_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles GreenZone.MouseLeftButtonDown
HitTarget(e)
Points = 7
End Sub
Private Sub BlueZone_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles BlueZone.MouseLeftButtonDown
HitTarget(e)
Points = 8
End Sub
Private Sub YellowZone_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles YellowZone.MouseLeftButtonDown
HitTarget(e)
Points = 9
End Sub
Private Sub RedZone_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles RedZone.MouseLeftButtonDown
HitTarget(e)
Points = 10
End Sub
The value of a variable, Points
, is set in the event handlers of the Viewbox
es and a method, HitTarget
, is called in all.
Private Sub HitTarget(ByVal e As System.Windows.Input.MouseButtonEventArgs)
Dim x As Double = e.GetPosition(TargetCanvas).X
Dim y As Double = e.GetPosition(TargetCanvas).Y
Dim dent As New Dent()
Canvas.SetLeft(dent, x)
Canvas.SetTop(dent, y)
TargetCanvas.Children.Add(dent)
End Sub
In the HitTarget
method, a Dent
UserControl
is added to the TargetCanvas
at the location where the user clicks on the target.
The other method in Target
that you should take note of is PlayStoryboard
.
Public Sub PlayStoryboard()
Dim targeter As Storyboard
targeter = CType(Me.Resources("TargetStoryboard"), Storyboard)
targeter.Begin(Me)
End Sub
MainWindow
In the MainWindow
Loaded
event handler, we do the following:
Private Sub MainWindow_Loaded(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) _
Handles Me.Loaded
Gunshot.Stream = My.Resources.Gunshot
DryFire.Stream = My.Resources.Dry_Fire
EjectMag.Stream = My.Resources.Ejecting_Magazine
PopInClip.Stream = My.Resources.Pop_Clip_In
Gunshot.Load()
DryFire.Load()
EjectMag.Load()
PopInClip.Load()
JungleCanvas.IsEnabled = False
AddHandler TargetsTimer.Tick, AddressOf TargetsTimer_Tick
TargetsTimer.Interval = New TimeSpan(0, 0, 0, 0, 3000)
AddHandler SecondsTimer.Tick, AddressOf SecondsTimer_Tick
SecondsTimer.Interval = New TimeSpan(0, 0, 1)
End Sub
Here, we load the sound files for several SoundPlayer
objects. The sound files are project resources which you can see in the project properties window by clicking on the Resources tab in Visual Studio.
The WAV files that provide the realistic sound effects are courtesy of www.soundbible.com.
Once you click the Start button to start Level 1, the following method is called:
Private Sub StartLevel1Btn_Click(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) _
Handles StartLevel1Btn.Click
Level_1_Grid.Visibility = Windows.Visibility.Hidden
StartNewLevel()
End Sub
The StartNewLevel
button does the following:
Private Sub StartNewLevel()
TotalPoints = 0
SevenPoints = 0
EightPoints = 0
NinePoints = 0
TenPoints = 0
SevensTxtBlock.Text = "0"
EightsTxtBlock.Text = "0"
NinesTxtBlock.Text = "0"
TensTxtBlock.Text = "0"
TotalPointsTxtBlck.Text = "0"
SecTextBlck.Text = "40"
JungleCanvas.IsEnabled = True
RestartGameBtn.IsEnabled = True
RestartLevelBtn.IsEnabled = True
If (RoundsStack.Children.Count > 0) Then
RoundsStack.Children.Clear()
End If
ammo = MagCapacity
Dim i As Integer = ammo
Do While i > 0
Dim round As New GlockRound()
RoundsStack.Children.Add(round)
i -= 1
Loop
TargetsTimer.Start()
SecondsTimer.Start()
End Sub
Since we call the Start
method of DispatcherTimer
objects in the method above, their Tick
event handlers are called at the intervals specified in the MainWindow
Loaded
event.
Private Sub TargetsTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
ShowTarget()
End Sub
Private Sub SecondsTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
If (seconds > -1) Then
SecTextBlck.Text = seconds.ToString()
seconds -= 1
Else
TargetsTimer.Stop()
SecondsTimer.Stop()
CheckPoints()
seconds = 40
End If
End Sub
The ShowTargets
method that is called in TargetsTimer_Tick
, randomly displays a different target at the specified interval.
Private Sub ShowTarget()
Dim rN As Integer = RandomTarget.Next(1, 5)
If (rN <> RandomNumber) Then
RandomNumber = rN
Select Case RandomNumber
Case 1
Target_1.PlayStoryboard()
Case 2
Target_2.PlayStoryboard()
Case 3
Target_3.PlayStoryboard()
Case 4
Target_4.PlayStoryboard()
End Select
Else
ShowTarget()
End If
End Sub
The CheckPoints
method that is called in SecondsTimer_Tick
checks whether the user gained enough points to proceed to the next level once time is up.
Private Sub CheckPoints()
If (Level = 1) And (TotalPoints >= 450) Then
Level_2_Grid.Visibility = Windows.Visibility.Visible
DisableSomeControls()
ElseIf (Level = 1) And (TotalPoints < 450) Then
LevelFailedGrid.Visibility = Windows.Visibility.Visible
DisableSomeControls()
End If
If (Level = 2) And (TotalPoints >= 550) Then
Level_3_Grid.Visibility = Windows.Visibility.Visible
DisableSomeControls()
ElseIf (Level = 2) And (TotalPoints < 550) Then
LevelFailedGrid.Visibility = Windows.Visibility.Visible
DisableSomeControls()
End If
If (Level = 3) And (TotalPoints >= 600) Then
FinalSevens += SevenPoints
FinalEights += EightPoints
FinalNines += NinePoints
FinalTens += TenPoints
ActualTotalPoints += TotalPoints
ChampGrid.Visibility = Windows.Visibility.Visible
TotalScoreTxtBlck.Text = ActualTotalPoints.ToString()
TotalSevensTxtBlck.Text = FinalSevens.ToString()
TotalEightsTxtBlck.Text = FinalEights.ToString()
TotalNinesTxtBlck.Text = FinalNines.ToString()
TotalTensTxtBlck.Text = FinalTens.ToString()
DisableSomeControls()
ElseIf (Level = 3) And (TotalPoints < 600) Then
Level3FailedGrid.Visibility = Windows.Visibility.Visible
DisableSomeControls()
End If
End Sub
When you're frantically tapping on your left mouse button to get off a shot, the first method that is called is the JungleCanvas
MouseLeftButtonDown
event handler:
Private Sub JungleCanvas_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As MouseButtonEventArgs) _
Handles JungleCanvas.MouseLeftButtonDown
If (ammo > 0) Then
Gunshot.Play()
Dim i As Integer = RoundsStack.Children.Count - 1
RoundsStack.Children.RemoveAt(i)
ammo -= 1
Else
Target_1.IsEnabled = False
Target_2.IsEnabled = False
Target_3.IsEnabled = False
Target_4.IsEnabled = False
DryFire.Play()
End If
End Sub
When you frantically tap your right mouse button to pop out the spent clip and load a fresh magazine, the JungleCanvas
MouseRightButtonDown
event handler is called:
Private Sub JungleCanvas_MouseRightButtonDown(ByVal sender As Object, _
ByVal e As MouseButtonEventArgs) _
Handles JungleCanvas.MouseRightButtonDown
If (ammo = 0) Then
If IsMagEjected = False Then
EjectMag.Play()
IsMagEjected = True
Else
PopInClip.Play()
IsMagEjected = False
ammo = MagCapacity
Dim i As Integer = ammo
Do While i > 0
Dim round As New GlockRound()
RoundsStack.Children.Add(round)
i -= 1
Loop
Target_1.IsEnabled = True
Target_2.IsEnabled = True
Target_3.IsEnabled = True
Target_4.IsEnabled = True
End If
Else
Exit Sub
End If
Hoping that you're not such a bad shot, and you manage to hit the targets, either one of the following event handlers is called:
Private Sub Target_1_MouseLeftButtonDown1(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles Target_1.MouseLeftButtonDown
UpdatePoints(Target_1.Points)
End Sub
Private Sub Target_2_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles Target_2.MouseLeftButtonDown
UpdatePoints(Target_2.Points)
End Sub
Private Sub Target_3_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles Target_3.MouseLeftButtonDown
UpdatePoints(Target_3.Points)
End Sub
Private Sub Target_4_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles Target_4.MouseLeftButtonDown
UpdatePoints(Target_4.Points)
End Sub
The UpdatePoints
method does the following:
Private Sub UpdatePoints(ByVal points As Integer)
If (ammo > 0) Then
Select Case points
Case 7
SevenPoints += 1
TotalPoints += 7
SevensTxtBlock.Text = SevenPoints.ToString()
TotalPointsTxtBlck.Text = TotalPoints.ToString()
Case 8
EightPoints += 1
TotalPoints += 8
EightsTxtBlock.Text = EightPoints.ToString()
TotalPointsTxtBlck.Text = TotalPoints.ToString()
Case 9
NinePoints += 1
TotalPoints += 9
NinesTxtBlock.Text = NinePoints.ToString()
TotalPointsTxtBlck.Text = TotalPoints.ToString()
Case 10
TenPoints += 1
TotalPoints += 10
TensTxtBlock.Text = TenPoints.ToString()
TotalPointsTxtBlck.Text = TotalPoints.ToString()
End Select
End If
End Sub
If you managed to get enough points to proceed to Level 2, and you click on the button to start that level, then the following method is called:
Private Sub StartLevel2Btn_Click(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) _
Handles StartLevel2Btn.Click
Level_2_Grid.Visibility = Windows.Visibility.Hidden
MagCapacity = 17
FinalSevens = SevenPoints
FinalEights = EightPoints
FinalNines = NinePoints
FinalTens = TenPoints
ActualTotalPoints = TotalPoints
StartNewLevel()
ClearTargetDents()
LevelTxtBlock.Text = "2"
Level = 2
End Sub
The ClearTargetDents
method provides you with clean targets for your next challenge:
Private Sub ClearTargetDents()
For Each el As UIElement In Target_1.TargetCanvas.Children
If TypeOf (el) Is Dent Then
el.Visibility = Windows.Visibility.Collapsed
End If
Next
For Each el As UIElement In Target_2.TargetCanvas.Children
If TypeOf (el) Is Dent Then
el.Visibility = Windows.Visibility.Collapsed
End If
Next
For Each el As UIElement In Target_3.TargetCanvas.Children
If TypeOf (el) Is Dent Then
el.Visibility = Windows.Visibility.Collapsed
End If
Next
For Each el As UIElement In Target_4.TargetCanvas.Children
If TypeOf (el) Is Dent Then
el.Visibility = Windows.Visibility.Collapsed
End If
Next
End Sub
Conclusion
I hope you enjoyed reading the article and that you picked up something useful. Targets doesn't have a high score feature but if you're into that sort of thing, you can go ahead and add it. You could also try adding some extra levels, after all I've already done the groundwork. Cheers!
History
- 13th April, 2011: Initial post