Tutorial 95: Speedy Blupi: A Real-World CNA Port
What is Speedy Blupi?
Speedy Blupi is an open-source action platformer originally developed for Windows and ported to Linux via MonoGame. It features a tile-based world, multiple game modes, animated sprites, and SDL-based audio. The game is part of the Blupi game series by EPSITEC SA and is available on GitHub.
Original MonoGame codebase
The original Speedy Blupi C# MonoGame codebase used: SpriteBatch for all 2D rendering, ContentManager.Load<Texture2D> for XNB assets, Song and MediaPlayer for background music, SoundEffect for game audio, GamePad and Keyboard input, and a custom tilemap renderer built on SpriteBatch.Draw calls.
Porting process to CNA
- Replace
using Microsoft.Xna.Framework;withusing namespace Microsoft::Xna::Framework; - Replace
new SpriteBatch(GraphicsDevice)withstd::make_unique<SpriteBatch>(getGraphicsDeviceProperty()) - Replace
Content.Load<Texture2D>("name")withTexture2D("assets/name.png", gd) - Replace all
Song/MediaPlayercalls withSoundEffect(loop viaSoundEffectInstance::setIsLooped(true)) - Replace
List<T>withstd::vector<T>orListCS<T> - Replace
nullwithnullptr,stringwithstd::string - Replace
(float)gameTime.ElapsedGameTime.TotalSecondswithstatic_cast<float>(gt.ElapsedGameTime.TotalSeconds()) - XACT
AudioEngine/WaveBank/SoundBanknow work directly in CNA (real.xgs/.xsb/.xwbparsing) — no replacement needed unless you prefer the simplerSoundEffect::FromFileAPI
API compatibility lessons
Rectangle.Intersects()works identically in CNA and MonoGame — no changes neededColorconstructornew Color(r,g,b,a)maps toColor(r,g,b,a)— identicalSpriteBatch.Drawoverloads are matched 1:1 in CNA — no changes to draw callsVector2.Zero,Vector2.Oneare static members in CNA exactly as in XNAMathHelper.Clamp,MathHelper.Lerpbehave identicallyGamePad.GetState(PlayerIndex.One)maps toGamePad::GetState(PlayerIndex::One)— identical semantics
Content pipeline adaptation
Speedy Blupi's XNB assets were converted by exporting PNG files from the original XNB textures using the MonoGame Content Builder, then placing them in an assets/ directory. The CNA port loads PNGs directly with Texture2D("assets/sprites.png", gd). No content pipeline tool is needed — CNA uses SDL3_image for PNG loading.
Performance results
The CNA port of Speedy Blupi achieves a stable 60 fps on an Intel integrated GPU (i5-10210U) under Linux, the same as the original MonoGame version on the same hardware. The C++ RAII model eliminated GC pauses that occasionally caused frame drops in the C# version on low-memory devices.
Key porting patterns from Speedy Blupi experience
// Pattern 1: Convert content loading
// C# MonoGame:
// texture = Content.Load<Texture2D>("Blupi/blupi");
// CNA C++:
// texture_ = std::make_unique<Texture2D>("assets/Blupi/blupi.png", gd);
// Pattern 2: Convert Song/MediaPlayer to looping SoundEffectInstance
// C# MonoGame:
// MediaPlayer.Play(Content.Load<Song>("music/theme"));
// MediaPlayer.IsRepeating = true;
// CNA C++:
auto music = SoundEffect::FromFile("assets/music/theme.ogg");
auto musicInst = music->CreateInstance();
musicInst->setIsLooped(true);
musicInst->Play();
// Pattern 3: Convert string interpolation to std::string
// C# MonoGame:
// string path = $"sprites/{name}.png";
// CNA C++:
std::string path = "assets/sprites/" + name + ".png";
// Pattern 4: Convert foreach to range-for
// C# MonoGame:
// foreach (var tile in tiles) tile.Draw(spriteBatch);
// CNA C++:
for (auto& tile : tiles_) tile.Draw(*spriteBatch_);
// Pattern 5: Convert nullable references to std::optional
// C# MonoGame:
// Texture2D? overrideTexture = null;
// CNA C++:
std::optional<Texture2D*> overrideTexture_ = std::nullopt;
// Pattern 6: Convert C# events to sharp-runtime Event<T>
// C# MonoGame:
// public event Action<int> OnScoreChanged;
// CNA C++ with sharp-runtime:
sharp::Event<int> OnScoreChanged;