[Vb.Net] Managed Game Loop-VBForums
Results 1 to 1 of 1

Thread: [Vb.Net] Managed Game Loop

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    4,417

    [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:
    Code:
    Private game_thread As Threading.Thread
    
    Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    	quit = True
    End Sub
    
    Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    
    	game_thread = New Threading.Thread(AddressOf GameLoop)
    	game_thread.Start()
    
    End Sub
    
    Private quit As Boolean = False
    Private Sub GameLoop()
    
    	Dim s As New Stopwatch
    	s.Start()
    
    	'Update
    	'Draw
    	'Render
    
    	'Lock in the framerate at 60 FPS
    	While s.Elapsed <= TimeSpan.FromMilliseconds(16.6)
    		Application.DoEvents()
    	End While
    
    	s.Stop()
    	
    	'Display the FPS
    	Console.WriteLine(s.ElapsedMilliseconds)
    
    	'Keep looping until quit is True
    	If quit = False Then
    		game_thread = New Threading.Thread(AddressOf GameLoop)
    		game_thread.Start()
    	End If
    End Sub
    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.

    Using the QueryPerformance APIs. Provided by Jacob Roman:
    Code:
    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()
            QueryPerformanceFrequency(Ticks_Per_Second)
        End Sub
     
        Private Function Get_Elapsed_Time() As Single
            Dim Current_Time As Long
     
            QueryPerformanceCounter(Current_Time)
            Return CSng(Current_Time / Ticks_Per_Second)
        End Function
     
        Private Sub Lock_Framerate(ByVal Target_FPS As Long)
     
            Static Last_Time As Long
            Dim Current_Time As Long
            Dim FPS As Single
     
            Do
                QueryPerformanceCounter(Current_Time)
                FPS = CSng(Ticks_Per_Second / (Current_Time - Last_Time))
            Loop While (FPS > Target_FPS)
     
            QueryPerformanceCounter(Last_Time)
     
        End Sub
     
        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)
            End If
     
            Return "Frames Per Second: " & Convert.ToString(Get_Frames_Per_Second)
        End Function
     
        Private Sub Game_Loop()
            Do While Running = True
                'Game Code Here
     
                Lock_Framerate(60)
                Me.Text = Get_FPS()
                Application.DoEvents()
            Loop
        End Sub
     
        Private Sub Main()
            Me.Show()
            Hi_Res_Timer_Initialize()
            Milliseconds = Get_Elapsed_Time()
            Running = True
            Game_Loop()
        End Sub
     
        Private Sub Shutdown()
            Running = False
            Application.Exit()
        End Sub
     
        Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.FormClosing
            Shutdown()
        End Sub
     
        Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Main()
        End Sub
    End Class
    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.

    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.