Tutorial 84: Migrating from XNA to CNA

CNA — C++ XNA 4.0 reimplementation

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, DrawableGameComponent lifecycle (Initialize, LoadContent, Update, Draw, UnloadContent)
  • GraphicsDevice: Clear, Present, DrawPrimitives, DrawIndexedPrimitives, SetVertexBuffer, SetIndexBuffer
  • SpriteBatch: Begin, End, Draw (all overloads), DrawString
  • Texture2D, RenderTarget2D, Texture3D, TextureCube
  • All Effect subclasses: 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(); }