目标






4.94/5 (32投票s)
一个基于 WPF 的射击游戏
 
 
引言
我非常喜欢第一人称射击游戏,而Targets就是我尝试用WPF创作一款具有这类游戏感觉的作品。Targets算不上Ghost Recon或Call of Duty,但它在2D WPF游戏领域也算尽了一份力。它的情节很简单。没有让你用各种武器杀死多个敌人的任务。在Targets中,你只需在限定时间内射击多个目标(这就是它名字的由来),获得一定分数,即可进入下一关。丛林是你的“战场”,你的“武器”是一把可靠的Glock 17。
只有三关,因为我确信你有更重要的事情要做,而通过这些关卡则取决于你移动鼠标的速度和点击鼠标按钮的速度,因为那些目标弹跳得非常快。
要求
要打开上面下载链接提供的解决方案,您需要以下任一条件:
- Visual Studio 2010
- Expression Blend
注意:如果您使用的是Visual Studio的Express版本,请确保您使用Visual Basic Express打开解决方案。
目标
工作原理
在玩Targets时,两个主要操作是射击和重新装弹。
- 射击:按鼠标左键射击。在第1关,每弹匣有10发子弹,第2关有17发,第3关有19发。
- 重新装弹:要重新装弹,您需要快速双击鼠标右键。第一次点击“弹出”用完的弹匣,第二次点击“装入”新弹匣。快速双击会有帮助。只有当弹匣中的子弹全部用完后才能重新装弹。
您的目标是在40秒内达到每关的特定分数
- 第1关:在40秒内获得450+分以进入下一关
- 第2关:在40秒内获得550+分以进入下一关
- 第3关:在40秒内获得600+分,即可被宣布为Targets的冠军
正如您所见,逻辑很简单,但目标的出现和消失速度非常快,这使得这种简单性难以维持。
设计和布局
我设计了Targets的大部分元素在Expression Design中,并在Expression Blend中添加/设计了一些额外的元素。下图显示了一些重要的元素

JungleCanvas包含两个Image控件。第一个Image控件JungleImage包含一幅完整的丛林图像,第二个Image控件OverlayImage包含一幅从JungleImage图像中剪切出的部分图像。下图显示了叠加图像在原始图像上的效果

叠加图像只是为了制造一些目标从灌木丛和树枝后面(更准确地说,是从地面下方和灌木丛及树枝后面)出现的错觉。Targets放置在两个Image控件之间。

游戏中的CrossHairs只不过是一个包含多个Path对象的ViewBox。其IsHitTestVisible属性设置为false。
目标
负责用虚拟铅弹“扫射”的目标是Target UserControl。如果您在Expression Blend中打开Target.xaml,您将看不到游戏中的元素。这是因为LayoutRoot的ClipToBounds属性设置为true。

取消选中checkbox会显示视觉元素

数字区域是ViewBox,包含多个Path对象,而白色区域只是一个Path对象

Target包含一个名为TargetStoryboard的Storyboard,它会导致目标在游戏中出现和消失。


如果您愿意,可以根据自己的喜好调整Storyboard。
注意:TargetStoryboard的RepeatBehavior设置为1x。
凹痕
当您射击目标时出现的凹痕是Dent UserControl的功劳。您在目标上看到的凹痕由一个具有径向渐变的椭圆形Path对象组成。下图是Dent UserControl的放大视图。

GlockRound
您在窗口顶部看到的弹药是GlockRound UserControl的功劳。下图显示了该UserControl的放大视图

要通过游戏,您将与几个对话框进行交互,这些对话框允许您进入下一关或重新开始特定关卡。这些对话框只是包含多个元素的Grid。下图显示了Level_1_Grid和LevelFailedGrid。

代码
目标
在Target UserControl的代码中,有五个事件处理程序用于构成该控件视觉元素的四个Viewbox和Path的MouseLeftButtonDown事件。
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
变量Points的值在Viewbox的事件处理程序中设置,并且所有事件处理程序都调用了HitTarget方法。
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
在HitTarget方法中,在用户点击目标的位置将一个Dent UserControl添加到TargetCanvas。
Target中您应该注意的另一个方法是PlayStoryboard。
Public Sub PlayStoryboard()
    Dim targeter As Storyboard
    targeter = CType(Me.Resources("TargetStoryboard"), Storyboard)
    targeter.Begin(Me)
End Sub
MainWindow
在MainWindow的Loaded事件处理程序中,我们执行以下操作
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
在这里,我们为几个SoundPlayer对象加载声音文件。声音文件是项目资源,您可以在Visual Studio中点击“Resources”选项卡查看项目属性窗口。

提供逼真音效的WAV文件来自www.soundbible.com。
一旦您点击“开始”按钮开始第1关,就会调用以下方法
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
StartNewLevel按钮执行以下操作
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
由于我们在上面的方法中调用了DispatcherTimer对象的Start方法,因此它们的Tick事件处理程序会在MainWindow的Loaded事件中指定的间隔被调用。
' 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
在TargetsTimer_Tick中调用的ShowTargets方法会以指定的时间间隔随机显示不同的目标。
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
在SecondsTimer_Tick中调用的CheckPoints方法会在时间结束后检查用户是否获得了足够的积分以进入下一关。
' 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
当您疯狂地点击鼠标左键进行射击时,第一个被调用的方法是JungleCanvas的MouseLeftButtonDown事件处理程序
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
当您疯狂地点击鼠标右键以弹出用完的弹匣并装入新弹匣时,会调用JungleCanvas的MouseRightButtonDown事件处理程序
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
希望您射击技术不错,并且成功击中了目标,此时会调用以下任一事件处理程序
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
UpdatePoints方法执行以下操作
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
如果您获得了足够的积分进入第2关,并且点击了开始该关卡的按钮,那么就会调用以下方法
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
ClearTargetDents方法为您提供干净的目标,以应对下一个挑战
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
结论
我希望您喜欢阅读这篇文章,并从中获得了一些有用的东西。Targets没有最高分功能,但如果您喜欢这类东西,可以自己添加。您也可以尝试添加一些额外的关卡,毕竟我已经打好了基础。祝您愉快!
历史
- 2011年4月13日:初始发布


