How To: Minimal Setup

Gets a MonoGame game running inside an Avalonia window with the fewest possible steps.

Reference files: Minimal/MinimalGame.cs · Minimal/MinimalWindow.axaml · Minimal/MinimalWindow.axaml.cs


1. Add the NuGet package

dotnet add package Thunder.MonoGame.Avalonia

Three names, one library.

Name Value Used where
NuGet package Thunder.MonoGame.Avalonia dotnet add package
Assembly name MonoGame.Framework clr-namespace: in XAML (mirrors MonoGame's own assembly name so existing using statements compile unchanged)
C# namespace Thunder.MonoGame.Avalonia.Controls using directive in code-behind

2. Add required .csproj flags

<PropertyGroup>
  <!-- Required by MonoGame's OpenGL bindings (unsafe pointer arithmetic in GL dispatch tables). -->
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

  <!-- Required for COM interop on Windows (SDL2 window handles).
       Omitting this causes a build error on Windows:
       "Type library exporter is not supported in .NET Core". -->
  <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
</PropertyGroup>

3. Declare the XAML namespace

Add the mg namespace to any Avalonia Window or UserControl that hosts the game:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mg="clr-namespace:Thunder.MonoGame.Avalonia.Controls;assembly=MonoGame.Framework"
        x:Class="MyApp.MainWindow">
  ...
</Window>

Note the assembly name is MonoGame.Framework, not Thunder.MonoGame.Avalonia.


4. Place MonoGameControl in XAML

<mg:MonoGameControl x:Name="GameHost" />

A single MonoGameControl fills whatever space it is given. Use a Panel root (see 04 — UI Overlay) to layer Avalonia controls on top.


5. Wire the game

Pattern A — code-behind

Create the game in the constructor and assign it via SetGame():

public partial class MainWindow : Window
{
    private readonly MyGame _game;

    public MainWindow()
    {
        InitializeComponent();
        _game = new MyGame();
        GameHost.SetGame(_game);  // shorthand for GameHost.Game = _game
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        _game.Dispose();
    }
}

Pattern B — MVVM

The ViewModel owns the game instance; XAML binds the Game property:

// ViewModel
public class MainViewModel
{
    public MyGame Game { get; } = new MyGame();
}
<!-- XAML -->
<mg:MonoGameControl Game="{Binding Game}" />

Dispose the game in the window's OnClosed handler regardless of which pattern you use.


6. Set timing flags in the game constructor

public class MyGame : Game
{
    private readonly GraphicsDeviceManager _graphics;

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

MonoGameControl automatically sets IsFixedTimeStep = false and GraphicsDeviceManager.SynchronizeWithVerticalRetrace = false before starting the game. You do not need to set these yourself — Avalonia drives the render loop, so MonoGame's internal governor and VSync wait must both be disabled or they fight the render callback.


7. Do not call game.Run()

MonoGameControl calls game.Run() automatically on its first rendered frame, after the OpenGL context is current. Calling Run() manually causes MonoGame to double-initialise (two Initialize() + LoadContent() calls).


8. Dispose in OnClosed

protected override void OnClosed(EventArgs e)
{
    base.OnClosed(e);
    _game.Dispose();
}

Dispose() stops the audio thread, releases OpenGL buffers, and closes the SDL window. Omitting it can leave the process alive after the Avalonia window closes because MonoGame's audio thread is still running.

latest ▼