Tutorial 83: Migrating from MonoGame/FNA to CNA
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# Namespace | C++ Namespace |
|---|---|
Microsoft.Xna.Framework | Microsoft::Xna::Framework |
Microsoft.Xna.Framework.Graphics | Microsoft::Xna::Framework::Graphics |
Microsoft.Xna.Framework.Audio | Microsoft::Xna::Framework::Audio |
Microsoft.Xna.Framework.Input | Microsoft::Xna::Framework::Input |
Microsoft.Xna.Framework.Media | Microsoft::Xna::Framework::Media |
C# to C++ type mapping
| C# Type | C++ CNA Type | Notes |
|---|---|---|
int | intcs (= int32_t) | sharp-runtime alias |
float | Single (= float) | or just use float |
double | double | same |
bool | bool | same |
byte | bytecs (= uint8_t) | sharp-runtime alias |
string | std::string | no implicit conversions |
List<T> | ListCS<T> | from sharp-runtime, or std::vector<T> |
null | nullptr | pointer null |
? nullable | std::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
SpriteFontdescriptor instead of.spritefontXML + 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 insteadMessageBox.Show— no equivalent; useSDL_ShowMessageBoxif 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_;
};