Read our blogs, tips and tutorials
Try our exercises or test your skills
Watch our tutorial videos or shorts
Take a self-paced course
Read our recent newsletters
License our courseware
Book expert consultancy
Buy our publications
Get help in using our site
551 attributed reviews in the last 3 years
Refreshingly small course sizes
Outstandingly good courseware
Whizzy online classrooms
Wise Owl trainers only (no freelancers)
Almost no cancellations
We have genuine integrity
We invoice after training
Review 30+ years of Wise Owl
View our top 100 clients
Search our website
We also send out useful tips in a monthly email newsletter ...
Flappy Bird in Excel VBA Part 4 - The Game Timing Loop |
---|
This part of the tutorial describes how to set up a timing loop which allows the game to update continuously. |
In this blog
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.
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 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
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.
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.
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!
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
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.
Some other pages relevant to the above blog include:
Kingsmoor House
Railway Street
GLOSSOP
SK13 2AA
Landmark Offices
99 Bishopsgate
LONDON
EC2M 3XD
Holiday Inn
25 Aytoun Street
MANCHESTER
M1 3AE
© Wise Owl Business Solutions Ltd 2024. All Rights Reserved.