Oct 9th, 2013, 02:29 PM
[Vb.Net] Managed Game Loop
In this thread I will discuss how to create a managed game loop in a proper manner. First I will identify ways that are most commonly used and explain why they should not be used. Then I will explain how to set up the proper managed game loop as well as why it works the way it does.
In game programming, one of the biggest and most basic concepts that should be applied is the game loop. A game loop is simply something that keeps the game in perpetual motion. Think of a normal windows form application for a second, in most scenarios the program remains idle until a user invokes some sort of an event such as clicking a button. In game programming, the game is constantly moving. In other words the game assumes that something is always happening and doesn't wait for user interaction for the program to continue.
Something that most beginners use as their game loop is the System.Windows.Forms.Timer class. This is OK in the sense that the timer does provide a simple means of raising an event(the Tick event) at user-defined intervals, however these timers are notoriously unreliable at lower intervals. The Interval property of the Timer gets or sets the time measured in milliseconds that invokes the Tick event. Therefore the lower the Interval, the less time between ticks. While theoretically the timer's Interval can be at one, realistically the timer starts to become inaccurate when it's Interval is set at about 50. The reason for this lies in the fact that this timer is not a high precision timer. This translates to over a period of time being off x amount of milliseconds(which is sufficient for most applications) after each Tick, there starts to be greater period of gaps. In games, those gaps are what are known as lag.
So if using the System.Windows.Forms.Timer is not a suitable solution, then why not set up a loop that runs as fast as the CPU will allow it to run. Running this type of loop is what is known as running a "Busy Wait" or "Busy Loop". The technique is often used(incorrectly) in normal windows form applications to just kill time until something has happened. One of the reasons why this is a terrible solution is because it kills the performance of the computer and can raise the CPU usage to almost %100! The other reason for this not being a suitable solution is the concept of a locked frame rate. The frame rate is the speed at which the game is refreshed measured in frames-per-second(FPS). Having a locked frame rate allows for a consistent animation of the game. Using a busy loop does not produce a precise and predictable frame rates which result in choppy animation.
The busy loop and the System.Windows.Forms.Timer is out of the question for our game loop. So what do we use? From my experience there are two solutions. One, which is the simpler of the two(but less accurate), is using a high precision timer. In the .NET framework you can find a high precision timer in the System.Diagnostics.Stopwatch class. This class, unlike the System.Windows.Forms.Timer class, does not provide a Tick event or some equivalent; so it is our job to create a method that will keep track of the frame rate and lock in the frames-per-second. This should be done in a separate thread to achieve maximum accuracy. The second solution is to use the QueryPerformance APIs. The QueryPerformance API's consist of two APIs: the QueryPerformanceCounter function and the QueryPerformanceFrequency function. Using both of these functions will return a high-resolution time stamp that cannot be surpassed.
Here are some examples of using both:
Using the System.Diagnostics.Stopwatch:
In this example, I start a thread in the form load event. In that thread, I start a stopwatch then perform the programming that is essential to the game. After all the actions have taken place I use a busy wait to make sure that there has been at least 60 FPS since the stopwatch has started. Then I use Console.WriteLine to keep track of the FPS. Finally I check if the game is to keep going and just repeat. But wait, you're probably thinking "You just said that busy waits are the devil!" well I probably should have specified a bit. Busy waits are not good, if they are done on the GUI thread. If they are done on a separate thread then you won't see the high CPU usage and other bad side effects that you would see otherwise.
Private game_thread As Threading.Thread
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
quit = True
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
game_thread = New Threading.Thread(AddressOf GameLoop)
Private quit As Boolean = False
Private Sub GameLoop()
Dim s As New Stopwatch
'Lock in the framerate at 60 FPS
While s.Elapsed <= TimeSpan.FromMilliseconds(16.6)
'Display the FPS
'Keep looping until quit is True
If quit = False Then
game_thread = New Threading.Thread(AddressOf GameLoop)
Using the QueryPerformance APIs. Provided by Jacob Roman:
In this wonderful example that Jacob Roman posted, he starts by calling a While loop that will stop when the running variable is false. Inside that sub, he locks the frame rate at 60 FPS by looping until the FPS is greater than the target FPS. The FPS is set by this operation: Ticks_Per_Second / (Current_Time - Last_Time). After the FPS is locked, he sets the window's text to the FPS and calls Application.DoEvents.
Option Explicit On
Option Strict On
Public Class Form1
Private Declare Function QueryPerformanceCounter Lib "kernel32" (ByRef lpPerformanceCount As Long) As Integer
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (ByRef lpFrequency As Long) As Integer
Private Milliseconds As Single
Private Get_Frames_Per_Second As Integer
Private Frame_Count As Integer
Private Running As Boolean
Private Ticks_Per_Second As Long
Private Start_Time As Long
Private Sub Hi_Res_Timer_Initialize()
Private Function Get_Elapsed_Time() As Single
Dim Current_Time As Long
Return CSng(Current_Time / Ticks_Per_Second)
Private Sub Lock_Framerate(ByVal Target_FPS As Long)
Static Last_Time As Long
Dim Current_Time As Long
Dim FPS As Single
FPS = CSng(Ticks_Per_Second / (Current_Time - Last_Time))
Loop While (FPS > Target_FPS)
Private Function Get_FPS() As String
Frame_Count = Frame_Count + 1
If Get_Elapsed_Time() - Milliseconds >= 1 Then
Get_Frames_Per_Second = Frame_Count
Frame_Count = 0
Milliseconds = Convert.ToInt32(Get_Elapsed_Time)
Return "Frames Per Second: " & Convert.ToString(Get_Frames_Per_Second)
Private Sub Game_Loop()
Do While Running = True
'Game Code Here
Me.Text = Get_FPS()
Private Sub Main()
Milliseconds = Get_Elapsed_Time()
Running = True
Private Sub Shutdown()
Running = False
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.FormClosing
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Now that we have two very well thought out examples of game loops, I wanted to take the time to discuss how and why setting the frames-per-second at a certain interval. The frame rate will vary depending on what device you are targeting. In both examples, you will see that the frames-per-second is locked in at 60FPS. This is because both examples are targeting computers that have a screen refresh rate of 60Hz. In both of the examples, there is no point in having a frame rate higher than 60FPS because no matter what the screen will on refresh at 60Hz. So how do you determine what the frame rate should be set at?
The simplest way is to check what the maximum refresh rate will be and set the frame rate equal to or less than that refresh rate. In the United States, the refresh rate for most monitors is 60Hz while in Europe the refresh rate for most monitors is 50Hz. If you have a television that has a refresh rate of 120Hz and you want to develop specifically for that TV, then lock your FPS at or below 120.
My last bit of advice is that the general rule of thumb is to have the FPS set between 30 and 60.
Last edited by dday9; May 22nd, 2014 at 11:29 AM.
Click Here to Expand Forum to Full Width