BLOGS BY TOPIC
BLOGS BY AUTHOR
BLOGS BY YEAR
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 - The Game Timing Loop
Useful Links
Return to the Flappy Bird in Excel VBA Tutorial index.
Download Flappy Owl Pt4 - Main Game Loop.
Training in Excel and training courses in VBA, for more background learning.
Introduction
This part of the tutorial introduces some extra Windows API functions and explains how to set up the sequence of individual procedures that will form our main game loop.
The SetTimer and KillTimer Functions
The function that will control our main game loop is called SetTimer. Copy the following declaration into your modPublicDeclarations module.
Public Declare Function SetTimer Lib "user32" ( _
ByVal hWnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
If you're using a 64-bit edition of Office 2010 or later you'll need this version of the declaration:
Public Declare PtrSafe Function SetTimer Lib "user32" ( _
ByVal hWnd As LongPtr, _
ByVal nIDEvent As LongPtr, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As LongPtr) As LongPtr
This function starts a timer which calls a specific procedure at a specified interval. The two parameters which interest us are uElapse, which is the number of milliseconds between each call to the procedure, and lpTimerFunction, which is a reference to the procedure that we want to call.
As we'll need to stop the timer when the game ends we should declare the KillTimer function as well. Copy this into the same module:
Public Declare Function KillTimer Lib "user32" ( _
ByVal hWnd As Long, _
ByVal nIDEvent As Long) As Long
The 64-bit version of this declaration is shown below:
Public Declare PtrSafe Function KillTimer Lib "user32" ( _
ByVal hWnd As LongPtr, _
ByVal nIDEvent As LongPtr) As Long
If you're using the version-independent declarations for your Windows API functions your entire modPublicDeclarations module should now look like this:
Option Explicit
#If Win64 Then
'Code is running in 64-bit Office
Public Declare PtrSafe Sub Sleep Lib "kernel32" ( _
ByVal dwMilliseconds As Long)
Public Declare PtrSafe Function SetTimer Lib "user32" ( _
ByVal hwnd As LongPtr, _
ByVal nIDEvent As LongPtr, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As LongPtr) As LongPtr
Public Declare PtrSafe Function KillTimer Lib "user32" ( _
ByVal hwnd As LongPtr, _
ByVal nIDEvent As LongPtr) As Long
#Else
'Code is running in 32-bit Office
Public Declare Sub Sleep Lib "kernel32" ( _
ByVal dwMilliseconds As Long)
Public Declare Function SetTimer Lib "user32" ( _
ByVal hwnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal hwnd As Long, _
ByVal nIDEvent As Long) As Long
#End If
Adding the Basic Game Procedures
When writing complex systems it pays to be organised as early as possible in the process. We're going to create a separate module to hold our game code, as well as separate modules for the main objects involved in the game. Ordinarily I'd use class modules to do this, but we'll stick to normal modules for now and look at how to switch to class modules later in the tutorial.
Start by inserting a new module called modGameCode. When you've done that, write or copy the following code into it:
Option Explicit
Public Sub InitialiseGame()
'Called once when game first starts
'Used to set starting parameters
'Begins the game timer
shTest.Select
Range("A1").Select
Cells.Clear
End Sub
Public Sub TerminateGame()
'Called once when game ends
'Used to tidy up
End Sub
Public Sub UpdateAndDrawGame()
'Called by the SetTimer function
'Runs once for each tick of the game clock
'Updates all game logic
'Draws all game objects
End Sub
The comments in each procedure describe reasonably neatly what each procedure is going to do. The InitialiseGame procedure will be called when we start the game, eventually this will be done by clicking a button on the worksheet. This procedure also sets the game timer running. The game timer will call the UpdateAndDrawGame procedure continuously until something in our game logic tells it to stop. At that point we'll call the TerminateGame procedure which will stop the timer and reset the game.
Creating the Timer Procedures
The next job is to create two timer procedures, one to start the game timer and one to stop it. Insert a new module called modTimerCode and add the following code to it:
Option Explicit
'stores the ID of the timer created
Private GameTimerID As Long
Public Sub InitialiseTimer()
'the time in milliseconds between each tick
Dim GameTimerInterval As Double
'makes the game clock tick 20 times per second
GameTimerInterval = 50
'pauses for half a second before starting game
Sleep (500)
'starts the timer calling UpdateGame
GameTimerID = _
SetTimer(0, 0, GameTimerInterval, AddressOf UpdateAndDrawGame)
End Sub
Public Sub TerminateTimer()
If GameTimerID <> 0 Then
'stops the timer whose ID we stored earlier
KillTimer 0, GameTimerID
GameTimerID = 0
End If
End Sub
The comments should describe what's happening here reasonably well. The GameTimerID variable is used to remember which timer was started by the game. It's declared as private because it's only used in this module.
We set the GameTimerInterval variable to 50, meaning that the game will update every 50 milliseconds, or 20 times per second. We could just as easily have used an expression here, for example:
'makes the game clock tick 60 times per second
GameTimerInterval = 1000 / 60
In most real games the game updates at either 30 times per second or 60 times per second. For our rudimentary Excel game this isn't quite so important so you'll be able to play with this value later to speed up or slow down the game. Leave it set to 50 for now.
The most important line is the one which calls the SetTimer function. We pass in the value of our timer interval and tell it to call our UpdateAndDrawGame procedure. The AddressOf operator is used to pass a pointer to that procedure into the SetTimer function. Windows API functions don't call procedures in quite the same way as normal VBA procedures do so the AddressOf operator is important here.
Starting and Stopping the Timer
Now we can go back to the modGameCode module by double-clicking on its name in the Project Explorer. To start the game timer we simply need to add this line to the end of the InitialiseGame subroutine:
InitialiseTimer
End Sub
We should also add this line to the end of the TerminateGame procedure:
TerminateTimer
End Sub
The next thing we need to do is make the game actually do something when it runs!
Creating a Module for the Bird Code
Just as we've created separate modules to store code for the game and timer, we'll have a separate module for the bird called modBirdCode. Create it and then add the following code to it:
Option Explicit
Private BirdCell As Range
Private BirdPreviousCell As Range
Private BirdVerticalMovement As Long
Private Const Gravity As Byte = 1
Private FloorRange As Range
Public Sub InitialiseBird()
'set initial bird parameters
Set BirdCell = shTest.Range("R5")
BirdVerticalMovement = 0
BirdCell.Interior.Color = rgbCornflowerBlue
Set FloorRange = shTest.Range("A40:Z40")
FloorRange.Interior.Color = rgbBlack
End Sub
Public Sub UpdateBird()
'calculate how many cells to fall
BirdVerticalMovement = BirdVerticalMovement + Gravity
'remember the cell that the bird was in previously
Set BirdPreviousCell = BirdCell
'store the new destination cell
Set BirdCell = BirdCell.Offset(BirdVerticalMovement, 0)
'check if the new destination is past the floor
If BirdCell.Row >= FloorRange.Row Then
'if so, put the bird above the floor
Set BirdCell = Cells(FloorRange.Row - 1, BirdCell.Column)
'reverse the movement direction (makes the bird bounce)
BirdVerticalMovement = -8
End If
End Sub
Public Sub DrawBird()
'clear the old cell, colour in the new cell
BirdPreviousCell.Clear
BirdCell.Interior.Color = rgbCornflowerBlue
End Sub
Each of these subroutines will need to be called in the appropriate place from the modGameCode module, so head back there next. Add a line to the InitialiseGame procedure so that it looks like this:
Public Sub InitialiseGame()
'Called once when game first starts
'Used to set starting parameters
'Begins the game timer
shTest.Select
Range("A1").Select
Cells.Clear
InitialiseBird
InitialiseTimer
End Sub
Now add two lines to the UpdateAndDrawGame procedure 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
UpdateBird
DrawBird
End Sub
What's Next
That's it for our basic game logic. When we finally start the game it will continue to run through the UpdateAndDrawGame procedure until the timer is stopped. Now all we have to do is provide a way for the player to start and stop the game! The next part of the tutorial describes how to do that.