Flappy Bird in Excel VBA Part 6 - Detecting Player Input
This part of the tutorial describes how to respond to keys pressed by the player. You'll learn about the Application.OnKey method and the GetAsyncKeyState Windows API function.

Posted by Andrew Gould on 11 April 2014

You need a minimum screen resolution of about 700 pixels width to see our blogs. This is because they contain diagrams and tables which would not be viewable easily on a mobile phone or small laptop. Please use a larger tablet, notebook or desktop computer, or change your screen resolution settings.

Flappy Bird in Excel VBA - Detecting Player Input

Useful Links

Return to the Flappy Bird in Excel VBA Tutorial index.

Download Flappy Owl Pt6a - Using OnKey.

Download Flappy Owl Pt6b - GetAsyncKeyState.


In this part of the tutorial we'll look at how to detect when the player presses keys on the keyboard and perform the appropriate action when they do. You can download the finished workbook here.

Creating the Procedures to be Called on a Key Press

In our game we'd like the bird to move upwards when the player presses the up arrow key and for it to dive downwards when they press the down arrow key.

To start with we're going to stop the bird from bouncing when it hits the floor. Go to the modBirdCode module and remove these lines from the UpdateBird procedure:

'reverse the movement direction (makes the bird bounce)

BirdVerticalMovement = -8

Now declare two constants at the top of the module, just below Option Explicit:

Private Const FlapHeight As Integer = -8

Private Const DiveDepth As Integer = 8

We can modify these numbers later to tweak the way the game works but these values will do for now. Now add two simple subroutines to the bottom of the module:

Private Sub Flap()

BirdVerticalMovement = FlapHeight

End Sub

Private Sub Dive()

BirdVerticalMovement = DiveDepth

End Sub

Attaching Procedures to Key Presses with the OnKey Method

One simple way to create a keyboard shortcut to run a subroutine is to use the Application.OnKey method. Go back to the modGameCode module and add a new subroutine to the bottom which looks like this:

Private Sub SetGameKeys()

Application.OnKey "{UP}", "Flap"

Application.OnKey "{DOWN}", "Dive"

End Sub

The OnKey method accepts two arguments: the first is the key to which you're assigning a procedure; the second is the name of the procedure that you're assigning. Note that, even though we declared the Flap and Dive procedures as private, we can still specify their names here.

While we're here I'd also like to assign a key which the user can press to end the game. Add the following line to the SetGameKeys procedure:

Application.OnKey "{TAB}", "TerminateGame"

Normally you'd use the Escape key to end a game, but in this case we're going to use the Tab key instead. The reason is that pressing this key will end the game and also reset all of the keys back to their original functions. For the Escape key, this means that it will go back to being the key which interrupts execution of our code. The danger here is that if a user presses the Escape key twice in quick succession the first press will stop the game but the second press might interrupt any tidying up code that we run after that. One way to avoid this would be to make sure that resetting the Escape key is the last thing that happens but I prefer to avoid the risk. In fact, we can go a step further and make sure that the Escape key won't do anything at all while our code is running by adding this line to the same procedure:

Application.OnKey "{ESC}", ""

The empty string passed to the second parameter tells the Escape key to do nothing when it is pressed.

We'll also need a procedure which resets all of the keys that we've changed at the end. To do this you can use the OnKey method and simply omit the second argument. Add another subroutine which will do this. At the end you should have two subroutines which look like this:

Private Sub SetGameKeys()

Application.OnKey "{ESC}", ""

Application.OnKey "{UP}", "Flap"

Application.OnKey "{DOWN}", "Dive"

Application.OnKey "{TAB}", "TerminateGame"

End Sub

Private Sub ResetKeys()

Application.OnKey "{UP}"

Application.OnKey "{DOWN}"

Application.OnKey "{TAB}"

Application.OnKey "{ESC}"

End Sub

Note that the Escape key is the first one to be disabled and the last one to be re-enabled.

If you had a lot of keys to assign for your game it would probably be better to store them and the names of their associated subroutines in worksheet cells (or even a text file). You could then loop over the range of cells to assign each key to a procedure rather than writing an explicit instruction for each one. This is the technique I've used in the full version of the game.

Activating and Deactivating the Game Keys

Now we need to activate our keys when the game starts and deactivate them when the game ends. Add a line which calls the SetGameKeys procedure to the top of the InitialiseGame subroutine:

Public Sub InitialiseGame()

'Called once when game first starts

'Used to set starting parameters

'Begins the game timer


Now change the TerminateGame procedure so that it looks like this:

Public Sub TerminateGame()

'Called once when game ends

'Used to tidy up




End Sub

Before we test the game again we need to change the UpdateBird procedure too. Now that we have the ability to move up as well as down we need to ensure that we don't try to move up past the top of the worksheet. Go back to the modBirdCode module and replace the UpdateBird procedure with this version:

Public Sub UpdateBird()

Dim TargetRow As Integer

'remember the cell that the bird was in

'at the start of this procedure call

Set BirdPreviousCell = BirdCell

'calculate how many cells to fall and

'ensure this isn't faster than the dive speed

BirdVerticalMovement = WorksheetFunction.Min( _

BirdVerticalMovement + Gravity, _


'calculate the row number of the target cell

TargetRow = BirdCell.Row + BirdVerticalMovement

'check if the destination row is past the floor

If TargetRow >= FloorRange.Row Then

'if so, set the target row to 1 row above the floor

TargetRow = FloorRange.Row - 1

BirdVerticalMovement = 0

'check if destination row is above the top of the sheet

ElseIf TargetRow <= 1 Then

'if so set the target row to the top row

TargetRow = 1

BirdVerticalMovement = 0

End If

'store the new destination cell

Set BirdCell = shTest.Cells(TargetRow, BirdCell.Column)

End Sub

You should now be able to test the game and press the up and down arrow keys to influence the bird's height. When you get bored you can either click your Stop Game button with the mouse or just press the Tab key on the keyboard. If the code didn't work, check everything on this page or just download the working version of the workbook.

Windows API Functions for Detecting Key Presses

One of the problems with using the basic OnKey method is that if a player holds down the up arrow key it will interrupt whatever is currently happening. If the player holds down the key it will also continuously call the procedure attached to that key press. Try doing this by running the game and holding down the up arrow - you should see the game stutter as it tries to run the Flap procedure continuously.

Just as with our timing functions we can use the Windows API to provide us with functions that allow us to control things in much more detail. We'll start by declaring a function with the snappy title of GetAsyncKeyState. Copy this to the appropriate place in your public declarations module:

Public Declare Function GetAsyncKeyState Lib "user32" ( _

ByVal vKey As Long) As Integer

Here's the version for 64-bit editions of Office 2010 or later:

Public Declare PtrSafe Function GetAsyncKeyState Lib "user32" ( _

ByVal vKey As Long) As Integer

This function accepts a code number corresponding to a key on the keyboard and returns a value representing whether the key is up or down. We're going to use this to replace the simple OnKey method that we used earlier.

It's worth mentioning that there is another Windows API function for getting the state of a single key called GetKeyState. The differences between GetKeyState and GetAsyncKeyState are subtle but important. Essentially, we're using GetAsyncKeyState because we're interested in the current status of a key, not its status when some other event occurred.

Replacing OnKey with GetAsyncKeyState

Although we're going to detect player input using our fancy new Windows API function we're not going to completely abandon the OnKey method. We want to ensure that the up and down arrow keys don't perform their normal function while the game is running so we can still use our SetGameKeys procedure to deactivate them. Change the procedure so that it looks like this:

Private Sub SetGameKeys()

Application.OnKey "{ESC}", ""

Application.OnKey "{UP}", ""

Application.OnKey "{DOWN}", ""

Application.OnKey "{TAB}", ""

End Sub

This prevents any of the four listed keys from performing any action while the game is running. Unfortunately, that also means that pressing Tab won't stop the game any longer. We can fix that by adding a line of code to the UpdateAndDrawGame procedure. Change it so that it looks like this:

Public Sub UpdateAndDrawGame()

'Called by the SetTimer function

'Runs once for each tick of the timer clock

'Updates all game logic

'Draws all game objects

If GetAsyncKeyState(vbKeyTab) <> 0 Then TerminateGame



End Sub

We pass the value of a constant, vbKeyTab, into the GetAsyncKeyState function. If the function doesn't return 0 we know that the key is being pressed so we call the TerminateGame procedure. This means that we can successfully stop the game again by pressing the Tab key.

You can see the list of VBA key constants by pressing CTRL + SPACEBAR to show the IntelliSense list.

Intellisense key constants

The IntelliSense list contains all of the key constants that you'll need.


We can now add code to detect if the player presses the up or down arrow keys. Go back to the modBirdCode module and add two lines to the top of the UpdateBird procedure, below the TargetRow variable, so that it looks like this:

Public Sub UpdateBird()

Dim TargetRow As Integer

If GetAsyncKeyState(vbKeyUp) <> 0 Then Flap

If GetAsyncKeyState(vbKeyDown) <> 0 Then Dive

You can now run the game again to test it. You should find that pressing the up and down arrow keys perform the appropriate action. You may also have spotted another small problem...

Checking if a Key is Being Held Down

If you hold down the up arrow key you'll see that the bird flies to the top of the screen and stays there. That's definitely not a behaviour we want in our game. We only want to execute the Flap subroutine if the key is pressed down in the current tick of the game clock but wasn't pressed down in the previous one.

The easiest way to do this is to read the state of the up and down arrow keys into variables each time the bird is updated. Add the following two declarations to the top of the modBirdCode module:

Private PreviousUpKeyState As Integer

Private PreviousDownKeyState As Integer

We'll need to initialise these variables when the bird is initialised, so add the following two lines to the end of the InitialiseBird procedure:

PreviousUpKeyState = GetAsyncKeyState(vbKeyUp)

PreviousDownKeyState = GetAsyncKeyState(vbKeyDown)

These lines check if the keys are being pressed at the point the InitialiseBird procedure is called. We could just as easily have set these variables to 0 which our code would interpret as the keys not being pressed, regardless of the actual state of the keys.

Next we need to modify the If statements we added to the UpdateBird procedure earlier. As the code to do this is a bit longer we should create a separate subroutine for it. Add a new subroutine to the bottom of the same module which looks like this:

Private Sub CheckKeys()

If GetAsyncKeyState(vbKeyUp) <> 0 And _

PreviousUpKeyState = 0 Then Flap

If GetAsyncKeyState(vbKeyDown) <> 0 And _

PreviousDownKeyState = 0 Then Dive

PreviousUpKeyState = GetAsyncKeyState(vbKeyUp)

PreviousDownKeyState = GetAsyncKeyState(vbKeyDown)

End Sub

The If statements check if each key is being pressed down during this tick of the game clock but was not pressed down in the previous tick. Once we've performed the appropriate action we then store the current state of each key in the appropriate variable ready for the next tick of the clock.

Now we just need to modify the UpdateBird procedure to call the one we've just created. Remove the If statements we added to the UpdateBird procedure earlier and replace them with a single call to the CheckKeys procedure. The first few lines of the subroutine should look like this:

Public Sub UpdateBird()

Dim TargetRow As Integer


You can now run the game again and check that everything works as intended. If it doesn't you can download the working example from the link at the top of the page.

In the UpdateAndDrawGame procedure we don't need to test if the Tab key was being pressed in the previous update. The reason is that pressing the Tab key immediately ends the game, meaning that the UpdateAndDrawGame subroutine won't be called again anyway. If it makes you feel better you can still add the code to check if the Tab key was previously being pressed but I'm not going to.

Checking the State of the Entire Keyboard

In our simple game the player only needs to use two keys to play so it's fairly convenient to store the previous state of each key in separate variables and that's what we're going to stick with in this tutorial.

In a more complex game however, it would rapidly become tedious to store each key one-by-one. Instead, we could use a Windows API function to store the state of the entire keyboard in an array. The function is called GetKeyboardState and, if you're interested, you can see a quick example of how it works here.

What's Next?

I think that our game has looked ugly enough for long enough so the next tutorial is going to focus on upgrading the art of our game. I use the term "art" in the vaguest possible sense of the word - you'll understand why when you witness my child-like drawing skills in the next part of this tutorial.

This blog has 0 threads Add post