In this article, Andrew Lock details the process of porting a classic Microsoft XNA 3.1 game from 2009 to MonoGame running on .NET 8. He covers technical steps, challenges, and solutions encountered during the migration.

Converting a Microsoft XNA 3.1 Game to MonoGame on .NET 8: A Step-by-Step Porting Journey

Author: Andrew Lock


Introduction

This article explores the process of porting a Microsoft XNA Framework 3.1 game—originally written in 2009—to MonoGame on .NET 8. Andrew Lock shares his hands-on experience, documenting the steps, issues, and solutions involved in bringing an old game back to life with modern tooling.

Background: Why Port an XNA Game?

Andrew was inspired by a Merge Conflict podcast episode discussing MonoGame, which implements Microsoft XNA APIs for contemporary platforms. Reminiscing about his own XNA-based clone of the game “Trash” (itself inspired by Nintendo’s Dr. Mario), Andrew wondered how feasible it would be to run his old codebase on modern frameworks. The original code targeted XNA 3.1 and .NET Framework 3.5, while MonoGame is built around XNA 4.0 and latest versions of .NET.

Approach Overview

The upgrade plan involved:

  1. Creating a MonoGame sample application.
  2. Replacing sample code with original project files.
  3. Iteratively resolving build and runtime issues.

The focus was to make the game run on current technology, not to refine or distribute it.

Detailed Porting Steps

1. Setting Up the New Project

  • Used the updated .slnx solution format for .NET projects.
  • Created a cross-platform MonoGame desktop app using mgdesktopgl template.
  • Added the project to the solution and verified that the default app (the classic Cornflower Blue screen) runs.
# Create and convert solution

dotnet new sln
dotnet sln migrate
rm *.sln

# Create MonoGame project in Trash subfolder

dotnet new mgdesktopgl --output Trash
dotnet sln add ./Trash/

2. Transferring Game Files

  • Copied all C# code files and content (such as .wav and .png assets) except:
    • The legacy .csproj project file (used MonoGame’s instead).
    • The old AssemblyInfo.cs (attributes handled by SDK now).

Compiling: Version Issues

  • Only one initial build error: Use of GraphicsDevice.RenderState not found in MonoGame/XNA 4.0.
    • Solution: Replace RenderState with RasterizerState as per XNA 4.0 changes.
    • Example fix:

      // XNA 3.1:
      bool defaultUseScissorTest = spriteBatch.GraphicsDevice.RenderState.ScissorTestEnable;
      // MonoGame/XNA 4.0:
      bool defaultUseScissorTest = spriteBatch.GraphicsDevice.RasterizerState.ScissorTestEnable;
      
  • MonoGame maintains impressive compatibility with XNA APIs, enabling mostly seamless compilation.

  • Notable limitation: MonoGame omits some XNA namespaces (like .Storage and .Net), for cross-platform reasons.

3. Content Pipeline Migration

Handling Audio Content (XACT to MonoGame)

  • On first run, the game crashed due to missing .xgs (XACT Game Studio) files.
  • Instead of porting XACT directly (unsupported by MonoGame), switched to MonoGame’s sound APIs:
    • Used SoundEffect, SoundEffectInstance, and Song types for audio.
    • Relied on the MonoGame Content Builder (MGCB) to import and process assets.
  • Ran the MGCB content editor:

     dotnet mgcb-editor
    
  • Most assets (e.g., .wav, images) processed fine, but .xap (XACT Audio Project) was unsupported.
  • Examined .xap content manually, rewrote usage to work with direct sound files.

      SoundEffect effect = Content.Load<SoundEffect>("file.wav");
      effect.Play();
    

4. Solving Font Issues

  • Build failed on a missing font: Narkisim, referenced in a SpriteFont.
  • Discovered Narkisim is part of the Windows Hebrew language pack.
  • Solution: Installed the required font using Windows Update, restoring successful content pipeline builds.

5. Handling RasterizerState and Scissor Test Changes

  • The next runtime error: attempting to set ScissorTestEnable on the default RasterizerState object, which is immutable in XNA 4.0/MonoGame.
  • Correction: Create a new RasterizerState object and assign it to GraphicsDevice.

      spriteBatch.GraphicsDevice.RasterizerState = new RasterizerState { ScissorTestEnable = true };
    
  • Additional fix: Must set the RasterizerState before calling SpriteBatch.Begin(), or better yet, pass it as a parameter.

      spriteBatch.Begin(rasterizerState: new RasterizerState { ScissorTestEnable = true });
    

6. Verifying the Final Result

  • With all above issues resolved, the game launched and functioned correctly in MonoGame under .NET 8, including proper scissor testing and working gameplay.
  • The source was made available on GitHub, though Andrew notes it’s not actively maintained or polished.

Summary and Takeaways

Porting an old Microsoft XNA Framework game (targeting .NET Framework 3.5 and XNA 3.1) to MonoGame running on .NET 8 proved surprisingly straightforward. Key migration challenges revolved around breaking changes in the XNA API, content pipeline differences, sound file handling, and font requirements. MonoGame’s strong compatibility with the XNA structure enabled most original code to work with limited updates. The project highlights the durability and adaptability of Microsoft’s developer platforms over the years.

Resources

This post appeared first on “Andrew Lock’s Blog”. Read the entire article here