Tutorial 20: Building and Running Your Game

Build System  ·  CMake  ·  Emscripten  ·  Android

What you’ll learn

  • Producing a release build and sanity-checking it with ctest.
  • Packaging the executable on Windows and Linux.
  • Building for WebAssembly with Emscripten and an overview of the Android APK path.
  • Bundling assets with CPack.

Before you startTutorial 02: Setting Up Your Dev Environment (a configured toolchain) and Tutorial 03: Your First CNA Window (a project to build).

CNA uses CMake as its build system. This tutorial covers release builds for Linux and Windows, the ctest sanity check, WebAssembly via Emscripten, Android APK packaging, and a CMakeLists.txt install target that bundles your game with its assets.

CMake release build (Linux)

## Configure a release build with the OPENGLES3 renderer
cmake -S . -B build-release \
      -DCMAKE_BUILD_TYPE=Release \
      -DCNA_GRAPHICS_RENDERER=OPENGLES3

## Build only your game target (faster than building everything)
cmake --build build-release --target MyGame --parallel

## Run
./build-release/MyGame

Release mode enables -O3 (GCC/Clang) or /O2 (MSVC) and strips debug symbols. Expect 3–10x faster rendering on CPU-bound scenes compared to Debug.

ctest sanity check

Before shipping, run the CNA test suite to confirm your dependencies and build configuration are sane:

cmake --build build-release --target CnaTests
ctest --test-dir build-release --output-on-failure --parallel 4

The suite should come back clean. How many tests run depends on the exact configuration: general GoogleTest cases are joined by renderer/platform-specific registrations for the implementations compiled into that build. If something fails, record the renderer, platform and audio selections with the result.

Do not try cmake --build build-release --target CNA. CNA is an INTERFACE library with no sources, so there is nothing to build under that name and CMake will refuse. Build your own game target, build CnaTests, or omit --target entirely.

Windows executable

:: Configure (PowerShell or cmd.exe)
cmake -S . -B build-win-release ^
      -DCMAKE_BUILD_TYPE=Release ^
      -DCNA_GRAPHICS_RENDERER=OPENGLES3

:: Build
cmake --build build-win-release --target MyGame --config Release

:: Run
build-win-release\Release\MyGame.exe

On Windows with MSVC, SDL3.dll is placed alongside the executable automatically by the build system. No additional DLL redistribution steps are required for self-contained distribution.

The command above keeps OPENGLES3 so that the same renderer runs on Linux and Windows. If you want a native Direct3D path instead, fourteen renderers are gated to a Windows configure — among them DIRECTX9, DIRECTX11, DIRECTX12, DIRECT2D and GDI. Swap the flag, for example -DCNA_GRAPHICS_RENDERER=DIRECTX11; naming one of them on a non-Windows configure is a hard FATAL_ERROR. The repository's Wine/DXVK and vkd3d-proton scripts and its native-Windows workflows are manual paths in alpha.1, so test on the machines you intend to ship to.

Linux executable packaging

For distributing to end users on Linux, bundle your executable with its assets:

# In CMakeLists.txt — install target
install(TARGETS MyGame DESTINATION .)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets DESTINATION .)
install(FILES ${CMAKE_SOURCE_DIR}/README.txt DESTINATION .)

# Build the install tree
cmake --install build-release --prefix dist/linux-x64

# dist/linux-x64/ now contains:
#   MyGame
#   assets/
#   README.txt

For AppImage packaging, run linuxdeploy on the install prefix. For Flatpak, add a CNA module to your manifest and point to the install prefix.

Emscripten WebAssembly build

CNA supports Emscripten 3.1+ for web deployment. You need the Emscripten SDK installed and activated (source emsdk_env.sh).

Web builds need a web renderer. OPENGLES3, OPENGLES2 and OPENGL33 are hard-gated off under Emscripten and will stop the configure with a FATAL_ERROR. The browser renderers are WEBGL2 (the Emscripten default), WEBGL1, CANVAS, HTML_DOM and SVG_DOM.

## Configure with the Emscripten toolchain
emcmake cmake -S . -B build-web \
      -DCMAKE_BUILD_TYPE=Release \
      -DCNA_GRAPHICS_RENDERER=WEBGL2

## Build
cmake --build build-web --target MyGame

## Output files:
##   build-web/MyGame.js
##   build-web/MyGame.wasm
##   build-web/MyGame.html   (auto-generated shell page)
##   build-web/MyGame.data   (packaged assets)

Assets are packaged into the .data file by Emscripten's --preload-file mechanism. In your CMakeLists.txt:

if(EMSCRIPTEN)
    set_target_properties(MyGame PROPERTIES
        SUFFIX ".html"
        LINK_FLAGS
            "-s USE_SDL=3 \
             -s USE_SDL_IMAGE=3 \
             -s USE_SDL_MIXER=3 \
             -s ASYNCIFY \
             -s ALLOW_MEMORY_GROWTH=1 \
             --preload-file ${CMAKE_SOURCE_DIR}/assets@/assets"
    )
endif()

Serve the output directory with any HTTP server — browsers block file:// access to .wasm files:

cd build-web
python3 -m http.server 8080
# Open http://localhost:8080/MyGame.html

CNA also ships a web CMake preset that configures a WEBGL2 release build for you: emcmake cmake --preset web.

Three things the web build does differently

  • Game must be heap-allocated. A stack-allocated Game is silently corrupted under Emscripten's main-loop model — you get no diagnostic, just wrong behaviour. Write auto* game = new MyGame(); game->Run(); rather than MyGame game; game.Run(); in your web entry point.
  • No save persistence. Anything written through StorageDevice lives in volatile MEMFS and is gone on reload. See Tutorial 19.
  • No video playback. The video translation units are excluded from Emscripten builds, so Video and VideoPlayer are missing symbols. The headers still exist, which means calling code compiles happily and then fails at link time. The same is true on Windows and Android.

Android APK overview

Android support in CNA uses SDL3's Android build infrastructure. The high-level steps are:

  1. Install Android NDK r25+ and Android SDK with API level 24+.
  2. Copy the SDL3 Android project template from third_party/SDL/android-project/.
  3. Configure the Gradle build to reference your CNA library as a native dependency.
  4. Place assets in src/main/assets/.
  5. Run ./gradlew assembleRelease to produce an APK.
## CMake cross-compile for Android ARM64
cmake -S . -B build-android \
      -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
      -DANDROID_ABI=arm64-v8a \
      -DANDROID_PLATFORM=android-24 \
      -DCNA_GRAPHICS_RENDERER=OPENGLES3 \
      -DCMAKE_BUILD_TYPE=Release

cmake --build build-android --target MyGame

See the Platforms documentation for a full step-by-step Android setup guide. Be aware that Android code paths exist and are maintained, but no CI job builds or runs them, so treat an Android build as something you verify yourself. Video playback is unavailable there for the same reason as on the web.

Packaging assets with CPack

CPack can produce a ZIP, DEB, or NSIS installer automatically:

## CMakeLists.txt additions for CPack
set(CPACK_PACKAGE_NAME        "MyGame")
set(CPACK_PACKAGE_VERSION     "1.0.0")
set(CPACK_PACKAGE_VENDOR      "My Studio")
set(CPACK_GENERATOR           "ZIP;DEB")   # or NSIS on Windows

include(CPack)
install(TARGETS MyGame DESTINATION bin)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets DESTINATION share/MyGame)
## Generate the package
cmake --build build-release --target package

## Output:
##   build-release/MyGame-1.0.0-Linux.zip
##   build-release/MyGame-1.0.0-Linux.deb

Full CMakeLists.txt example

cmake_minimum_required(VERSION 3.20)
project(MyGame VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Pull in CNA (assumes sibling directory layout)
add_subdirectory(../cna cna-build)

add_executable(MyGame
    src/main.cpp
    src/MyGame.cpp
    src/states/MainMenuState.cpp
    src/states/GameplayState.cpp
)

target_link_libraries(MyGame PRIVATE CNA)

# Copy assets to build dir for development convenience
add_custom_command(TARGET MyGame POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        ${CMAKE_SOURCE_DIR}/assets $<TARGET_FILE_DIR:MyGame>/assets
)

# Install rules
install(TARGETS MyGame DESTINATION .)
install(DIRECTORY assets DESTINATION .)

# CPack
set(CPACK_PACKAGE_NAME    "MyGame")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_GENERATOR       "ZIP")
include(CPack)