【问题标题】:Why does my VB.NET Snake game freeze when I hold a key down?当我按住一个键时,为什么我的 VB.NET Snake 游戏会卡住?
【发布时间】:2011-12-24 18:11:47
【问题描述】:

我正在尝试在 VB.NET 中制作经典的 Snake 游戏,但如果我在游戏过程中按住一个键(任何键),几秒钟后游戏会冻结,直到我松开该键。我已经尝试了很多来解决这个问题,但没有任何效果,可能是因为我不明白这个问题。

我假设当我按住一个键时,Form1_KeyDown 函数被调用,几秒钟后,当键进入“我被按住”模式时,该函数被不断调用,所以计时器没有机会更新。但就像我说的,我可能错了。

任何帮助都将不胜感激,我已经为此苦苦挣扎了一段时间。我认为这是所有必要的代码,如果不是,请告诉我。

按键事件代码:

 Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown

    ' Sorts out all the key presses: movement, resetting, pausing

    ' Change direction, unless the player tries to travel backwards into themself
    Select Case e.KeyCode
        Case upKey
            If previousDirection <> "D" Then
                nextDirection = "U"
            End If
        Case leftKey
            If previousDirection <> "R" Then
                nextDirection = "L"
            End If
        Case rightKey
            If previousDirection <> "L" Then
                nextDirection = "R"
            End If
        Case downKey
            If previousDirection <> "U" Then
                nextDirection = "D"
            End If
        Case resetKey
            resetGame()
        Case pauseKey
            paused = Not paused
            If paused Then
                lblPaused.Visible = True
                tmrTime.Stop()
                tmrFruit.Stop()
                tmrMove.Stop()
            Else
                lblPaused.Visible = False
                tmrTime.Start()
                tmrFruit.Start()
                tmrMove.Start()
            End If
    End Select

End Sub

更新/移动蛇的计时器代码(我知道这确实效率低下):

 Private Sub tmrMove_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrMove.Tick

    ' Adds a new head in direction of travel, and removes the tail, giving the illusion of snake movement

    Dim head As Object = bodyParts(bodyParts.Count - 1)
    Dim tail As Object = bodyParts(0)
    Dim newHead As Object

    head.Text = ""

    ' Add new head
    Select Case nextDirection

        Case "R"
            ' If snake goes out of bounds
            If head.Tag(0) + 1 >= numberOfColumns Then
                newHead = grid(0, head.Tag(1))
                If newHead.BackColor = snakeColor Then
                    killSnake()
                End If
            Else
                ' If snake overlaps itself
                If bodyParts.Contains(grid(head.Tag(0) + 1, head.Tag(1))) Then
                    killSnake()
                    Exit Sub
                Else
                    ' If snake is fine
                    newHead = grid(head.Tag(0) + 1, head.Tag(1))
                End If
            End If

            ' If fruit taken
            If newHead.BackColor = fruitColor Then
                eatFruit(newHead, tail)
            End If

        Case "L"
            If head.Tag(0) - 1 < 0 Then
                newHead = grid(numberOfColumns - 1, head.Tag(1))
                If newHead.BackColor = snakeColor Then
                    killSnake()
                End If
            Else
                If bodyParts.Contains(grid(head.Tag(0) - 1, head.Tag(1))) Then
                    killSnake()
                    Exit Sub
                Else
                    newHead = grid(head.Tag(0) - 1, head.Tag(1))
                End If
            End If

            If newHead.BackColor = fruitColor Then
                eatFruit(newHead, tail)
            End If

        Case "U"
            If head.Tag(1) - 1 < 0 Then
                newHead = grid(head.Tag(0), numberOfRows - 1)
                If newHead.BackColor = snakeColor Then
                    killSnake()
                End If
            Else
                If bodyParts.Contains(grid(head.Tag(0), head.Tag(1) - 1)) Then
                    killSnake()
                    Exit Sub
                Else
                    newHead = grid(head.Tag(0), head.Tag(1) - 1)
                End If
            End If

            If newHead.BackColor = fruitColor Then
                eatFruit(newHead, tail)
            End If

        Case "D"
            If head.Tag(1) + 1 >= numberOfRows Then
                newHead = grid(head.Tag(0), 0)
            Else
                If bodyParts.Contains(grid(head.Tag(0), head.Tag(1) + 1)) Then
                    killSnake()
                    Exit Sub
                Else
                    newHead = grid(head.Tag(0), head.Tag(1) + 1)
                End If
            End If

            If newHead.BackColor = fruitColor Then
                eatFruit(newHead, tail)
            End If

        Case Else
            newHead = grid(head.Tag(0), head.Tag(1))

    End Select

    bodyParts.Add(newHead)
    newHead.BackColor = snakeColor
    newHead.Font = headFont
    newHead.Text = headText

    ' Remove tail
    tail.BackColor = gridColor
    bodyParts.RemoveAt(0)

    previousDirection = nextDirection

End Sub

【问题讨论】:

  • 这不会阻止系统连续引发该事件。

标签: vb.net timer freeze keydown


【解决方案1】:

我假设当我按住一个键时,Form1_KeyDown 函数被调用,几秒钟后,当键进入“我被按住”模式时,该函数被不断调用,所以计时器没有机会更新。但就像我说的,我可能错了。

其实你是对的。

在 Windows 中,您将在按下键后立即收到 WM_KEYDOWN 消息,然后在一定间隔后,您将收到大量 WM_KEYDOWN 消息,它们之间还有一定的间隔。

如果您进入控制面板 - 键盘,您可以找到这些间隔。

修复它的最简单方法是在密钥处理程序的末尾添加对DoEvents 的调用。

尝试完全删除 keydown 处理程序。相反,通过检查Keyboard.IsKeyDown,在tmrMove_Tick 的开头找出nextDirection

尝试完全删除 keydown 处理程序。相反,通过检查GetAsyncKeyState,在tmrMove_Tick 的开头找到nextDirection,您可以声明如下:

Private Declare Function GetAsyncKeyState Lib "user32" Alias "GetAsyncKeyState" (ByVal vKey As Keys) As Short

Private Shared Function IsKeyDown(ByVal Key As Keys) As Boolean
    Return (GetAsyncKeyState(Key) And &H8000S) = &H8000S
End Function

【讨论】:

  • 你说得对,如果我将延迟更改为 Windows 将其视为已暂停,则 Snake 冻结所需的时间更长。所以谢谢你,我现在确定问题了。
  • 我尝试将 Application.DoEvents() 添加到事件处理程序的末尾,然后启动,但都没有成功。
  • 没有 Keyboard.IsKeyDown 方法/事件/Idon'tknowthename。也没有 System.Windows.Input。我尝试了 Imports System.Windows.Input,但这也不起作用。我对 VB 很陌生,你可能会收集到,所以在导入东西时我有点插手我不明白的东西。但是,如果有一种简单的方法可以获取密钥的状态,那将是完美的。
  • @SiliconCelery 有一个link in the answer,它会将您带到文档页面,告诉您它位于 PresentationCore.dll 中。您必须添加对项目的两个引用,System.Windows.Presentation(您自己做)和WindowsBase(编译器告诉您做)。无论如何,您不必这样做,因为这些东西适用于 WPF,并且不适用于 Windows 窗体应用程序。查看另一个编辑。
  • 最后一条评论完全超出了我的想象。而且我也不明白 VK_ 常量,它们是什么,它们做什么。我尝试将所有的换向代码放在tmrMove_Tick函数中,例如使用“If GetAsyncKeyState(upKey) Then”,但仍然存在游戏卡顿的问题。 =[ 编辑:“Dim upKey As Keys = Keys.Up”也在代码中。
【解决方案2】:

我建议尝试使用 keyup 事件。它不会像 keypress 或 keydown 事件那样发送垃圾邮件

【讨论】:

  • 恐怕还是同样的问题。
  • 您是否停止使用 keydown 事件? Keyup 每次按键仅触发一次
  • 我把所有的keyDown都改成了keyUp,虽然动作看起来更流畅了,但是按住一个键几秒后还是卡住了。
【解决方案3】:

您是正确的,问题在于密钥重复。我过去曾使用一个变量来保存先前的键状态并在相同的情况下退出按键事件。我正在用一个计时器重置它,它应该能让你有足够的延迟。

If oldKeyData = e.KeyCode Then
    e.Handled = True
    Exit Sub
End If

oldKeyData = e.KeyCode
tmrKeyReset.Enabled = True

编辑:@SpectralGhosts 答案将起作用,如果您想通过单独的按键移动。

【讨论】:

  • 请原谅我的慢,但是计时器是做什么的呢?那需要代码吗?
  • 计时器重置 oldKeyData 以便再次处理 KeyCode。它会减慢您正在处理的 KeyDown 数量,而不会影响计算机的全局设置。
  • 它似乎不起作用。问题不就是这个idea虽然退出了函数,但是之后还是直接调用了函数,不管我做什么,都不给定时器做任何事情的机会?
  • 这解释了为什么我在 e.Handled = true 和 Exit Sub 之间有一个 Application.DoEvents。它不会阻止函数被调用,只是从它处理你的逻辑开始
  • 它仍然无法工作,即使有 Application.DoEvents。我开始认为我的代码还有其他问题,因为很多解决方案看起来应该可以工作。
猜你喜欢
  • 2018-08-06
  • 1970-01-01
  • 2020-06-14
  • 1970-01-01
  • 2021-12-31
  • 2019-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多