Tutorial 03: Your First CNA Window
What you’ll learn
- Laying out a minimal CNA project directory.
- Writing the
CMakeLists.txtthat links against CNA. - Deriving from
Gameand running an empty window.
Before you start — Tutorial 02: Setting Up Your Dev Environment — you need a built CNA before you can link against it.
Project Structure
We will create a tiny standalone game project that lives outside the CNA source tree. This mirrors how a real game project would be structured. Create a new directory alongside your cloned repositories:
my-cna-workspace/
├── sharp-runtime/
├── cna/
└── my-first-game/ <-- create this
├── CMakeLists.txt
├── src/
│ ├── MyGame.hpp
│ └── MyGame.cpp
└── main.cpp
mkdir my-first-game
cd my-first-game
mkdir src
CMakeLists.txt
Create my-first-game/CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(MyFirstGame LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# --- Locate CNA ---
# CNA must be cloned as a sibling of this project.
# Adjust the path if your layout differs.
set(CNA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../cna")
# Add CNA as a subdirectory so we can use its targets.
# The C++ framework has no general install/export package; add_subdirectory is the way in.
# Choose the renderer before including CNA.
set(CNA_GRAPHICS_RENDERER "OPENGLES3" CACHE STRING "CNA graphics renderer")
add_subdirectory(${CNA_DIR} ${CMAKE_BINARY_DIR}/cna)
# --- Our game executable ---
add_executable(MyFirstGame
main.cpp
src/MyGame.cpp
)
target_link_libraries(MyFirstGame PRIVATE CNA)
# Copy assets next to the executable
add_custom_command(TARGET MyFirstGame POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/assets
$<TARGET_FILE_DIR:MyFirstGame>/assets
COMMENT "Copying assets"
)
The CNA CMake target exposes CNA's include directories and, in this default SDL3 platform/audio configuration, links SDL3 transitively. You only need to call target_link_libraries(YourGame PRIVATE CNA); an SDL2/Headless/Terminal or non-SDL3-audio build resolves a different dependency closure.
The Minimal Game Class
Every CNA game starts with a class that extends Game. You override the lifecycle methods you need. Here is the minimal version that opens a window and clears it to the traditional XNA cornflower blue.
src/MyGame.hpp
#pragma once
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/GraphicsDeviceManager.hpp"
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
class MyGame final : public Game {
public:
MyGame();
protected:
void Initialize() override;
void LoadContent() override;
void Update(GameTime& gameTime) override;
void Draw(const GameTime& gameTime) override;
void UnloadContent() override;
private:
GraphicsDeviceManager graphics_;
};
src/MyGame.cpp
#include "MyGame.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"
MyGame::MyGame()
: graphics_(this) // GraphicsDeviceManager takes a pointer to the Game
{
// Set window title and size before Initialize() is called
setWindowTitle("My First CNA Game");
graphics_.setPreferredBackBufferWidthProperty(800);
graphics_.setPreferredBackBufferHeightProperty(600);
}
void MyGame::Initialize() {
// Called once after the graphics device is created.
// Call the base implementation first.
Game::Initialize();
}
void MyGame::LoadContent() {
// Load textures, sounds, fonts here.
// Nothing to load yet.
}
void MyGame::Update(GameTime& gameTime) {
(void)gameTime; // unused for now
// Game logic goes here.
}
void MyGame::Draw(const GameTime& gameTime) {
(void)gameTime;
// Get a reference to the graphics device
auto& device = getGraphicsDeviceProperty();
// Clear the back buffer to cornflower blue
device.Clear(Color::CornflowerBlue);
// Present the rendered frame to the screen
device.Present();
}
void MyGame::UnloadContent() {
// Free any resources loaded in LoadContent().
}
main.cpp
#include "src/MyGame.hpp"
int main() {
MyGame game;
game.Run();
return 0;
}
Linking CNA
The CMakeLists.txt above already handles linking. Here is what happens under the hood when you call target_link_libraries(MyFirstGame PRIVATE CNA):
- CNA's
include/directory is added to your include path, so#include "Microsoft/Xna/Framework/Game.hpp"resolves. sharp-runtimeheaders (at../sharp-runtime/include/) are transitively included.- SDL3, SDL3_image, and SDL3_mixer are linked as static libraries built from submodules.
- The selected renderer (
OPENGLES3) is linked. This tutorial uses the default single-renderer mode; an opt-in multi-renderer build can contain several, but its choice still latches before the first device is created.
CNA is an INTERFACE library, which is why you link against it but cannot build it: cmake --build build --target CNA fails. Build your own executable target instead, as shown below.
You should not need to set any CMAKE_PREFIX_PATH or find SDL3 manually.
Running Your Window
Build and run:
cd my-first-game
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3 -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j$(nproc)
./build/MyFirstGame
You should see a window titled "My First CNA Game" with a solid cornflower blue background. Close it by clicking the window's close button — we have not added keyboard input yet.
What CornflowerBlue means
Color::CornflowerBlue is the traditional default clear color in XNA. Its RGBA value is (100, 149, 237, 255). Every XNA tutorial starts with this color — it is a nod to the original XNA "Getting Started" tutorial from 2006.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Could not find CNA target | CMake path to cna/ is wrong | Adjust CNA_DIR in CMakeLists.txt |
sharp-runtime not found | sharp-runtime not cloned as sibling | Clone sharp-runtime next to cna/ |
easy-gl / meta-gl not found | The GL-profile renderers live in sibling checkouts | Clone easy-gl and meta-gl next to cna/, or select SDL_RENDERER instead |
Configure fails on libavcodec | FFmpeg development packages missing | On Linux and macOS FFmpeg is required with no opt-out — install the dev packages listed in Tutorial 02 |
OpenGL context failed | GPU driver missing | Install Mesa or proprietary drivers; try the SDL_RENDERER renderer |
| Black window | device.Present() missing | Ensure Draw() calls device.Present() at the end |
With a working window, you are ready to understand what CNA calls when it runs your game. Continue to Tutorial 04: The Game Class Lifecycle.