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/ (apple/m4-stabilization branch)
├── easy-gl/ meta-gl/ (for the default OPENGLES3 renderer)
├── cna/ (apple/m4-stabilization branch)
└── my-first-game/ <-- create this
├── CMakeLists.txt
├── assets/ (empty for now; copied next to the executable)
├── src/
│ ├── MyGame.hpp
│ └── MyGame.cpp
└── main.cpp
mkdir my-first-game
cd my-first-game
mkdir src assets
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, and skip CNA's own tests and examples, before including CNA
# (both default to ON and would otherwise be built as part of your project).
set(CNA_GRAPHICS_RENDERER "OPENGLES3" CACHE STRING "CNA graphics renderer")
set(CNA_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CNA_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
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)
# On Windows the vendored SDL3 is a shared library: copy SDL3.dll, SDL3_image.dll and
# SDL3_mixer.dll next to the executable (CNA's own demos do this too).
if(WIN32 AND COMMAND cna_copy_sdl_runtime)
cna_copy_sdl_runtime(MyFirstGame)
endif()
# 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); a build that selects another platform or audio implementation (CNA_PLATFORM / CNA_AUDIO_PLATFORM, for example the windowless HEADLESS platform with ALSA audio) resolves a different dependency closure. There is no CNA::CNA alias — the target is simply CNA.
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
getWindowProperty().setTitleProperty("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);
// No device.Present(): Game presents the frame in EndDraw(), after Draw() returns.
}
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 module include directories (
modules/<name>/include/) are added to your include path, so#include "Microsoft/Xna/Framework/Game.hpp"resolves. sharp-runtimeheaders (from its own component include directories) are transitively included.- SDL3, SDL3_image, and SDL3_mixer are built from the submodules at configure time and linked as shared libraries on Linux, macOS, Windows and Android (static under Emscripten and on iOS). On Linux with CMake 3.27 or newer and GCC or Clang, CNA itself is also one shared
libcna.sothat your executable links (CNA_SHARED_LIBRARY). - 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 errors or link errors from sharp-runtime | sharp-runtime is on its default branch, which lacks components CNA now needs | Use its apple/m4-stabilization branch: git -C ../sharp-runtime checkout apple/m4-stabilization |
Video throws NotSupportedException when you use it | CNA was built without the optional FFmpeg backend | FFmpeg is optional (CNA_ENABLE_VIDEO=AUTO): install the four FFmpeg dev packages from Tutorial 02 and reconfigure. It is never built for Windows, Emscripten, Android or iOS. |
OpenGL context failed | GPU driver missing | Install Mesa or proprietary drivers; try the SDL_RENDERER renderer |
| Black window | Draw() is never reached, or never clears or draws | Check that your override is exactly void Draw(const GameTime&) override and that it calls device.Clear(...). Do not call Present() yourself: Game presents in EndDraw() after Draw() returns |
This window runs on the SDL3 platform layer, which opens it through whichever video driver SDL picks for the desktop session — see Tutorial 135 (running on X11 through SDL3) and Tutorial 136 (running on Wayland through SDL3).
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.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- A first CNA game, read line by line — A minimal CNA game and its extension to movement, edge clamping, sound and rectangle collision, explaining each framework contract and where CNA conveniences differ from portable XNA code.