Every game needs a main loop. Since C# is intended for applications and not games it doesn’t provide a very efficient way to create the main loop. Fortunatly, C# makes it possible to import the necessary DLLs to create a main loop the old fashioned way: the C way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | //found in bunny.cs struct POINTAPI { public Int32 x; public Int32 y; } struct MSG { public Int32 hwmd; public Int32 message; public Int32 wParam; public Int32 lParam; public Int32 time; public POINTAPI pt; } [DllImport("user32.dll", SetLastError = true)] private static extern bool PeekMessage( ref MSG lpMsg, Int32 hwnd, Int32 wMsgFilterMin, Int32 wMsgFilterMax, PeekMessageOption wRemoveMsg); [DllImport("user32.dll", SetLastError = true)] private static extern bool TranslateMessage(ref MSG lpMsg); [DllImport("user32.dll", SetLastError = true)] private static extern Int32 DispatchMessage(ref MSG lpMsg); private enum PeekMessageOption { PM_NOREMOVE = 0, PM_REMOVE } private static Int32 WM_QUIT = 0x12; ... public void Run() { MSG msg = new MSG(); ... while (!done) { while (PeekMessage(ref msg, 0, 0, 0, PeekMessageOption.PM_REMOVE)) { if (msg.message == WM_QUIT) { this.Close(); break; } TranslateMessage(ref msg); DispatchMessage(ref msg); } ... //game stuff goes here } } ... private void Form1_FormClosing(object sender, FormClosingEventArgs e) { done = true; //this makes sure everything exits properly } |
We use a “while” instead of an “if” when processing messages because we want to keep everything moving along. Say we use an “if” instead. Now imagine that the game, for whatever reason, is only able to run at 1 frame per second. We would only be able to handle one windows message per second. The windows message system is going to get backed up very quickly. By using a “while” we take a fraction of a second to clear out the messages and then the actual render can take as long as it wants. You can put the code from the Run() method directly into your C# form’s main method or call it from the main method. It really doesn’t matter. What this essentially does is get you an unmanaged main loop. This link has a discussion about setting up a main loop in C#. Specifically Tom Miller explains what the problem is with the standard C# main loop.
He gives 4 options for creating the main loop.
- Set your form to have all drawing occur in WmPaint, and do your rendering there. Before the end of the OnPaint method, make sure you do a this.Invalidate(); This will cause the OnPaint method to be fired again immediately.
- P/Invoke into the Win32 API and call PeekMessage/TranslateMessage/DispatchMessage. (Doevents actually does something similar, but you can do this without the extra allocations).
- Write your own forms class that is a small wrapper around CreateWindowEx, and give yourself complete control over the message loop.
- Decide that the DoEvents method works fine for you and stick with it.
The problem with the first method is that you’re relying on windows messaging to get the next frame rendered. A message has to be created, sent, received and translated before the next frame is rendered.
This is what Bunnies does. No messages are created, it just processes them as they come in. Bunnies doesn’t have to wait for a message to come in before it renders the next frame.
I havn’t played around with C# enough to know what he’s refering to exactly but it sounds dreadful.
This is the standard method that is used in the DirectX Tutorials by Microsoft. It does pretty much what Bunnies is doing but DoEvents allocates memory every time its called which slows things down.
You can stick with the version that has been implemented or try implementing one of the alternatives.