Tutorial 83: Migrating from MonoGame/FNA to CNA

CNA — C++ XNA 4.0 reimplementation

Why migrate?

CNA offers C++23 performance, no garbage collector pauses, and the same XNA 4.0 API surface you already know from MonoGame or FNA. Games ported from MonoGame/FNA keep their architecture intact — the Game loop, SpriteBatch calls, Effect hierarchy, and math types are all preserved. Only the language changes from C# to C++.

Namespace changes

CNA uses the same Microsoft::Xna::Framework namespace hierarchy as MonoGame, expressed in C++ :: notation instead of C# . notation.

C# NamespaceC++ Namespace
Microsoft.Xna.FrameworkMicrosoft::Xna::Framework
Microsoft.Xna.Framework.GraphicsMicrosoft::Xna::Framework::Graphics
Microsoft.Xna.Framework.AudioMicrosoft::Xna::Framework::Audio
Microsoft.Xna.Framework.InputMicrosoft::Xna::Framework::Input
Microsoft.Xna.Framework.MediaMicrosoft::Xna::Framework::Media

C# to C++ type mapping

C# TypeC++ CNA TypeNotes
intintcs (= int32_t)sharp-runtime alias
floatSingle (= float)or just use float
doubledoublesame
boolboolsame
bytebytecs (= uint8_t)sharp-runtime alias
stringstd::stringno implicit conversions
List<T>ListCS<T>from sharp-runtime, or std::vector<T>
nullnullptrpointer null
? nullablestd::optional<T>or raw pointer

Memory management

MonoGame relies on the .NET GC. In CNA you manage lifetimes explicitly. Use std::unique_ptr<T> for owned resources (most game objects), std::shared_ptr<T> when shared ownership is genuinely needed, and pass by reference to avoid unnecessary copying. All CNA resource classes (Texture2D, VertexBuffer, etc.) implement RAII — they release GPU resources in their destructor so you never need to call a separate Dispose().

Content pipeline differences

MonoGame uses .xnb binary files produced by the Content Pipeline. CNA does not use XNB — load assets directly from source files:

  • Textures: Texture2D("assets/logo.png", gd) — PNG/JPG loaded via SDL3_image
  • Models: Model::Load("assets/house.obj", gd) — OBJ/FBX via the model loader
  • Fonts: JSON-based SpriteFont descriptor instead of .spritefont XML + XNB

No ContentManager.Load<T>() XNB path is needed.

Audio (SoundEffect same, XACT now real)

SoundEffect and SoundEffectInstance APIs are identical to MonoGame. SoundEffect::FromFile("sound.wav") loads OGG/WAV via SDL3_mixer. Song/ MediaPlayer streaming is also fully implemented via SDL3_mixer. XACT (.xap-authored AudioEngine/SoundBank/WaveBank/Cue) is now a real, ~97% functional runtime with a genuine .xgs/.xsb/.xwb parser — MonoGame games that use XACT do not need to be rewritten to use SoundEffect directly, though it remains the simpler option for one-off sounds.

Missing APIs

A few MonoGame extensions are not present in CNA:

  • VideoPlayer — partial (see the Video Playback tutorial)
  • StorageContainer — use standard C++ file I/O instead
  • MessageBox.Show — no equivalent; use SDL_ShowMessageBox if needed

Side-by-side: C# MonoGame vs C++ CNA

// ===== C# MonoGame =====
// using Microsoft.Xna.Framework;
// using Microsoft.Xna.Framework.Graphics;
//
// public class Player {
//     private Texture2D sprite;
//     private Vector2 position;
//
//     public Player(ContentManager content) {
//         sprite = content.Load<Texture2D>("player");
//         position = new Vector2(100, 200);
//     }
//
//     public void Update(GameTime gt) {
//         float dt = (float)gt.ElapsedGameTime.TotalSeconds;
//         position.X += 100.0f * dt;
//     }
//
//     public void Draw(SpriteBatch sb) {
//         sb.Draw(sprite, position, Color.White);
//     }
// }

// ===== C++ CNA equivalent =====
#include "Microsoft/Xna/Framework/Graphics/Texture2D.hpp"
#include "Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp"
#include "Microsoft/Xna/Framework/Vector2.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"

using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;

class Player {
public:
    Player(GraphicsDevice& gd)
        : sprite_("assets/player.png", gd)   // direct load, no ContentManager
        , position_(100.0f, 200.0f)
    {}

    void Update(const GameTime& gt) {
        float dt = static_cast<float>(gt.ElapsedGameTime.TotalSeconds());
        position_.X += 100.0f * dt;
    }

    void Draw(SpriteBatch& sb) {
        sb.Draw(sprite_, position_, Color::White);
    }

private:
    Texture2D sprite_;   // owns GPU resource, released in destructor
    Vector2   position_;
};