Tutorial 03: Your First CNA Window
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-runtimeheaders (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
| 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/ |
OpenGL context failed | GPU driver missing | Install Mesa or proprietary drivers; try SDL_RENDERER backend |
| 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.