Embedding Your MonoGame Game in a WinForms Control

This post is a follow-on to my previous entry: Bringing your XNA WinForms Controls to MonoGame + OpenGL.

In my previous entry, one reader brought up a good question: once you have your MonoGame-based WinForms control working, how do you run your game in it? This is a problem I had to solve in XNA during early development of my editor when I wanted to run my live game in it. For reasons that will follow, my solution involves replacing MonoGame/XNA’s Game class entirely for the embedded version, and making sure my game is designed to not depend on having a Game object present.

The WinForms series project with an embedded game.

The WinForms series project with an embedded game.

The Game Object

The Game object that you typically derive your project from is designed to create and manage its own native window and rendering environment, with all the guts hidden away so its usage appears to be platform-independent. This is no good for embedding in our new GraphicsDeviceControl, and we can’t get at the underlying OpenTK surface that we need to be able to properly render our game in the control either.

I have a theory that if you hacked through enough layers of MonoGame to expose access to the underlying OpenTKGameWindow or OpenTK.GameWindow object, then you could suppress the standalone window and grab the necessary Handle to instead show rendered contents in your control. Doing so would allow you to directly wrap your derived Game object. If you want to stick to stock MonoGame code, then it needs to be replaced. Hopefully you’ve architected your game to not depend on an explicit instance of the Game object being available everywhere. If you also haven’t signed on to XNA’s suggested model of IDrawables and IUpdateables, then you’re even better off and have less work to do.

If we’re going to replace Game, we need to look at its API and the services it provides:

Methods Properties
BeginDraw() Components
BeginRun() Content
Dispose() GraphicsDevice
Draw(GameTime) InactiveSleepTime
EndDraw() IsFixedTimeStep
EndRun() IsMouseVisible
Exit() LaunchParameters
Game() Services
Initialize() TargetElapsedTime
LoadContent() Window
ResetElapsedTime()
Run()
Run(GameRunBehavior)
RunOneFrame()
SuppressDraw()
Tick()
UnloadContent()
Update(GameTime)

Roughly, this laundry list of an API provides the following services for us:

  • Main game loop with separate taps for Update and Draw
  • Timekeeping and framerate control
  • Graphics device management
  • Content management
  • Component management (IGameComponents, related to IDrawable and IUpdateable)

The amount that you depend on XNA infrastructure to model your game will determine the amount of this API that needs to be duplicated. I’ve boldfaced the minimum amount of API that is necessary to implement any game. Some other entries like IsFixedTimeStep should also be provided to influence the frame rate, but other services like the ContentManager can easily be provided directly within your engine.

Replacing Game with GameControl

The following implementation represents the minimum viable replacement for the Game API, and derives from GraphicsDeviceControl just like any other type of rendered control you would write.

public abstract class GameControl : GraphicsDeviceControl
{
    GameTime _gameTime;
    Stopwatch _timer;
    TimeSpan _elapsed;

    protected override void Initialize ()
    {
        _timer = Stopwatch.StartNew();

        Application.Idle += delegate { GameLoop(); };
    }

    protected override void Draw ()
    {
        Draw(_gameTime);
    }

    private void GameLoop ()
    {
        _gameTime = new GameTime(_timer.Elapsed, _timer.Elapsed - _elapsed);
        _elapsed = _timer.Elapsed;

        Update(_gameTime);
        Invalidate();
    }

    protected abstract void Update (GameTime gameTime);
    protected abstract void Draw (GameTime gameTime);
}

GraphicsDeviceControl already gives us entry points for everything we need: a GraphicsDeviceService, Initialize, Update, and Draw. We only do minimum tracking of game time, and the frame rate this runs at is entirely determine by how quickly the underlying Windows message loop idles. Depending on your needs, this may be sufficient, but chances are you’ll need to implement something more sophisticated. It’s worth noting that by hooking the game loop to the Application.Idle event, the game is running asynchronously with the rest of your UI.

You can use MonoGame’s Game.cs implementation as a reference for replacing services. Or you could just steal huge swathes of it outright.

Extending Game(Control)

Here’s a basic implementation of a game as if it were a normal MonoGame project. Instead of extending from Game, we’re extending from GameControl.

public class Game1 : GameControl
{
    private Engine _engine;

    protected override void Initialize ()
    {
        base.Initialize();

        _engine = new Engine(GraphicsDeviceService);
        _engine.Initialize();
    }

    protected override void Update (GameTime gameTime)
    {
        _engine.Update(gameTime);
    }

    protected override void Draw (GameTime gameTime)
    {
        _engine.Draw(gameTime);
    }
}

This is where your game’s architecture becomes interesting. I’ve intentionally tried to keep the code in Game1 as minimal as possible, delegating all work to a separate, independent game engine.

public class Engine
{
    private GameServiceContainer _services;
    private GraphicsDevice _graphicsDevice;
    private ContentManager _content;
    private SpriteBatch _spriteBatch;

    private Texture2D _xor;
    private Vector2 _xorPosition;
    private Vector2 _xorDirection;

    public Engine (IGraphicsDeviceService graphics)
    {
        _services = new GameServiceContainer();
        _services.AddService(typeof(IGraphicsDeviceService), graphics);

        _graphicsDevice = graphics.GraphicsDevice;

        _content = new ContentManager(_services);
        _spriteBatch = new SpriteBatch(_graphicsDevice);
    }

    public void Initialize ()
    {
        _xor = BuildXorTexture(_graphicsDevice, 6);
        _xorDirection = new Vector2(.5f, .5f);
    }

    public void Update (GameTime gameTime)
    {
        float limitX = _graphicsDevice.Viewport.Width - _xor.Width;
        float limitY = _graphicsDevice.Viewport.Height - _xor.Height;

        if (_xorPosition.X >= limitX && _xorDirection.X > 0)
            _xorDirection.X *= -1;
        if (_xorPosition.X <= 0 && _xorDirection.X < 0)
            _xorDirection.X *= -1;
        if (_xorPosition.Y >= limitY && _xorDirection.Y > 0)
            _xorDirection.Y *= -1;
        if (_xorPosition.Y <= 0 && _xorDirection.Y < 0)
            _xorDirection.Y *= -1;

        _xorPosition += _xorDirection;
    }

    public void Draw (GameTime gameTime)
    {
        _graphicsDevice.Clear(Color.Teal);

        _spriteBatch.Begin();
        _spriteBatch.Draw(_xor, _xorPosition, Color.White);
        _spriteBatch.End();
    }

    private static Texture2D BuildXorTexture (GraphicsDevice device, int bits)
    {
        Texture2D tex = new Texture2D(device, 1 << bits, 1 << bits);
        Color[] data = new Color[tex.Width * tex.Height];

        for (int y = 0; y < tex.Height; y++) {
            for (int x = 0; x < tex.Width; x++) {
                float lum = ((x << (8 - bits)) ^ (y << (8 - bits))) / 255f;
                data[y * tex.Width + x] = new Color(lum, lum, lum);
            }
        }

        tex.SetData(data);
        return tex;
    }
}

The Engine class represents the implementation of our very interesting game: Bouncing XOR Texture.

I have implemented several services in the engine that were previously provided by the Game object, such as the GameServiceContainer and the ContentManager. I’m not using IGameComponent infrastructure, so I don’t need to worry about the absence of automagically self-updating IUpdatables or self-drawing IDrawables. But it would still be okay to use them if you spent the effort to properly implement support for them in your GameControl.

The Stand-Alone Game

Now that the game is implemented in terms of GameControl, let’s see what a stand-alone implementation would look like.

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private Engine _engine;

    public Game1 ()
    {
        _graphics = new GraphicsDeviceManager(this);
    }

    protected override void Initialize ()
    {
        base.Initialize();

        _engine = new Engine(_graphics);
        _engine.Initialize();
    }

    protected override void Update (GameTime gameTime)
    {
        _engine.Update(gameTime);
        base.Update(gameTime);
    }

    protected override void Draw (GameTime gameTime)
    {
        _engine.Draw(gameTime);
        base.Draw(gameTime);
    }
}

The implementation is almost identical to the GameControl version, save for creating a GraphicsDeviceManager as my GraphicsDeviceService. The Engine class being used is identical to the one above.

By keeping your game logic out of your Game class, it’s easy to drive your game from another game manager if necessary, such as a GameControl used in your custom level editor. The amount of effort needed to implement a compatible GameControl will vary from project to project, and you may be willing to accept a reduced implementation for your embedded use case. On the other hand, if you’ve already invested a large amount of time developing your game with your core Game object tightly intertwined through your codebase, then this approach will not be viable to you.

Update: MonoGame keyboard input does not work “out of the box” with GraphicsDeviceControl solutions. If you want to keep using KeyboardState objects, check this separate entry on keyboard input.

A complete sample project is available using the download link below.

GameControl Demo
40.17 kB - Downloaded 4832 times

About jaquadro

I am a full time software developer and part time game and game tooling developer.
This entry was posted in Tutorials and tagged , , , . Bookmark the permalink.

40 Responses to Embedding Your MonoGame Game in a WinForms Control

  1. John says:

    Hello jaquadro,

    I’ve noticed that his method consumes a [LOT] of CPU. In order to make it use less resources when not focused I’ve just made a simple Thread.Sleep:

    if (!this.Focused)
    {
    Thread.Sleep(50);
    }

    This is good for when the control is not focused but when the control is focused it consumes a lot of CPU (about 100% of one core). Do you have any better idea to solve this?

    Thanks

    • John says:

      I’ve made it a litle better:

      protected override void Draw()
      {
      if (!this.Focused)
      {
      Thread.Sleep(50);
      }
      else
      {
      Thread.Sleep(1);
      }

      Draw(gameTime);
      }

      By doing this it consumes only 2% of the cpu (when focused) and around 0% when not, but maybe is not the most elegant solution.

    • jaquadro says:

      Thanks for the tip! I’ll have to play with this a little more when I get a moment.

      Slowing down the update loop when the window is out of focus is a great idea. Or at least having the option to do so. As for when it is in focus, that’s a little trickier to think about. This uses the prescribed method of driving a GLControl, which is hooking up to Application.Idle. That also causes it to run the update and draw loop as fast as possible and basically burn up a core. I’m curious what your Sleep(1) translates to in terms of an FPS drop. The granularity of Sleep is about 10ms, so I’d expect your corresponding FPS to be around 1-2.

      • John says:

        Another thing I would like to ask you is how are you handling the properties in a propertygrid of a Vector2 and Color (using the MonoGame.Framework). They don’t seem to expand like they used to using XNA.

        • jaquadro says:

          I’m not, basically. PropertyGrid is a very flexible control that will try to display whatever you give it, to the best of its ability.

          Microsoft probably provided the necessary TypeConverters to deal with this, even though they did not give us a first-party XNA control. It would probably not be much work to implement them. More info on the topic can be found here.

        • John says:

          Ok, thanks for the info

  2. Rafael says:

    Cant compile your example.. dont know why…
    assembly reference problems in your forms =(

    • jaquadro says:

      It’s probably the MonoGame and OpenTK assemblies — remove the references and re-add your own copies. If it’s something else, can you be more specific?

      • Rafael says:

        ty for your fast reply..
        in winformsGraphicDevice subproject

        Error 1 Invalid Resx file. Could not load file or assembly ‘System.Runtime.Serialization, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e’ or one of its dependencies. The system cannot find the file specified. Line 126, position 5. C:\Users\user\Downloads\mgtkWinFormsDemo2 (1)\WinFormsGraphicsDevice\MainForm.resx 126 5 WinFormsGraphicsDevice

        • jaquadro says:

          Are you playing with Silverlight by any chance? The 2.0.5.0 is a bit suspect. Seems like a framework problem regardless. You could also try out the examples in this repository: https://github.com/jaquadro/MonoGame-WinFormsControls — see if you can run them as-is before trying to integrate into something else.

        • Rafael says:

          Same problem… any chance that is a problem with visual C#? (using visual c# 2010, not visual studio)
          =(

        • jaquadro says:

          VC#2010 should be fine. I was actually able to reproduce your error by deleting Resources.resx in the Properties directory, which causes causes that error because it’s referenced by MainForm.resx. Make sure you’ve got Resources.resx in your project and it’s set to EmbeddedResource.

        • Rafael says:

          yeah, its setted to embedded profile.. realy dont know why i got this error from your files….
          and i cant open your forms with GUI designer of visual c#….

        • jaquadro says:

          I apologize, I just tried opening the project in actual Visual C# 2010 Express, and it’s doing something to the project to make it unusable (the error you’re reporting). I’m not quite sure what it’s doing yet, but I’m looking into it.

        • jaquadro says:

          You’re sure you’re running into the same problem with the MonoGame-WinFormsControls project I linked you to? That runs fine for me in VC#2010 Express and it’s better example code anyway. There’s a few different example projects in that solution you can try building and running.

          It’s looking more like there’s something just broken with the project file that’s included with this post.

  3. Rafael says:

    theres no easy solution for this buddy? in my xna code i just made something like this:

    initialize()
    {
    base.Initialize();
    SysWinForms.Form gameWindowForm =(SysWinForms.Form)SysWinForms.Form.FromHandle(this.Window.Handle);
    gameWindowForm.Shown += new EventHandler(gameWindowForm_Shown);
    }

    void gameWindowForm_Shown(object sender, EventArgs e)
    {
    ((SysWinForms.Form)sender).Hide();
    }

    draw()
    {
    this.GraphicsDevice.Present(null, null, Global.myForm.PanelHandle);
    base.Draw(gameTime);
    }

    but in monogame my SysWinForms.Form.FromHandle(this.Window.Handle); return null =(

    • jaquadro says:

      Does Window.Handle return something besides null? The OpenTKGameWindow implementation could just be off. OpenTK creates an awkward window abstraction that doesn’t give up direct access to a handle.

      Whether or not that works, you still need to do some reoganization. MonoGame does not implement that overload of GraphicsDevice.Present. You need another way of channeling your game’s window handle to your GraphicsDeviceControl, assuming that it’s compatible.

      I haven’t thought of your approach for old XNA games, so I think there’s some value in playing around with this a little more and seeing if we can use Game classes directly after all.

    • jaquadro says:

      Right, I see what you’re doing now. You made an assumption in your code that XNA would create your Game’s backing window from the Form class. OpenTK never derives from System.Windows.Form. It creates its own ugly window class, and you’re getting a handle to that instead. You could try casting to an OpenTK.INativeWindow instead and setting the Visible property and hooking up to the VisibleChanged event. Well, that would also require a way to even get an INativeWindow from your handle, which I don’t know how to do.

      That issue aside, without a way to direct your output to another form element, I don’t think you can drive your panel from within Game/MonoGame. On the other hand, trying to run a Game from another control results in two event loops fighting. Ugh. It’s messy and I haven’t had much luck creating anything workable.

      • Rafael says:

        fuck off =( …. i really cant run your example… just trying to port my engine to mono game =(…
        ty for your assist =)

  4. termoventilador says:

    To implement the frame rate capping?
    like fixed timestep?
    looking at monogame implementation of game.cs left me really confused hehe.

    Can you give me some light on that subject?
    Thanks, good article.

  5. rtrawr says:

    This is great, thank you so much. I’m currently trying to port this over too Xamarin too use GTK Sharp controls for cross platform use. Do you have any advice in doing so?

  6. Mip says:

    For whatever reason, I’m just getting a black (non-rendered) window when I run the built exe. I can see the winform controls, but the graphic portion does not render. (VS 2013)

  7. Jens Eckervogt says:

    Hello dear nice trick but for me doesn’t work yet 🙁

    If I use WinForms than it throws error NullReferenceException cause it doesn’t know for latest version of MonoGame 3.6

    It sees incompatible to “GraphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.HiDef, parameters);”

    How do I fix?

    Thanks

  8. Idealny post, ogólnie to ma sens, jednakże w kilku kwestiach bym polemizowała.
    Z pewnością ten blog może liczyć na uznanie.
    Z pewnością tu wrócę.

  9. Can I write a game engine using this feature?

Leave a Reply

Your email address will not be published. Required fields are marked *