Custom XNA Game loop in Windows
Asked Answered
B

1

11

I'm trying to figure out how to manage the whole game loop manually in a Windows game, without using the regular Game Microsoft.Xna.Framework.Game class.

The reason for this is using the regular Game class causes some stuttering in my game. Not much, but because of the specific nature of the game it is still quite visible.

After trying a bunch of different settings (vsync, fixedtimestep, various framerates etc.) I decided to try write my own Game class to have full control of the timing. I am not sure that will fix it, but at least this way I have full control.

Basically I need to:

  1. Set up the game window
  2. In a loop: Do all rendering as usual, and then flush the result to the screen, manage backbuffers etc.

Anyone knows how to do this? It sounds quite easy in fact, but could not find any documentation on how to do it.


Not sure what I am doing wrong, but I have the following code (just for testing, timing will be handled differently), and the loop will run for a little while then stop. Once I pass my mousepointer over the window the loop will run for a little while again.

    private void Application_Idle(object pSender, EventArgs pEventArgs)
    {
        Thread.Sleep(500);
        //Message message;
        //while (!PeekMessage(out message, IntPtr.Zero, 0, 0, 0))
        {
            gametime.update();
            Update(gametime);
            Draw(gametime);
            GraphicsDevice.Present();
        }
    }

If enabling the "while PeekMessage", the loop will run continuously, but ignoring the sleep and also stopping when the mouse is moving over the window. Not sure what is going on here...

I think optimally I would just want to do something simple like this in the main render loop:

    while (alive)
    {
      Thread.Sleep(100);
      gametime.update();
      Update(gametime);
      Draw(gametime);
      GraphicsDevice.Present();
    }

But in this case the window remains blank, as it seems the window is not actually being redrawn with the new content. I tried a form.Refresh(), but still no go... Any ideas?

Bucella answered 15/6, 2011 at 17:23 Comment(4)
The XNA game is really not the source of your lag. Impossible.Scotland
I strongly suspect that your problem has nothing to do with the Game class itself and that replacing it is not the correct solution.Castleman
Yet, I cannot explain this stuttery behavior. I checked, and there's no garbage collection going on, yet, at random intervals the measured time between each time draw is called is varying, even though the complexity of what is drawn remains constant. I tried a lot of different approaches, this is my last ditch effort...Bucella
@VincentKoeman: I concur. My game engine runs 4km x 4km scenes at 300fps, no stutter whatsoever, small change that it is Microsoft.XNA.Game.Portaltoportal
M
7

(added xbox information)

for windows you Basically need to create a Form and Show it, then store its handle and the form itself. Using this handle you can create a GraphicsDevice. Then you hook Application.Idle to your own function that calls your update and render. For example

public class MyGame
{
public Form form;
public GraphicsDevice GraphicsDevice;

public MyGame()
{
    form = new Form();
    form.ClientSize = new Size(1280, 1024);
    form.MainMenuStrip = null;

    form.Show();
}

public void Run()
{      
    PresentationParameters pp = new PresentationParameters();
    pp.DeviceWindowHandle = form.Handle;

    pp.BackBufferFormat = SurfaceFormat.Color;
    pp.BackBufferWidth = 1280;
    pp.BackBufferHeight = 1024;
    pp.RenderTargetUsage = RenderTargetUsage.DiscardContents; 
    pp.IsFullScreen = false; 

    pp.MultiSampleCount = 16;

    pp.DepthStencilFormat = DepthFormat.Depth24Stencil8;

    GraphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter,
                                              GraphicsProfile.HiDef,
                                              pp);
    Application.Idle += new EventHandler(Application_Idle);
    Application.Run(form);
}

 private void Application_Idle(object pSender, EventArgs pEventArgs)
 {
    Message message;
    while (!PeekMessage(out message, IntPtr.Zero, 0, 0, 0))
    {
        /* Your logic goes here
         Custom timing and so on
        Update();
        Render();
        */
    }

 }

 void Render()
 {
      GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 1, 0);
      //Your logic here.
     GraphicsDevice.Present();
 }
    [StructLayout(LayoutKind.Sequential)]
    private struct Message
    {
        public IntPtr hWnd;
        public int msg;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
        public Point p;
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    [SuppressUnmanagedCodeSecurity, DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint
        messageFilterMin, uint messageFilterMax, uint flags);
}

EDIT 1

For xbox you may just be able to place your own custom run function with your game loop in a throttled while true loop. Inside that run outside the top of the while true you will probably have to do the graphics device initialization and verification with IntPtr.Zero as your handle

EDIT 2 i use something like this ( got from http://www.koonsolo.com/news/dewitters-gameloop/ )

        private long nextGameTick;

        private Stopwatch stopwatch;

        const int ticksPerSecond = 60;
        const int skipTicks = 1000 / ticksPerSecond;
        private const int maxSkip = 10;
        `constructor 
         stopwatch = Stopwatch.StartNew();

        nextGameTick = stopwatch.ElapsedMilliseconds; 

        `loop 
        int loops = 0;
        long currentTick = stopwatch.ElapsedMilliseconds;
        while ( (ulong)(currentTick - nextGameTick) > skipTicks && loops < maxSkip)
        {
            Update(16.667f);
            nextGameTick += skipTicks;
            loops++;

        }

        PreRender();
        Render();
        PostRender();

EDIT 3

Creating a content manager was a little more work, but still managable. You need to create a class that implements IServiceProvider. This class takes a GraphicsDevice in its constructor in order to create the next class the implements IGraphicsDeviceProvider. in addition I implement GetService like this

    //in implementer of IServiceProvider
    public object GetService ( Type serviceType )
    {
        if ( serviceType == typeof ( IGraphicsDeviceService ) )
        {
            return myGraphicsService;
        }

        return null;
    }

For convenience i also add a method to the class to create and return managers

    //in implementer of IServiceProvider
    public ContentManager CreateContentManager( string sPath )
    {

        ContentManager content = new ContentManager(this);

        content.RootDirectory = sPath;

        return content;

    }

In addition i create a class that implements IGraphicsDeviceService and takes a reference to my GraphicsDevice. then I create a property and field in it like so

    //in implementer of IGraphicsDeviceService 
    private GraphicsDevice graphicsDevice;
    public GraphicsDevice GraphicsDevice
    {
        get
        {
            return graphicsDevice;
        }
    }

So the call ends up being somehting like

MyServiceProvider m = new MyServiceProvider(graphicsDevice);
ContentManager content = m.CreateContentManager("Content");

where

MyServiceProvider(GraphicsDevice graphicsDevice)
{
      myGraphicsService = new MyGraphicsDeviceService(graphicsDevice);
}

MyGraphicsDeviceService(GraphicsDevice gfxDevice)
{
     graphicsDevice = gfxDevice;
}

-Sorry for fragmenting the code around but its not something i wrote too recently so im having difficulty remembering parts.

EDIT 4

i had an odd case with my custom game i just remembered when i new the Form for it i had to bind

    private void IgnoreAlt(object pSender, KeyEventArgs pEventArgs)
    {
        if (pEventArgs.Alt && pEventArgs.KeyCode != Keys.F4)
            pEventArgs.Handled = true;

    }

to

    form.KeyUp += IgnoreAlt;
    form.KeyDown += IgnoreAlt;

otherwise i got some horrible stalls.

Misguided answered 15/6, 2011 at 17:58 Comment(11)
I think for xbox you need to call the graphicsdevice constructor with IntPtr.Zero or something to get it to create a device since you dont have access to System.Windows.Forms... not certain thoughMisguided
Thank you, the code you provided is very useful. I have the game loop up and running now, however, I wonder how to accurately time each update? I need to set it up so that the render call is executed exactly every x milliseconds (or as soon as possible after rendering previous frame). The above code seems to simply render as fast as possible. I tried to add a Thread.sleep but that did not work well...Bucella
Added a new edit about loop time for you. couldnt fit it in commentMisguided
Thanks, that part was clear though. It turned out it was PeekMessage which caused trouble, as mouseevents triggered updates when there should be none. I just removed that inner loop, then it seems like it's working as intended. :) How did you deal with making your own ContentManager by the way, to load graphics and other data? Normally the Game class handles this through Content...Bucella
@Bucella Im new to using these sites, does it notify you when i update the answer or do i need to post a comment?Misguided
I did not enable notifications, so I am not sure. I suppose you need to post a new answer or comment. I'll enable notifications now, and we can check. :)Bucella
Thank you for the ContentManager code, it works like a charm :)Bucella
Hmm i cant commment on your answer post cuz im not high enough rep, but on windows environment you need to have the PeekMessage or windows will kill your app. (it is essentially your While(alive) )Misguided
Also using sleep is not recommended as it is indeterminate and blocks the whole threadMisguided
@Bucella I Used nuclex framework input to do my input it was fairly simple. Im not sure if xna's default input works in a custom game unless you do some pretty fancy reflection stuff.Misguided
Thank you again for all your help. I found no ill effects from not using PeekMessage though, the Application.Run(form) seems to keep the application alive already.Bucella

© 2022 - 2024 — McMap. All rights reserved.