在结束子程序之前有一个子程序等待按钮输入

时间:2013-05-22 12:59:47

标签: vb.net winforms

我正在使用do while循环,在每次循环之后,需要询问用户他/她下一步要做什么。该节目是一个口袋妖怪风格的战斗RPG,除了让口袋妖怪打架,你自己打怪物。

我正在寻找的是口袋妖怪/最终幻想的战斗风格。

这是我处理战斗的潜艇:

  Private Sub Battle(ByVal Name As String, ByVal life As Integer, ByVal damage As Integer)

    lbl_MonsterName.Text = Name
    mobCurrHealth = mobMaxHealth
    lbl_MonsterHP.Text = mobCurrHealth & " / " & mobMaxHealth
    bar_monsterHP.Value = mobCurrHealth
    bar_monsterHP.Maximum = mobMaxHealth


    txtbx_Action.AppendText(Environment.NewLine & "You find a " & Name)
    lbl_MonsterName.Visible = True
    lbl_MonsterHP.Visible = True
    bar_monsterHP.Visible = True

    wait(2000)

    txtbx_Action.AppendText(Environment.NewLine & "What would you like to do?")
    btn_Attack.Visible = True
    btn_Run.Visible = True

'Here I want the program to wait for button input, but i still have to find a way to do that        


End Sub

在“您想做什么”之后,将显示2(3)个按钮:

  • 攻击
  • 运行
  • (库存)

然而,正如你所看到的那样(而且我确实理解了什么是错的,我只是不知道如何解决它)我的战斗子接头在按钮可见后结束。

我希望sub等到我按下这些按钮之一,然后按相应的行为。

我该怎么做?


编辑:

这是我使用的代码,其中称为“Battle”:

   Private Sub btn_Adventure_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_Adventure.Click

    btn_Adventure.Visible = False

    txtbx_Action.Visible = True

    Dim rng As New Random
    Dim MobGen As Integer = rng.Next(1, 4)

    Select Case MobGen

        Case 1
            mobName = "Rat"
            mobMaxHealth = 5
            mobDamage = 2
        Case 2
            mobName = "Bat"
            mobMaxHealth = 7
            mobDamage = 3
        Case 3
            mobName = "Snake"
            mobMaxHealth = 9
            mobDamage = 4
        Case 4
            mobName = "Wolf"
            mobMaxHealth = 11
            mobDamage = 5

    End Select

    txtbx_Action.AppendText(Environment.NewLine & "You run around the forest trying to find something to kill")
    wait(1500)
    Battle(mobName, mobMaxHealth, mobDamage)


    btn_Adventure.Visible = True

End Sub

另外,如果您认为我现在处理程序的方式非常错误,请告诉我如何改进它!这是一个尝试更好地学习vb.net语言的爱好项目,所以任何建议或建设性的批评都非常赞赏!

6 个答案:

答案 0 :(得分:3)

更新:使用await

我正坐在TechEd研讨会上,该研讨会提出了一种可能更好的方法来处理这种情况:async。请参阅此处的演示:Channel 9: Tip 3: Wrap events up in Task-returning APIs and await them

基本上,您可以将事件包装在await中,然后让您的UI线程等待事件完成(不会导致它挂起)。这是一个很好的故事板方式,无需跟踪复杂的状态。它不适用于所有情况,但如果它可能是传统的基于状态的解决方案的一个明显更简单的替代方案。


基于状态的方法

您正在考虑这种方式非常直观,但并不是很正确。一般而言,编程可以被认为是在一系列不同状态之间的转换。这基本上就是你在显示和隐藏各种控件时所做的事情 - 隐式地从一个状态转换到下一个状态。

我认为将隐式状态更改显式化是最有帮助的。您可以定义游戏可以“休息”的不同状态,然后根据用户输入从一种状态转换到下一种状态。

因此,例如,您可以定义一个名为CurrentState的类级属性和一个具有您可能希望表示的各种状态的Enumeration。打开和关闭控件可能是由于在游戏状态发生变化时触发的事件而发生的。

使用CurrentState属性,您可以检测游戏所处的状态,并对同一输入做出不同的响应(如果这是一个问题)。显然我无法访问您的整个代码,但希望您能找到以下代码鼓舞人心。要意识到你需要使用你的创造性判断:你不一定要复制粘贴下面的代码并使你的游戏适合它。

列举你的游戏状态。在程序集级别(任何类之外)定义它:

Public Enum GameState
    Adventure
    Battle
    BattleCompleted
End Enum

您的游戏类(使用CurrentState属性,StateChanged事件和存根方法显示使用State的一种可能方式):

Public Class Game

    ' This defines your custom event which will be
    ' raised when the CurrentState property changes
    Public Event GameStateChanged As EventHandler(Of GameStateChangedEventArgs)

    Private _currentState As GameState = GameState.Adventure
    Private Property CurrentState() As GameState
        Get
            Return _currentState
        End Get
        Set(ByVal value As GameState)
            ' Don't raise GameStateChanged if setting the GameState to the same value
            If value = _currentState Then Exit Property

            ' If your application code changes the CurrentState
            ' property, raise the GameStateChanged event
            Dim l_oldState = _currentState
            _currentState = value
            RaiseEvent GameStateChanged(Me, New GameStateChangedEventArgs(l_oldState, _currentState))
        End Set
    End Property

    ' This is the event handler for your GameStateChanged event.
    ' When the game state changes, you can respond by updating the UI.
    Public Sub Game_GameStateChanged(ByVal sender As Object, ByVal e As GameStateChangedEventArgs) Handles Me.GameStateChanged
        If e.ToState = GameState.Adventure Then
            ' Turn on adventure mode buttons, etc
        ElseIf e.FromState = GameState.Adventure Then
            ' Turn off adventure mode buttons, etc
        End If
        If e.ToState = GameState.Battle Then
            ' Turn on battle monster controls, etc
        ElseIf e.FromState = GameState.Battle Then
            ' Turn off monster controls, etc
        End If
        If e.ToState = GameState.BattleCompleted Then
            ' Turn on post-battle controls, etc
        ElseIf e.FromState = GameState.BattleCompleted Then
            ' Turn off post-battle controls, etc
        End If
    End Sub

    ' Now, all you need to do is set the state of your game...
    Private Sub Adventure()
        Me.CurrentState = GameState.Adventure

        Battle()
    End Sub

    Private Sub Battle()
        Me.CurrentState = GameState.Battle

        ' Do the battle

        PostBattle()
    End Sub

    Private Sub PostBattle()
        Me.CurrentState = GameState.BattleCompleted
    End Sub

End Class

StateChanged EventArgs类。这是完全可选的,但在处理状态转换时为您提供了一些额外的灵活性。

Public Class GameStateChangedEventArgs
    Inherits EventArgs

    Private _fromState As GameState
    Public ReadOnly Property FromState() As GameState
        Get
            Return _fromState
        End Get
    End Property

    Private _toState As GameState
    Public ReadOnly Property ToState() As GameState
        Get
            Return _toState
        End Get
    End Property

    Public Sub New(ByVal fromState As GameState, ByVal toState As GameState)
        _fromState = fromState
        _toState = toState
    End Sub

End Class

这绝不是完成程序设计的唯一方法,但在基于事件的设置中工作时,这是一个有用的架构。

答案 1 :(得分:3)

当您单击一个看起来正在寻找的按钮时会发生按钮事件。我实际上已经开始为自己的成长重写口袋妖怪,我使用了button_click事件。

Button_Click subs是单击按钮时运行的subs。当程序悬停时,单击等待输入和按钮,程序将跳转到与单击的按钮关联的子程序。

为了生成“button_click”子,您可以在查看表单时双击按钮以自动生成“Button_Click”子。还有其他方法,但我个人觉得它们使用起来比较棘手,而且更容易出错。

这里会发生什么,你的“战斗”子将结束,战斗将会建立。该计划将徘徊,等待一些事情发生。当您单击其中一个按钮时,程序将跳转到子按钮,无论单击任何按钮。在按下按钮之前,它不需要程序保持循环,并且您不必将任何代码添加到现有的战斗子(您可以让它结束)。

这是一个帮助您入门的小例子。填充button_click subs的代码是您根据自己的目的调整的,但您的设置应该与此非常相似。它的编写是Visual Basic 2010 Express。

Private Sub btn_Attack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_Attack.Click
    If mobCurrHealth > 0 And youCurrHealth > 0 Then
        'Both the monster and you are alive
        'Execute attack code
    End If
End Sub

Private Sub btn_Run_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_Run.Click
    If mobCurrHealth > 0 And youCurrHealth > 0 Then
        'Both the monster and you are alive
        'Execute run code
    End If
End Sub

重要的行是您可能自动生成的子声明行。如果您没有通过双击按钮自动生成子,请确保将“Handles”设置为要激活sub的按钮,然后单击“.Click”。按下按钮后,您想要发生什么,取决于您。上面的例子检查以确保你和怪物都还活着,但它可以是任何东西。

祝你的游戏开发好运!

答案 2 :(得分:2)

我在这个完整的例子中避免了绑定和其他事情,试图让它更容易理解并尽可能地保持它。

Public AllMobs As New List(Of Mob)
Public Player As Mob
Public CurrentMob As Mob
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    AllMobs.Add(New Mob() With {.Name = "rat", .Damage = 2, .MaxHealth = 5, .CurrentHealth = 5})
    AllMobs.Add(New Mob() With {.Name = "bat", .Damage = 3, .MaxHealth = 7, .CurrentHealth = 7})
    AllMobs.Add(New Mob() With {.Name = "wolf", .Damage = 5, .MaxHealth = 11, .CurrentHealth = 11})
    Player = New Mob() With {.Name = "Gutanoth", .Damage = 1, .MaxHealth = 100, .CurrentHealth = 100}
    GetNextFight()
End Sub
Private Sub GetNextFight()
    If CurrentMob Is Nothing Then
        CurrentMob = SpawnMob()
        txtbx_Action.AppendText("You continue into the forest" & vbNewLine)
        txtbx_Action.AppendText(CurrentMob.Name + " is here" & vbNewLine)
    End If
End Sub
Private Sub NextBattleAction()
   If Player.IsDead Then
        MessageBox.Show("You died. Lose!")
        Close()
    ElseIf CurrentMob.IsDead Then
        txtbx_Action.AppendText("---Victorious!" & vbNewLine)
        AllMobs.Remove(CurrentMob)
        CurrentMob = Nothing ' destroy mob object
    End If
    If AllMobs.Count = 0 Then
        MessageBox.Show("You've killed them all. Win!")
        Close()
    Else
        GetNextFight()
    End If
    If CurrentMob IsNot Nothing Then
        lbl_MonsterHP.Text = CurrentMob.CurrentHealth & " / " & CurrentMob.MaxHealth
    End If
End Sub
Private Function SpawnMob() As Mob
    If AllMobs.Count = 0 Then Return Nothing
    Dim rng As New Random
    Dim selectedIndex As Integer = rng.Next(AllMobs.Count)
    Return AllMobs(selectedIndex)
End Function
Private Sub btn_Attack_Click(sender As Object, e As EventArgs) Handles btn_Attack.Click
    CurrentMob.Attack(Player)
    txtbx_Action.AppendText(CurrentMob.Name & " attacks" & vbNewLine)
    Player.Attack(CurrentMob)
    txtbx_Action.AppendText("you attack " & vbNewLine)
    NextBattleAction()
End Sub
Private Sub btn_Run_Click(sender As Object, e As EventArgs) Handles btn_Run.Click
    txtbx_Action.AppendText("you run away" & vbNewLine)
    CurrentMob = Nothing ' make no current mob 
    GetNextFight()
End Sub

这应该放在单独的类文件Mob.vb

Public Class Mob
    Property Name() As String
    Property MaxHealth() As Integer
    Property CurrentHealth() As Integer
    Property Damage As Integer
    Public Sub Attack(targetMob As Mob)
        targetMob.CurrentHealth = targetMob.CurrentHealth - Damage
    End Sub
    Public Function IsDead() As Boolean
        If CurrentHealth <= 0 Then Return True Else Return False
    End Function
End Class

答案 3 :(得分:2)

你必须稍微改变一下你的方法。你应该考虑每个按钮做一个特定的工作,不应该等待任何人。

在你的btn_Adventure.Click上应该初始化战斗,而不是照顾它。

Private Sub btn_Adventure_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_Adventure.Click

    GetModToAttack()
    InitialiseUI() ' Show the btnAttack, btnRun, show the "What would you like to do text", ...

End Sub

现在UI将自行等待用户输入,当用户单击按钮时,您将处理操作

Private Sub btn_Attack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_Attack.Click

    ' Handle attack action
    ' Handle UI

End Sub

答案 4 :(得分:2)

您似乎对Windows GUI程序的真正运作方式缺乏基本的了解。当您包含一行代码时:

button.Visible = true;

你实际上并没有让按钮可见。将要发生的是Windows Forms框架将设置一个标志,指示该按钮应该是可见的,然后它将向按钮的消息队列发送消息,要求按钮被重新绘制。如果代码是从UI线程运行的,这似乎是这里的情况,它必须将控制权传递回操作系统才能接收和处理这些消息。

您的方法的主要缺陷是您尝试在单个功能中执行所有操作。实际上,您可能需要以不同方式构建整个程序。 Cyborgx37 在他的回答中有正确的基本想法,但也许你不太了解它。

基本思想是你的程序需要有各种各样的功能,负责处理整个游戏逻辑的某些单独块,以及一组变量和数据结构,用于定义游戏的当前状态。任何给定的时间。这些功能需要适当考虑GUI事件处理的工作方式。

让我们走一条概述。这不是确切的,但一般来说应该非常接近你想要做的事情。

当您开始游戏时,您将以适合游戏开始的任何方式初始化变量和数据结构(以下称为&#34;游戏状态&#34;)。这可能包括从磁盘,数据库加载信息,或通过代码动态创建信息。这将包括图形数据,声音数据等。

接下来,您将创建游戏的显示。请注意,创建显示和重新绘制显示是两回事。

&#34;创建显示&#34;意味着打开窗口(如果需要),添加任何UI控件,如您需要的按钮等等。

相比之下,&#34;重绘显示器&#34;是实际推动像素的过程。如果你的程序使用图形库调用来更新屏幕上的任何内容,你的代码将需要实现一个函数,当你的程序收到一个&#34; PAINT&#34;消息。

请注意,如果您严格按照Windows GUI应用程序进行游戏,则可能不需要明确创建&#34;重绘屏幕&#34;功能。通常,如果所有内容都是由UI控件绘制的,那么您就不需要了。

此时,您可能想要强制进行屏幕重绘事件。在Windows GUI应用程序中,这通常通过发送&#34; Invalidate&#34;消息到窗口。处理完毕后,将根据需要重新绘制所有内容。标准UI控件之类的东西通常会重新绘制,而不需要程序中的任何特殊操作。

现在可能需要某种用户输入才能继续进行,即使它只是点击&#34;开始游戏&#34;。如果您有一系列按钮,则每个按钮都应链接到一个事件处理函数,该函数包含特定于该特定选择的代码。该代码应根据需要更新游戏的变量和数据结构。

Cyborgx37 显示了一个示例,其中单个变量指定了游戏状态。在实践中,该变量将成为冰山一角,并且将涉及各种其他变量和数据结构。基本的游戏状态可以指示各种各样的事情......比如&#34;等待开始&#34;或&#34;用户按下ATTACK&#34;。其他变量可能表明哪个怪物受到攻击,玩家和怪物在哪个区域,玩家目前的健康状况等等。

所有基本信息汇集在一起​​描述游戏状态&#34;可能希望组成一个班级。这不应该包括应该单独维护的UI设置等细节。

如果用户输入意味着需要在屏幕上更改某些内容,则事件处理程序应更新游戏状态并删除/添加/更改所涉及的任何UI控件。您可能想要一个能够配置任何特定游戏状态所需的屏幕控制的功能。确保始终使用正确的事件处理函数配置这些按钮。

如果用户输入意味着在屏幕上动态绘制的内容需要更新,则应发送&#34; Invalidate&#34;消息到窗口(或特定控件)。

您的代码示例在几个地方调用了 wait()。这通常是错误的暂停方式,因为你会停止整个线程。然而,这并不意味着基本的基本想法是错误的。想要在两件事情之间有一定的时间,这是完全可以的。定时器进来的地方。

计时器允许您根据需要更新游戏状态以响应时间的推移。出于多种原因可能需要这样做,但对于游戏来说,最常见的是动画。例如,您可能有一个图像需要每秒动画5次。您将创建一个定时器设置为5hz,并在事件处理函数中,它将增加动画帧索引,然后发送&#34; Invalidate&#34;消息到窗口或控件与图像。

您可以将多个计时器设置为不同的频率,但最好尽可能组合。如果屏幕上有十几个不同的东西需要设置动画,请使用单个计时器,其中处理函数分别检查每个项目。

还有更多参与,但这些是我认为你需要走上正确轨道的基础。

答案 5 :(得分:0)

制作VarAVarB班级:

Private VarA, VarB As Integer ' Or whatever type

然后为循环的开始逻辑添加一个函数:

Private Sub NextIteration() ' Give this a better name, or even make it part of Update
    If VarA <= 0 OrElse VarB <= 0 Then
        ' Loop’s over; hide the buttons or something
    Else
        Update()
    End If
End Sub

然后单独处理所有操作按钮,执行操作,并在每个NextIteration()处理程序的末尾调用Click。请致电NextIteration()开始。