Почему моя игра VB.NET Snake зависает, когда я удерживаю клавишу?

Я пытаюсь сделать классическую игру Snake в VB.NET, но если я удерживаю клавишу (любую клавишу) во время игры, через несколько секунд игра зависает, пока я не отпущу клавишу. Я пробовал много, чтобы исправить это, но ничего не работает, может быть, потому что я не понимаю проблему.

Я предполагаю, что когда я удерживаю клавишу, вызывается функция 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

person SiliconCelery    schedule 24.12.2011    source источник
comment
Это не помешает системе постоянно вызывать это событие.   -  person UnhandledExcepSean    schedule 24.12.2011


Ответы (3)


Я предполагаю, что когда я удерживаю клавишу, вызывается функция Form1_KeyDown, и когда через несколько секунд клавиша переходит в режим «Меня удерживают», эта функция вызывается постоянно, поэтому таймеры не работают. нет возможности обновить. Но, как я уже сказал, я, вероятно, ошибаюсь.

На самом деле, вы правы.

В Windows вы получите сообщение WM_KEYDOWN, как только будет нажата клавиша, а затем, через определенный интервал, вы получите множество сообщений WM_KEYDOWN с другим определенным интервалом между ними.

Вы можете найти эти интервалы, если зайдете в Панель управления - Клавиатура.

Самый простой способ исправить это — добавить вызов DoEvents в конец обработчика ключей.

Попробуйте полностью удалить обработчик нажатия клавиши. Вместо этого нарисуйте nextDirection в начале tmrMove_Tick, изучив Keyboard.IsKeyDown.

Попробуйте полностью удалить обработчик нажатия клавиш. Вместо этого нарисуйте nextDirection в начале tmrMove_Tick, изучив GetAsyncKeyState, который вы можете объявить следующим образом:

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
person GSerg    schedule 24.12.2011
comment
Вы правы, если я изменю задержку до тех пор, пока Windows не посчитает ее задержанной, Snake потребуется больше времени, чтобы заморозить. Так что спасибо, теперь я уверен в проблеме. - person SiliconCelery; 24.12.2011
comment
Я попытался добавить Application.DoEvents() в конец обработчика событий, а затем в начало, но ничего не получилось. - person SiliconCelery; 24.12.2011
comment
Нет метода/события/Idon'tknowthename Keyboard.IsKeyDown. Также нет System.Windows.Input. Я попробовал Imports System.Windows.Input, но это тоже не сработало. Я довольно новичок в VB, как вы, наверное, поняли, поэтому я как бы вмешиваюсь в вещи, которых не понимаю, когда дело доходит до импорта вещей. Но если бы существовал простой способ получить состояние ключа, это было бы идеально. - person SiliconCelery; 24.12.2011
comment
@SiliconCelery В ответе есть ссылка, который приведет вас на страницу документации, где указано, что он находится в PresentationCore.dll. Вы должны добавить в проект две ссылки: System.Windows.Presentation (вы делаете сами) и WindowsBase (которую вам говорит компилятор). В любом случае, вам не нужно этого делать, потому что этот материал предназначен для WPF и не работает в приложении Windows Forms. Смотрите другое редактирование. - person GSerg; 24.12.2011
comment
Последний комментарий вылетел у меня из головы. А еще я не понимаю константы ВК_, что это такое, что они делают. Я пытался поместить весь код изменения направления в функцию tmrMove_Tick и использовал, например, If GetAsyncKeyState(upKey) Then, но проблема с зависанием игры все еще оставалась. =[ edit: Dim upKey As Keys = Keys.Up также присутствует в коде. - person SiliconCelery; 25.12.2011
comment
Константы @SiliconCelery VK_ — это просто целочисленные константы. Так получилось, что их значения равны соответствующим Keys членам, поэтому вы можете их использовать. Не делайте просто If GetAsyncKeyState(upKey) Then, вместо этого используйте функцию (см. другое редактирование). Что касается зависания, вы полностью удалили обработчик нажатия клавиш? - person GSerg; 25.12.2011

Вместо этого я бы рекомендовал попробовать событие keyup. Он не будет спамить, как нажатия клавиш или события нажатия клавиш.

person UnhandledExcepSean    schedule 24.12.2011
comment
Боюсь, все та же проблема. - person SiliconCelery; 24.12.2011
comment
вы перестали использовать событие keydown? Keyup срабатывает только один раз при нажатии клавиши - person UnhandledExcepSean; 24.12.2011
comment
Я изменил все keyDown на keyUp, и хотя движение казалось более плавным, оно все еще зависало после нескольких секунд удержания клавиши. - person SiliconCelery; 24.12.2011

Вы правы в том, что проблема заключается в повторении ключа. В прошлом я использовал переменную для хранения предыдущего состояния ключа и выхода из события нажатия клавиши, если оно такое же. Я сбрасываю это с помощью таймера, который должен дать вам достаточную задержку.

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

oldKeyData = e.KeyCode
tmrKeyReset.Enabled = True

Изменить: ответ @SpectralGhosts будет работать, если вы хотите перемещаться с помощью отдельных нажатий клавиш.

person Mark Hall    schedule 24.12.2011
comment
Простите за медлительность, но что делает таймер? Для этого нужен код? - person SiliconCelery; 24.12.2011
comment
Таймер сбрасывает oldKeyData, чтобы снова обработать KeyCode. Это замедляет количество KeyDown, которые вы обрабатываете, не влияя на глобальные настройки компьютера. - person Mark Hall; 24.12.2011
comment
Кажется, это не работает. Разве проблема не в том, что, хотя эта идея выходит из функции, функция все еще вызывается сразу после этого, независимо от того, что я еще делаю, и не дает таймеру возможности что-либо сделать? - person SiliconCelery; 24.12.2011
comment
Это объясняет, почему у меня был Application.DoEvents между e.Handled = true и Exit Sub. Это не остановит вызов функции, просто обработав вашу логику - person Mark Hall; 24.12.2011
comment
Он по-прежнему не работает, даже с включенным Application.DoEvents. Я начинаю думать, что с моим кодом что-то еще не так, потому что кажется, что многие решения должны работать. - person SiliconCelery; 24.12.2011
comment
Вы пробовали ответ @GSerg об опросе состояния ключа в вашем таймере? Это может быть ваш ответ. - person Mark Hall; 24.12.2011