Programmer's Corner - theraje's VB6 Game Tutorial Series I - Making a Game Loop

Backup and Security Solutions 10% off all products with promo code: VISI-P1YR
Get the Programmer's Corner FireFox Search Plug-In

theraje's VB6 Game Tutorial Series I

Making a Game Loop

Hey amigos, how y'all doin'?! In this tutorial, we're going to make a timed game loop for games written in Visual Basic 6.0. This is just like a regular Do/Loop, except that it only fires after a certain amount of time has passed. We do this to make sure that our program runs at the same speed on a fast computer as it does a slow one.

"Well, why not just use a timer," you ask. Mainly because timers, although they're nice to get things up and running quickly, aren't very accurate. In fact, their accuracy is rather sporadic. If you've tried out my Keyboard Input game tutoral, you may've noticed that the speed of the picMover fluctuated as it traveled. A timed game loop aims to fix this sort of inconsistency found in using timer controls. Timer controls are also only accurate down to 50-100ms - any faster, and they don't tend to keep up. A timed loop runs as it should at even faster speeds.

So, what do we need to get started? Well, once again, it's time to crack open our handy API Text Viewer and get WIN32API loaded. Today's lucky function is GetTickCount, and its declaration is as follows (replace Public with Private if you're just going to dump it in a form rather than a module):

Public Declare Function GetTickCount Lib "kernel32" () As Long

Looks simple enough: Just a function with no arguments and a return value of the Long variety. Looks can be deceiving, as GetTickCount is a little tricky to use unless you know how. The first step to mastering this function is to tie a pair of constants to it:

Private gtcDesiredTime As Long
Private gtcStart As Long

These will help us when using GetTickCount. gtcDesiredTime is, appropriately enough, the desired interval of time we want to wait to do things. It is measured in milliseconds, just like the timer control. So, setting it to 1000 would give us an interval of a whole second between each firing of the game loop. And, as you probably know, one second is a long time for a game to not do anything. gtcStart simply stores the value of GetTickCount when we run it. We check it against gtcDesiredTime to see if it's OK to fire the loop again.

Now that we have GetTickCount and some necessary helpers declared, let's get down to business! Create a Boolean variable in the general declarations area of your form/module, and name it bRunning. This variable will tell the loop when it should stop. If we don't tell it when to stop, our game will not unload properly. Next thing to do is create a sub called GameLoop, which will contain our game loop (duh!). Then create a command button on your form that sets gtcDesiredTime to 100, bRunning to True, and calls GameLoop. Your game loop should look like this:

    Do While bRunning = True

        If gtcDesiredTime < GetTickCount - gtcStart Then
            gtcStart = GetTickCount
            'Do game stuff
        End If

        DoEvents
    Loop

So, we check to see if our desired time is less than the current tick minus what GetTickCount returned the last time the loop was run. Basically, it works by testing how many milliseconds have passed since the loop was last fired. GetTickCount stores how much time has passed in total. We subtract gtcStart, and use whatever's left to see how long it has been since the loop was last run. If what's left is greater than our desired interval, it knows it's time to run the loop again. Simply put, the statement is used to make sure it isn't running too fast. Hopefully I explained it well enough... if not, no big deal. Either you can analyze it yourself and it will come to you, or you can just use it without really understanding how it works (just be sure to keep that If statement somewhere you'll be able to find it next time!). After that, we reset gtcStart to equal GetTickCount, so we'll know when the loop last ran. If it hasn't run yet, our desired interval will be small enough to fire the loop anyway.

We also made absolutely certain to add a DoEvents line in the loop. If we didn't, our loop would basically go out of control, and not let you do anything at all until the loop ended. And, since we have no way to set bRunning to False unless we can do stuff to our form - our program would be completely inoperable. We wouldn't even be able to unload the dadgum thing!

As for what goes inside the If statement, that's where we call all our game functions - getting input, drawing images, etc. Please be sure to use functions and subs for doing game actions - your game code will be utterly atrocious otherwise... and this coming from a guy so disorganized that he stores everything under the bed because he otherwise would be unable to find what he was looking for (not that it helps much).

That's pretty much everything you need to get going. In order to demonstrate the differences between using a timer, an untimed Do/Loop, and a timed loop like above, download my test sample by clicking here.

Come back and see us again!

Author Information:

Clint V Franklin

http://theraje.programmers-corner.com

Comments:

Add your comments here.

Name

Comment

You can also send feedback to feedback@programmers-corner.com


John Oliver - January 19, 2005 7:00 AM

The tick counter read by GetTickCount is reset to zero when the system is rebooted. If the system is up for more than about 25 days (I think, is the counter treated as signed or unsigned?), GetTickCount rolls over to 0 ... If this loop happens to set StartCount just before that roll over then the If statement will not be satisfied for a very long time. I ran into this in software running my telescope and the reason I found this page was to remind myself of the declaration for GetTickCount so I could write a bit of code reminding me to restart the scope software when this happens.