Tutorial 45: ContentManager and Asset Pipeline
CNA's ContentManager provides the same Load<T> interface as XNA 4.0 but works with open file formats — PNG, OBJ, WAV, OGG, and JSON descriptors — rather than compiled XNB binary assets.
No XNB support. CNA does not read Microsoft's XNB binary content format. All assets are loaded from their native formats (PNG textures, OBJ/FBX models, WAV/OGG audio) described by lightweight JSON descriptor files.
ContentManager::Load<T> generic
#include "Microsoft/Xna/Framework/Content/ContentManager.hpp"
using namespace Microsoft::Xna::Framework::Content;
using namespace Microsoft::Xna::Framework::Graphics;
using namespace Microsoft::Xna::Framework::Audio;
// Inside Game::LoadContent():
auto& content = getContentProperty(); // returns ContentManager&
content.setRootDirectory("Content"); // relative to the executable
// Texture2D — loads PNG/JPG/BMP from Content/textures/player.png
auto* tex = content.Load<Texture2D>("textures/player");
// Model — loads via Content/models/ship.model.json
auto* model = content.Load<Model>("models/ship");
// SoundEffect — loads from Content/audio/explosion.wav
auto* sfx = content.Load<SoundEffect>("audio/explosion");
// Song (streaming) — loads from Content/music/theme.ogg
auto* song = content.Load<Song>("music/theme");
// SpriteFont — loads from Content/fonts/arial20.font.json
auto* font = content.Load<SpriteFont>("fonts/arial20");
Supported asset types
| Type | File extension(s) | Notes |
|---|---|---|
Texture2D | .png .jpg .bmp .tga | Loaded via SDL3_image; all standard formats |
Model | .model.json + referenced OBJ | JSON descriptor points to geometry files |
SoundEffect | .wav | Loaded into memory; use for short clips |
Song | .ogg .mp3 | Streaming; only one Song plays at a time |
SpriteFont | .font.json + .png atlas | Pre-rendered glyph atlas format |
Effect | .effect.json | Built-in effects (BasicEffect etc.) |
ContentManager::RootDirectory
// Default is empty string (alongside the executable)
content.setRootDirectory("Content");
// Nested managers per state (one content manager per game screen)
ContentManager levelContent(getServicesProperty(), "Content/Level1");
ContentManager::Unload()
Unload() disposes every asset loaded through that ContentManager instance. Call it when transitioning between game states to reclaim GPU and audio memory.
// On screen exit:
levelContent.Unload();
// All Texture2D, Model, SoundEffect, etc. loaded via levelContent are now freed.
// Do NOT use any pointers obtained from levelContent after this point.
JSON descriptor formats
.texture.json
When you need to specify texture settings beyond defaults:
// Content/textures/terrain.texture.json
{
"file": "terrain.png",
"generateMipmaps": true,
"addressU": "Wrap",
"addressV": "Wrap",
"filter": "Linear"
}
.model.json
// Content/models/ship.model.json
{
"meshes": [
{ "file": "ship_body.obj", "material": "textures/hull" },
{ "file": "ship_engine.obj", "material": "textures/engine" }
]
}
.font.json
// Content/fonts/arial20.font.json
{
"atlas": "arial20.png",
"size": 20,
"lineSpacing": 24,
"glyphs": [
{ "char": 32, "x": 0, "y": 0, "w": 6, "h": 0, "xoff": 0, "yoff": 0, "advance": 6 },
{ "char": 65, "x": 10, "y": 0, "w": 14, "h": 20,"xoff": 0, "yoff": 0, "advance": 14 }
]
}
.sound.json
// Content/audio/explosion.sound.json (optional — plain .wav works too)
{
"file": "explosion.wav",
"volume": 0.8,
"pitch": 0.0,
"pan": 0.0
}
Code example: load multiple assets, unload on state change
class MultiAssetGame final : public Game {
public:
MultiAssetGame() : graphics_(this),
menuContent_(nullptr),
gameContent_(nullptr) {}
protected:
void Initialize() override {
Game::Initialize();
state_ = GameState::Menu;
}
void LoadContent() override {
// Menu assets use the global ContentManager
auto& shared = getContentProperty();
shared.setRootDirectory("Content");
menuFont_ = shared.Load<SpriteFont>("fonts/title");
menuBg_ = shared.Load<Texture2D>("ui/menu_bg");
// Game-level assets in a separate manager so they can be unloaded
gameContent_ = std::make_unique<ContentManager>(
getServicesProperty(), "Content/Game");
playerTex_ = gameContent_->Load<Texture2D>("player");
enemyTex_ = gameContent_->Load<Texture2D>("enemy");
shootSfx_ = gameContent_->Load<SoundEffect>("shoot");
levelModel_ = gameContent_->Load<Model>("level01");
}
void Update(GameTime& gameTime) override {
auto kb = Keyboard::GetState();
if (state_ == GameState::Menu && kb.IsKeyDown(Keys::Enter)) {
state_ = GameState::Playing;
}
if (state_ == GameState::Playing && kb.IsKeyDown(Keys::Escape)) {
// Free all in-game assets; menu assets remain loaded
gameContent_->Unload();
playerTex_ = nullptr;
enemyTex_ = nullptr;
shootSfx_ = nullptr;
levelModel_ = nullptr;
state_ = GameState::Menu;
}
}
void Draw(const GameTime&) override {
auto& gd = getGraphicsDeviceProperty();
gd.Clear(Color::Black);
// ... draw based on state_ ...
gd.Present();
}
private:
enum class GameState { Menu, Playing };
GameState state_ = GameState::Menu;
GraphicsDeviceManager graphics_;
std::unique_ptr<ContentManager> gameContent_;
SpriteFont* menuFont_ = nullptr;
Texture2D* menuBg_ = nullptr;
Texture2D* playerTex_ = nullptr;
Texture2D* enemyTex_ = nullptr;
SoundEffect* shootSfx_ = nullptr;
Model* levelModel_ = nullptr;
};
Pointers returned by Load<T> are owned by the ContentManager. After calling Unload(), all pointers from that manager are dangling. Null them immediately as shown above.