Screenshot_1.png

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 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,

Screenshot_2.png

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,

Screenshot_3.png

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 Targets are placed between the two Image controls.

Screenshot_4.png

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 you have the challenging task of showering with virtual lead are Target UserControls. 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.

Screenshot_5.png

Unchecking the checkbox reveals the visual elements,

Screenshot_6.png

The numbered regions are ViewBoxes, containing several Path objects, while the whitish area is just a Path object,

Screenshot_7.png

Target contains a Storyboard named TargetStoryboard that causes the apparent popping-up and down of the target in the game.

Storyboard.gif

Screenshot_11.png

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.

Screenshot_8.png

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,

Screenshot_9.png

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 Grids containing several elements. The following image shows Level_1_Grid and LevelFailedGrid.

Screenshot_10.png

The Code

Target

In the code for the Target UserControl, there are five event handlers for the MouseLeftButtonDown events of the four Viewboxes 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 Viewboxes 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.

Screenshot_12.png

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

        ' Remove any visible rounds, if any.
        If (RoundsStack.Children.Count > 0) Then
            RoundsStack.Children.Clear()
        End If

        ammo = MagCapacity
        ' Show rounds.
        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.

    ' TargetsTimer Tick event handler.
    Private Sub TargetsTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
        ShowTarget()
    End Sub

    ' SecondsTimer Tick event handler.
    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
            ' Recall method to ensure a new target
            ' is shown.
            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.

    ' Check points gained when 40secs are over. 
    Private Sub CheckPoints()
        ' Check points gained in Level 1.
        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
        ' Check points gained in Level 2.
        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
        ' Check points gained in Level 3.
        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
            ' Disable targets.
            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
                ' Show Ammo.
                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()
        ' Clear/Hide dents from Target_1.
        For Each el As UIElement In Target_1.TargetCanvas.Children
            If TypeOf (el) Is Dent Then
                el.Visibility = Windows.Visibility.Collapsed
            End If
        Next
        ' Clear dents from Target_2.
        For Each el As UIElement In Target_2.TargetCanvas.Children
            If TypeOf (el) Is Dent Then
                el.Visibility = Windows.Visibility.Collapsed
            End If
        Next
        ' Clear dents from Target_3.
        For Each el As UIElement In Target_3.TargetCanvas.Children
            If TypeOf (el) Is Dent Then
                el.Visibility = Windows.Visibility.Collapsed
            End If
        Next
        ' Clear dents from Target_4.
        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 Apr, 2011: Initial post

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"