Jump to content
  • Advertisement
Sign in to follow this  
yuppies

[.net] Game Loop in C#?

This topic is 5011 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Advertisement
There are two ways that I can think of to do it. But the easiest (for me to demonstrate it) is this..


static void Main()
{
using (Form1 frm = new Form1())
{
frm.Show();

while (frm.Created)
{
frm.Render();

Application.DoEvents();
}

}
}

This is of course assuming your form is called Form1.

The other (probably better) way to do it is to wrap some Win32 API functions to create a traditional windows message loop. There are quite a few threads on this forum describing this method. I tend to use the above one when trying stuff out - its a lot quicker and no fuss. The only catch is I believe there are some memory allocations occuring in DoEvents() which can cause the garbage collector to fire more frequently. Again, there is a good thread on this forum regarding this and what exactly occurs in the DoEvents() method.

Share this post


Link to post
Share on other sites

i belive using application.doevents() allocates alot of objects over time. so its not really suited to being used for a game loop i would suggest doing some thing similar to this.


//=============================================
// MessagePump.cs
// a replacement for application.doevents()
//=============================================
#region Using directives

using System;
using System.Reflection;
using System.Runtime.InteropServices;

#endregion

namespace Win32
{
public class MessagePump
{
public enum PeekMessage_Paramaters
{
No_Remove = 0,
Remove = 1,
No_Yield = 2
}

public struct POINT
{
public Int32 X;
public Int32 Y;
}

public struct MSG
{
public IntPtr hwnd;
public uint message;
public uint wParam;
public uint lParam;
public uint time;
public POINT pt;
}

private static MSG msg = new MSG();

[DllImport("user32", EntryPoint = "PeekMessage")]
public static extern bool PeekMessage(out MSG lpMsg,
IntPtr hWnd,
Int32 MsgFilterMin,
Int32 MsgFilterMax,
PeekMessage_Paramaters RemoveMsg);

[DllImport("user32", EntryPoint = "TranslateMessage")]
public static extern bool TranslateMessage(ref MSG
lpMessage);

[DllImport("user32", EntryPoint = "DispatchMessage")]
public static extern Int32 DispatchMessage(ref MSG
lpMessage);

public static bool DoEvents()
{
if (PeekMessage(out msg, (IntPtr)0, 0, 0, PeekMessage_Paramaters.Remove))
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
return true;
}
return false;
}

public static bool DoEvents(IntPtr hWnd)
{
if (PeekMessage(out msg, hWnd, 0, 0, PeekMessage_Paramaters.Remove))
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
return true;
}
return false;
}

}
}




// game loop
while (running && gameWindow.Created)
{
while (!MessagePump.DoEvents())
{
GameMain();
}
}



i hope this helps you

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Tom Miller, co-creator of the Managed DX libraries officially denounces any game loop which using DoEvents. Apparently it causes problems with the garbage collector and has the potential for much greater overhead. I believe it also does some dirty stuff to the events model.

The solution he suggests is to hook the OnPaint override to perform rendering, invalidating it when necessary to update the frame. This method also requires you to make the following setting on your form:
this.Setstyle(Controlstyles.AllPaintingInWmPaint | Controlstyles.Opaque, true);

Share this post


Link to post
Share on other sites
Search for Jason Olsen on Google and he has written an article about that. He will most likely see this formum anyway.

Share this post


Link to post
Share on other sites
You're right Andrew, I will most likely see this forum :). My post about this can be found here. It's important to note that the post is a little out of date though. For performant games, there is another method that Tom Miller actually recommends now instead of the OnPaint method(and RenZimE was basically right on). Tom Miller recommends hooking into the Win32 messaging loop via P/Invoke. As a commenter says on the post above, it almost seems like a step back in my opinion. Oh well. Whoever said writing performant games was easy, right?

If you want to know how this is done, you can simply look at the sample framework used in the various samples with the latest SDK update. The sample framework uses the Win32 methodology.

I hope this helps :).

Share this post


Link to post
Share on other sites
i really dont think it is that much of a step back. you just have to write a few more lines of code, and you only have to do it once. Write your own DoEvents() method like that and forget about it, it will be like good old times ( the DoEvents() with no worries times ).

Share this post


Link to post
Share on other sites
Hi Jason, I was wondering wether you could give a quick shakedown of the 'performant' render loop that tom miller suggested? I'm forced to stick around the 9.0b SDK, so I can't browse the latest samples and these all use DoEvents... :(

Share this post


Link to post
Share on other sites
Hey Martaver, no problem. However, I believe it is a better learning experience to reverse engineer existing code so that you really understand what it is doing rather than having it spoon fed to you. In that vein, here is the source for the main SampleFramework class's main game loop from the 9.0c samples:


public void GameLoop()
{
// Not allowed to call this from inside the device callbacks or reenter
if (State.IsInsideDeviceCallback || State.IsInsideMainloop)
{
State.ApplicationExitCode = 1;
throw new InvalidOperationException("You cannot call this method from a callback, or reenter the method.");
}

// We're inside the loop now
State.IsInsideMainloop = true;

// If CreateDevice*() or SetDevice() has not already been called,
// then call CreateDevice() with the default parameters.
if (!State.WasDeviceCreated)
{
if (State.WasDeviceCreateCalled)
{
// It already called and failed, don't try again
State.ApplicationExitCode = 1;
throw new InvalidOperationException("The device was never created.");
}

try
{
CreateDevice(0, true, 640, 480, null);
}
catch
{
// Well, can't do anything, just set the exit code and rethrow
State.ApplicationExitCode = 1;
throw;
}
}

// Initialize() must have been called and succeeded for this function to proceed
// CreateWindow() or SetWindow() must have been called and succeeded for this function to proceed
// CreateDevice() or CreateDeviceFromSettings() or SetDevice() must have been called and succeeded for this function to proceed
if ( (!State.IsInited) || (!State.WasWindowCreated) || (!State.WasDeviceCreated) )
{
State.ApplicationExitCode = 1;
throw new InvalidOperationException("The framework was not initialized, cannot continue.");
}

bool gotMessage = false;
NativeMethods.Message msg;

// Get the first message
NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, NativeMethods.PeekMessageFlags.NoRemove);

while(msg.msg != NativeMethods.WindowMessage.Quit)
{
gotMessage = NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, NativeMethods.PeekMessageFlags.Remove);
if (gotMessage)
{
NativeMethods.TranslateMessage(ref msg);
NativeMethods.DispatchMessage(ref msg);
}
else
{
// Render a frame during idle time (no messages are waiting)
Render3DEnvironment();
}
}
}



and the NativeMethods you see being used above:


public class NativeMethods
{
#region Win32 User Messages / Structures
/// <summary>Show window flags styles</summary>
public enum ShowWindowFlags: uint
{
Hide = 0,
ShowNormal = 1,
Normal = 1,
ShowMinimized = 2,
ShowMaximized = 3,
ShowNoActivate = 4,
Show = 5,
Minimize = 6,
ShowMinNoActivate = 7,
ShowNotActivated = 8,
Restore = 9,
ShowDefault = 10,
ForceMinimize = 11,

}
/// <summary>Window styles</summary>
[Flags]
public enum WindowStyles: uint
{
Overlapped = 0x00000000,
Popup = 0x80000000,
Child = 0x40000000,
Minimize = 0x20000000,
Visible = 0x10000000,
Disabled = 0x08000000,
ClipSiblings = 0x04000000,
ClipChildren = 0x02000000,
Maximize = 0x01000000,
Caption = 0x00C00000, /* WindowStyles.Border | WindowStyles.DialogFrame */
Border = 0x00800000,
DialogFrame = 0x00400000,
VerticalScroll = 0x00200000,
HorizontalScroll = 0x00100000,
SystemMenu = 0x00080000,
ThickFrame = 0x00040000,
Group = 0x00020000,
TabStop = 0x00010000,
MinimizeBox = 0x00020000,
MaximizeBox = 0x00010000,
}

/// <summary>Peek message flags</summary>
public enum PeekMessageFlags : uint
{
NoRemove = 0,
Remove = 1,
NoYield = 2,
}

/// <summary>Window messages</summary>
public enum WindowMessage : uint
{
// Misc messages
Destroy = 0x0002,
Close = 0x0010,
Quit = 0x0012,
Paint = 0x000F,
SetCursor = 0x0020,
ActivateApplication = 0x001C,
EnterMenuLoop = 0x0211,
ExitMenuLoop = 0x0212,
NonClientHitTest = 0x0084,
PowerBroadcast = 0x0218,
SystemCommand = 0x0112,
GetMinMax = 0x0024,

// Keyboard messages
KeyDown = 0x0100,
KeyUp = 0x0101,
Character = 0x0102,
SystemKeyDown = 0x0104,
SystemKeyUp = 0x0105,
SystemCharacter = 0x0106,

// Mouse messages
MouseMove = 0x0200,
LeftButtonDown = 0x0201,
LeftButtonUp = 0x0202,
LeftButtonDoubleClick = 0x0203,
RightButtonDown = 0x0204,
RightButtonUp = 0x0205,
RightButtonDoubleClick = 0x0206,
MiddleButtonDown = 0x0207,
MiddleButtonUp = 0x0208,
MiddleButtonDoubleClick = 0x0209,
MouseWheel = 0x020a,
XButtonDown = 0x020B,
XButtonUp = 0x020c,
XButtonDoubleClick = 0x020d,
MouseFirst = LeftButtonDown, // Skip mouse move, it happens a lot and there is another message for that
MouseLast = XButtonDoubleClick,

// Sizing
EnterSizeMove = 0x0231,
ExitSizeMove = 0x0232,
Size = 0x0005,

}

/// <summary>Mouse buttons</summary>
public enum MouseButtons
{
Left = 0x0001,
Right = 0x0002,
Middle = 0x0010,
Side1 = 0x0020,
Side2 = 0x0040,
}

/// <summary>Windows Message</summary>
[StructLayout(LayoutKind.Sequential)]
public struct Message
{
public IntPtr hWnd;
public WindowMessage msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}

/// <summary>MinMax Info structure</summary>
[StructLayout(LayoutKind.Sequential)]
public struct MinMaxInformation
{
public System.Drawing.Point reserved;
public System.Drawing.Point MaxSize;
public System.Drawing.Point MaxPosition;
public System.Drawing.Point MinTrackSize;
public System.Drawing.Point MaxTrackSize;
}

/// <summary>Monitor Info structure</summary>
[StructLayout(LayoutKind.Sequential)]
public struct MonitorInformation
{
public uint Size; // Size of this structure
public System.Drawing.Rectangle MonitorRectangle;
public System.Drawing.Rectangle WorkRectangle;
public uint Flags; // Possible flags
}

/// <summary>Window class structure</summary>
[StructLayout(LayoutKind.Sequential)]
public struct WindowClass
{
public int Styles;
[MarshalAs(UnmanagedType.FunctionPtr)] public WndProcDelegate WindowsProc;
private int ExtraClassData;
private int ExtraWindowData;
public IntPtr InstanceHandle;
public IntPtr IconHandle;
public IntPtr CursorHandle;
public IntPtr backgroundBrush;
[MarshalAs(UnmanagedType.LPTStr)] public string MenuName;
[MarshalAs(UnmanagedType.LPTStr)] public string ClassName;
}
#endregion

#region Delegates
public delegate IntPtr WndProcDelegate(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam);
#endregion

#region Windows API calls
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[System.Runtime.InteropServices.DllImport("winmm.dll")]
public static extern IntPtr timeBeginPeriod(uint period);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, PeekMessageFlags flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool TranslateMessage(ref Message msg);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool DispatchMessage(ref Message msg);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern void PostQuitMessage(int exitCode);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
#if(_WIN64)
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int index, [MarshalAs(UnmanagedType.FunctionPtr)] WndProcDelegate windowCallback);
#else
private static extern IntPtr SetWindowLong(IntPtr hWnd, int index, [MarshalAs(UnmanagedType.FunctionPtr)] WndProcDelegate windowCallback);
#endif

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", EntryPoint="SetWindowLong", CharSet=CharSet.Auto)]
private static extern IntPtr SetWindowLongStyle(IntPtr hWnd, int index, WindowStyles style);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", EntryPoint="GetWindowLong", CharSet=CharSet.Auto)]
private static extern WindowStyles GetWindowLongStyle(IntPtr hWnd, int index);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("kernel32")]
public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("kernel32")]
public static extern bool QueryPerformanceCounter(ref long PerformanceCount);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool GetClientRect(IntPtr hWnd, out System.Drawing.Rectangle rect);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool GetWindowRect(IntPtr hWnd, out System.Drawing.Rectangle rect);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndAfter, int x, int y, int w, int h, uint flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ScreenToClient(IntPtr hWnd, ref System.Drawing.Point rect);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SetFocus(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool GetMonitorInfo(IntPtr hWnd, ref MonitorInformation info);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr MonitorFromWindow(IntPtr hWnd, uint flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern short GetAsyncKeyState(uint key);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SetCapture(IntPtr handle);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ReleaseCapture();

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowFlags flags);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool SetMenu(IntPtr hWnd, IntPtr menuHandle);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool DestroyWindow(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool IsIconic(IntPtr hWnd);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool AdjustWindowRect(ref System.Drawing.Rectangle rect, WindowStyles style,
[MarshalAs(UnmanagedType.Bool)]bool menu);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr windowHandle, WindowMessage msg, IntPtr w, IntPtr l);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr RegisterClass(ref WindowClass wndClass);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool UnregisterClass([MarshalAs(UnmanagedType.LPTStr)] string className, IntPtr instanceHandle);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", EntryPoint="CreateWindowEx", CharSet=CharSet.Auto)]
public static extern IntPtr CreateWindow(int exStyle, [MarshalAs(UnmanagedType.LPTStr)] string className, [MarshalAs(UnmanagedType.LPTStr)] string windowName,
WindowStyles style, int x, int y, int width, int height, IntPtr parent, IntPtr menuHandle, IntPtr instanceHandle, IntPtr zero);

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern int GetCaretBlinkTime();
#endregion

#region Class Methods
private NativeMethods() {} // No creation
/// <summary>Hooks window messages to go through this new callback</summary>
public static void HookWindowsMessages(IntPtr window, WndProcDelegate callback)
{
#if(_WIN64)
SetWindowLongPtr(window, -4, callback);
#else
SetWindowLong(window, -4, callback);
#endif
}
/// <summary>Set new window style</summary>
public static void SetStyle(IntPtr window, WindowStyles newStyle)
{
SetWindowLongStyle(window, -16, newStyle);
}
/// <summary>Get new window style</summary>
public static WindowStyles GetStyle(IntPtr window)
{
return GetWindowLongStyle(window, -16);
}

/// <summary>Returns the low word</summary>
public static short LoWord(uint l)
{
return (short)(l & 0xffff);
}
/// <summary>Returns the high word</summary>
public static short HiWord(uint l)
{
return (short)(l >> 16);
}

/// <summary>Makes two shorts into a long</summary>
public static uint MakeUInt32(short l, short r)
{
return (uint)((l & 0xffff) | ((r & 0xffff) << 16));
}

/// <summary>Is this key down right now</summary>
public static bool IsKeyDown(System.Windows.Forms.Keys key)
{
return (GetAsyncKeyState((int)System.Windows.Forms.Keys.ShiftKey) & 0x8000) != 0;
}
#endregion
}



If this is too much, just let us know and I'm sure someone can break it down for you (if they get here before me, that is :)). I hope this helps! Good luck!

Share this post


Link to post
Share on other sites
Ok.

I've been looking for a while now to find a great way to create a game loop using C#. The examples here gave me some fresh ideas, However, the real problem I'm experiencing is this. I'd like to use the managed DirectX environment in windowed mode (no need to build a new GUI system when you already have one). The loop works fine however when I access a menu, the loop freezes (as if inside the menu control a second loop is created and running). This is the code.

// local variables
int t_start, t_end; /* frame start and end times (in msec) */
int frame_number, loop_number;

form.Show ();
t_end = System.Environment.TickCount;
frame_number = 0;
loop_number = 0;
while (form.Created) {
t_start = System.Environment.TickCount;
if (t_start - t_end > TIME_BETWEEN_FRAMES) {
RenderFrame ();
t_end = System.Environment.TickCount;
frame_number++;
}
MessagePump.DoEvents ();
loop_number++;
Debug.WriteLine (string.Format ("{0} / {1}", frame_number, loop_number));
}

If a menu is opened (either a mainmenu or system menu), both loop_number and frame_number values will freeze.

The alternative, adding an invalidate command in the paint event also has it drawbacks. If many messages are fired the queue sort of gets stocked with non-paint messages and the frame rate will collapse. Looks like I'm stuck here.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!