Tutorial 03: Your First CNA Window

CNA Tutorial Series  ·  Beginner

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.
# Set the backend before including CNA.
set(CNA_GRAPHICS_BACKEND "EASYGL" CACHE STRING "CNA rendering backend")
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 links SDL3 transitively. You only need to call target_link_libraries(YourGame PRIVATE 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/Graphics/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_.setPreferredBackBufferWidth(800);
    graphics_.setPreferredBackBufferHeight(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-runtime headers (at ../sharp-runtime/include/) are transitively included.
  • SDL3, SDL3_image, and SDL3_mixer are linked as static libraries built from submodules.
  • The selected backend (EASYGL) is linked.

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_BACKEND=EASYGL -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

ErrorCauseFix
Could not find CNA targetCMake path to cna/ is wrongAdjust CNA_DIR in CMakeLists.txt
sharp-runtime not foundsharp-runtime not cloned as siblingClone sharp-runtime next to cna/
OpenGL context failedGPU driver missingInstall Mesa or proprietary drivers; try SDL_RENDERER backend
Black windowdevice.Present() missingEnsure 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.