-
Dec 24th, 2013, 11:28 AM
#1
GameLoop theories!
I know that there are a ton of ways to execute a game loop so I'm dedicating this thread to just that. I'd hope that this leads to improvement of all types of game loops.
Here is my first example that I came up with about 3 months back:
Code:
Private quit As Boolean = False
Private Sub GameLoop()
'Update
'Draw
'Render
'Loop
If quit = False Then
ExecuteAfterPause(CInt(16.6), New MethodInvoker(AddressOf GameLoop))
End If
End Sub
This first one uses JMcIlhinney's pauser code.
Here is my second example that I came up with literally about 3 seconds ago:
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
'Loop
While s.Elapsed <= TimeSpan.FromMilliseconds(16.6)
Application.DoEvents()
End While
Console.WriteLine(s.ElapsedMilliseconds)
s.Stop()
If quit = False Then
game_thread = New Threading.Thread(AddressOf GameLoop)
game_thread.Start()
End If
End Sub
For both of those I get 17 frames per milliseconds. The second one is a little bit more jittery and I think that has to do with the DoEvents there. There are even times where it spikes up to 30 frames per millisecond.
Last edited by dday9; Dec 24th, 2013 at 11:38 AM.
Reason: Took out the VB.Net prefix.
-
Dec 24th, 2013, 02:18 PM
#2
Re: GameLoop theories!
Ooooh but there are far better ways to do a game loop my friend. And I'm about to unleash my own personal secrets. Games have not just the game itself, but also an Intro, a Title Screen (with options), sometimes a demo, a loading screen, and even an ending (not to mention collision detection, ai, controls, physics, and a bunch of other things), all managed by one entity...the main game loop. And it must be locked at 60 frames per second. Unless you live in Europe where the standard is 50 frames per second. To lock the framerate at any given rate, you do this:
vb.net Code:
Option Explicit On Option Strict On Public Class Form1 Private Declare Function timeGetTime Lib "winmm.dll" () As Integer Private Milliseconds As Integer Private Get_Frames_Per_Second As Integer Private Frame_Count As Integer Private Running As Boolean Private Function Get_Elapsed_Time() As Single Dim Current_Time As Integer Current_Time = timeGetTime Return Convert.ToSingle(Current_Time) / 1000 End Function Private Sub Lock_Framerate(ByVal Target_FPS As Integer) Static Last_Time As Integer Dim Current_Time As Integer Dim FPS As Single Do Current_Time = timeGetTime FPS = 1000 / Convert.ToSingle(Current_Time - Last_Time) Loop While (FPS > Target_FPS) Last_Time = timeGetTime 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() With Me .Show() .BackColor = Color.FromArgb(255, 0, 0, 0) .DoubleBuffered = True End With Running = True Game_Loop() End Sub Private Sub Shutdown() Running = False Application.Exit() End Sub Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Main() End Sub Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.FormClosing Shutdown() End Sub End Class
Unless you use DirectX, then it locks the framerate for you. Once your game gets more complex and you wanna add more things, you break it up a notch:
vb.net Code:
Private Enum GAME_STATUS INTRO = 1 TITLE_SCREEN = 2 LOAD_SCREEN = 3 GAME_WORLD = 4 ENDING = 5 End Enum Private Status as GAME_STATUS ... Private Sub Game_Loop() Do While Running = True Select Case Status Case INTRO Intro_Loop Case TITLE_SCREEN Title_Screen_Loop Case LOAD_SCREEN Load_Screen_Loop Case GAME_WORLD Game_World_Loop Case ENDING Ending_Loop End Select Lock_Framerate(60) Me.Text = Get_FPS() Application.DoEvents() Loop End Sub
Where the Intro_Loop, Title_Screen_Loop, Game_World_Loop, etc., are their own sub routines that contain all the code done in the main game loop. But the subs DO NOT contain their own endless Do loops. That would defeat the purpose of what we are doing and the frame rate would not be locked because you are stuck in another endless loop. The subs only contain the code that is executed over and over related to the game. Doing this would help you build an entire game with these entities from start to finish. And you can lock the frame rate at any given rate desired, even for debugging purposes which helps a ton. I not only do this code for VB6 and VB.NET, but also C++ as well. It works. I hope this helps.
-
Dec 26th, 2013, 11:16 AM
#3
Re: GameLoop theories!
Holy crap I wish I had known about the timeGetTime function before! This is going to be fun :]
Do you run the loop on the UI thread or do you run it on it's own separate thread?
Have you ever tried running a game loop using parallelism?
I haven't ever run anything that was very data intensive such as complex AI, so I really have nothing to compare to when it comes to FPS and multi/parallel threading.
-
Dec 26th, 2013, 12:11 PM
#4
Re: GameLoop theories!
To be honest I dont do multithreading. Its all under one thread and works like a charm, even in my massively sized games I wrote. I just dont see the need to put the loop on another thread.
-
Jan 2nd, 2014, 01:35 PM
#5
Re: GameLoop theories!
Let me ask you this Jacob, I know why it's important to have a game locked in at a certain FPS, but why is the target at 60(or 50 in Europe)? I never quite got why it should be at 60 and not at say... 40 or hell even 120.
-
Jan 2nd, 2014, 04:26 PM
#6
Re: GameLoop theories!
What's your screen refresh rate? 60Hz, right? (As indeed is your electricity supply.) There's no point having an FPS greater than that because it would simply fail to show some frames altogether. And if its lower than that it could go out of phase with the possibility of visual glitches. In Europe the power supply is 50Hz and the refresh rate has usually matched it. Having said that I'm now running a 60Hz refresh rate (my display settings won't allow me not to!) so I'm not sure whether it's still strictly necessary to change the target fps to suit us Right Pondians.
As the 6-dimensional mathematics professor said to the brain surgeon, "It ain't Rocket Science!"
Reviews: "dunfiddlin likes his DataTables" - jmcilhinney
Please be aware that whilst I will read private messages (one day!) I am unlikely to reply to anything that does not contain offers of cash, fame or marriage!
-
Jan 2nd, 2014, 11:14 PM
#7
Re: GameLoop theories!
Oh ok, that makes sense. Because I was looking at reviews on some different processors and I saw that they were saying: "Oh with this processor I had blah blah blah amount of FPS" and I'm thinking ok so it's better to have a higher FPS? But really it's all dependent on the screen's refresh rate then.
-
Jan 3rd, 2014, 12:11 PM
#8
Re: GameLoop theories!
Originally Posted by dday9
Oh ok, that makes sense. Because I was looking at reviews on some different processors and I saw that they were saying: "Oh with this processor I had blah blah blah amount of FPS" and I'm thinking ok so it's better to have a higher FPS? But really it's all dependent on the screen's refresh rate then.
Exactomundo. And if you work with DirectX, it does it for you. No locking framerates necessary. And whats good about my code on locking the framerate is that you can slow it down to lets say 10 FPS for debugging purposes.
-
Jan 3rd, 2014, 12:26 PM
#9
Re: GameLoop theories!
See XNA has a managed game loop built in... if you use the GameStudios which is only available for C#. This forced me to think of ways for game loops. Now with XNA being no more... I'm trying to expand my options.
-
Mar 17th, 2014, 07:12 PM
#10
Re: GameLoop theories!
Thought you should know that TimeGetTime is not necessarily consistent across all hardware and versions of Windows, so may or may not give you 60hz using Jacob's code.
I've gotten to use a lot of different computers, and Windows (or the hardware manufactures) lack of consistency when it comes to the clocks behind these various API calls has been a real thorn in the side.
Just when you think you have a simple timing solution, you find a machine that doesn't work.
In this case, I tried it on an HP EliteBook 8470p laptop (running Windows 7 Enterprise 64-bit), and I asked for 60hz, but was getting 32hz.
I investigated and found that the time returned by the timeGetTime function incremented at 64hz, i.e. some example deltas
35006709, 35006709, 35006709,..... doesn't matter how often you read it in the loop, it won't change until
35006724, 35006724, 35006724,..... 15.625 milliseconds has passed....
35006740, 35006740, 35006740,.....
So, what happens is we try to wait for 16.666 milliseconds to pass, and the timeGetTime changes just short of that, so we continue to wait, and it doesn't change again until another 64hz period has passed, at which time we see we've exceeded the 16.666 milliseconds (by quite a bit) and return.
The result is, we always wait two 64hz periods, so are running at 32 hz.
One way to improve on that situation is to establish a base time, and add the "trigger" time to that.
Instead of
31. Last_Time = timeGetTime
you have
31. Last_Time += 1000/Target_FPS
The result is you will average 60 hz over time. In this case, the code will run at 64 hz for around 11 passes (15 to 16ms), and then 32hz for 1 pass(31 ms)
and repeat.
You need a safety check to initialize the Last_Time, and in case of debugging, or some other reason your code gets delayed and you miss a number of frames, you reset the Last_Time to re-sync to the current time.
Since we expect one time period out of 11 to be almost twice the target_FBS, we'll set the limit to be three times the target_FBS interval (i.e. if the update was less than 20hz, reset the Last_Time value)
If ((Current_Time - Last_Time) > (3 * (1000 / Target_FPS))) Then Last_Time = Current_Time
Bottom line, you can't always trust that timeGetTime will return 1 ms resolution, or even close to it.
-
Mar 18th, 2014, 08:39 AM
#11
Re: GameLoop theories!
What happens if you run my very first example?
-
Mar 20th, 2014, 12:11 AM
#12
Re: GameLoop theories!
Originally Posted by dday9
What happens if you run my very first example?
I'm on a different machine now, but I did a quick test to see what interval I was getting on timeGetTime to see if I could use this laptop as a test platform, or wait until I was back on the machine that had the 64hz based interval.
The interesting thing on this machine, is that it looks like it is mostly 100hz based, with the millisecond interval being 10ms for a good portion of a second.
But then it will drop to 1ms resolution for a series, up to 22 times in a row, then return to 10ms. On this machine I never saw it slower than 10ms, but testing 400 loops (which would be about 4 seconds at 10ms), showed that it increased to 1ms resolution for several streaks, but never in a consistent pattern. it would also have short breaks (usually only 3 or 4 times through the loop) where it would be some other smaller number, in the range of 2 to 4ms.
Anyway, since it wasn't a consistent 1ms resolution, being 10ms the majority of the time, it seemed an acceptable surrogate for the other computer.
Using your first code, based on JMcIlhinney's pauser code, it behaved as I would expect, with erratic timing, the majority of the time being around 20 to 27ms (JMc's pause time (17) + the 10ms coarseness of the timer, dropping to 17ms or so when the timer dropped to 1ms resolution).
Your second code in the first post was a very consistent 17ms, because the stopwatch object on most of the machines I've tried is very consistent, and when I tested the stopwatch earlier with my timing test (I only tested the millisecond accuracy), it was consistent at 1ms. It actually would probably be accurate to some sub-millisecond value, if I looked at the frequency of Tick change, but I didn't).
I removed the Applications.DoEvents call from your second code, because that loop is on a background thread so what purpose would calling DoEvents have on a background thread (other than possibly, as you suspected, introduce some connection back to the GUI thread, making the timing inconsistent).
The way the background thread is used is the main drawback of that code (in my opinion), since it is launching and exiting a background thread for every loop, which is a lot of resource thrashing.
The simplest and best timing on this machine (and I will have to relook at the other machine to check its stopwatch accuracy), is to simply replace the timeGetTime call in Jacob's code with a stopwatch.ElapsedMilliseconds call to get a very well behaved locked frame rate, the vast majority of the time. It definitely would appear, given the half dozen or so machines that I've tried it on over the last two years, is that the stopwatch is more likely to give a reasonable result compared to timeGetTime. The stopwatch object uses the high resolution timer tick count, if it is supported, so is based on the same source that the unmanaged API calls QueryPerformanceFrequency and QueryPerformanceCounter are based.
The only caveat (which also applies to QueryPerformanceCounter), is that on multi-processor machines, the counter value can be different depending on which core the thread is running on, so you would want to specify processor affinity for a thread, with the ProcessThread.ProcessorAffinity method, to have consistent timing at the possible cost of performance loss by the system not being able to switch your thread to a lighter loaded processor (but hopefully keeping the processors balanced by moving threads off "your" processor that aren't tied to a particular processor, so keeping the impact of your desire to stick to one processor at a minimum.
Last edited by passel; Mar 20th, 2014 at 12:27 AM.
-
Mar 20th, 2014, 11:41 PM
#13
Re: GameLoop theories!
Guys guys lol. I posted the wrong code. Must have been drunk cause that was an old and obsolete program. I don't really use the timeGetTime for my game loops so don't sweat it. I was just rushing out the answer
I always use the QueryPerformance APIs. 60 frames per second every time. Sorry for the mishap, I don't want the thread to stretch on over it :rollseyes:
vb.net 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
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|