Tutorial 84: Migrating from XNA to CNA
XNA historical context
Microsoft XNA Game Studio 4.0 was released in 2010 and discontinued in 2014. CNA's primary goal is to be a C++ drop-in replacement for XNA 4.0 — the same namespaces, class names, method signatures, and behavior. If you have an XNA 4.0 game in C#, CNA gives you a migration path to a maintained, cross-platform C++ codebase that runs on Linux, Windows, macOS, Android, and the web.
Identical API surface goal
CNA targets ~95% of the XNA 4.0 public API. Every class in Microsoft.Xna.Framework has a
C++ counterpart in Microsoft::Xna::Framework. Method names use the same capitalization (XNA's
PascalCase is preserved). Enum values are identical. The goal is that the XNA 4.0 documentation at
learn.microsoft.com applies
directly to CNA without translation.
What's the same
Game,GameComponent,DrawableGameComponentlifecycle (Initialize, LoadContent, Update, Draw, UnloadContent)GraphicsDevice: Clear, Present, DrawPrimitives, DrawIndexedPrimitives, SetVertexBuffer, SetIndexBufferSpriteBatch: Begin, End, Draw (all overloads), DrawStringTexture2D,RenderTarget2D,Texture3D,TextureCube- All
Effectsubclasses: BasicEffect, AlphaTestEffect, DualTextureEffect, EnvironmentMapEffect, SkinnedEffect - Math types: Vector2, Vector3, Vector4, Matrix, Quaternion, Color, Rectangle, BoundingBox, BoundingSphere, Ray, Plane
- Input: Keyboard, Mouse, GamePad, TouchPanel
- Audio: SoundEffect, SoundEffectInstance
- ContentManager (direct file loading, no XNB)
What's different (C++ semantics, no GC, NOXNA markers)
C++ requires explicit resource management. Any XNA class that implements IDisposable in C#
has a destructor in CNA that releases GPU resources. Use std::unique_ptr where you would
have used using blocks in C#.
APIs that cannot be implemented due to platform or design constraints are marked
// NOXNA in the CNA headers. Examples:
GraphicsAdapter.IsProfileSupported (always returns true),
StorageDevice.BeginShowSelector (UI-based storage selection).
GamerServices (Gamer, SignedInGamer, GamerProfile,
leaderboards, Guide, achievements, and the Avatar subsystem) has a complete API port with
local/synthetic semantics — the same approach FNA itself takes for this namespace, since it's not
binary-compatible with real Xbox Live.
XACT (now real, ~97% functional)
The Microsoft::Xna::Framework::Audio::AudioEngine/WaveBank/SoundBank/Cue
(XACT) classes implement a real .xgs/.xsb/.xwb parser with SDL3_mixer-backed
playback — category/lifecycle/3D positional audio/instance-limit enforcement with fade in/out and
continuous RPC volume/pitch curves are all real. XNA games that use XACT can carry their existing
.xap-authored audio projects over rather than rewriting to SoundEffect::FromFile.
A couple of narrow, documented deviations remain (no HRTF/elevation, no AttackTime/ReleaseTime
envelope tracking).
Content pipeline
XNA's Content Pipeline produces .xnb binary files. CNA does not read XNB. Replace
content.Load<Texture2D>("name") with
Texture2D("assets/name.png", gd). For models, replace XNB FBX import with OBJ or FBX
loaded via CNA's model loader.
Testing with the 4,373-test suite
CNA ships 4,373 unit tests covering math types, geometry, curves, and game loop semantics — the same
behavior XNA documents. Run ctest --test-dir build after migrating to verify that your
migrated math and physics code produces identical results to XNA. A green test run is strong evidence
that the numerical behavior is correct.
XNA C# class to CNA C++ migration example
// ===== Original XNA 4.0 C# =====
// using Microsoft.Xna.Framework;
// using Microsoft.Xna.Framework.Graphics;
//
// public class MyXnaGame : Game {
// GraphicsDeviceManager graphics;
// SpriteBatch spriteBatch;
// Texture2D texture;
//
// public MyXnaGame() {
// graphics = new GraphicsDeviceManager(this);
// Content.RootDirectory = "Content";
// }
//
// protected override void LoadContent() {
// spriteBatch = new SpriteBatch(GraphicsDevice);
// texture = Content.Load<Texture2D>("logo");
// }
//
// protected override void Update(GameTime gameTime) {
// if (Keyboard.GetState().IsKeyDown(Keys.Escape))
// Exit();
// base.Update(gameTime);
// }
//
// protected override void Draw(GameTime gameTime) {
// GraphicsDevice.Clear(Color.CornflowerBlue);
// spriteBatch.Begin();
// spriteBatch.Draw(texture, Vector2.Zero, Color.White);
// spriteBatch.End();
// base.Draw(gameTime);
// }
// }
// ===== Equivalent CNA C++ =====
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"
#include "Microsoft/Xna/Framework/Input/Keyboard.hpp"
#include "Microsoft/Xna/Framework/Input/Keys.hpp"
#include "Microsoft/Xna/Framework/Graphics/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp"
#include "Microsoft/Xna/Framework/Graphics/Texture2D.hpp"
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
using namespace Microsoft::Xna::Framework::Input;
class MyGame final : public Game {
public:
MyGame() : graphics_(this) {}
protected:
void LoadContent() override {
spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
// Direct file load — no XNB, no Content.RootDirectory needed
texture_ = std::make_unique<Texture2D>("assets/logo.png",
getGraphicsDeviceProperty());
}
void Update(const GameTime& gameTime) override {
if (Keyboard::GetState().IsKeyDown(Keys::Escape))
Exit();
}
void Draw(const GameTime& gameTime) override {
auto& gd = getGraphicsDeviceProperty();
gd.Clear(Color::CornflowerBlue);
spriteBatch_->Begin();
spriteBatch_->Draw(*texture_, Vector2::Zero, Color::White);
spriteBatch_->End();
gd.Present();
}
private:
GraphicsDeviceManager graphics_;
std::unique_ptr<SpriteBatch> spriteBatch_;
std::unique_ptr<Texture2D> texture_;
};
int main() { MyGame game; game.Run(); }