Skip to content

Build a game

A build turns the open project into a game that players run without the editor. It contains the native Feather player, a copy of the project and the assets it uses. The player runs the same scripts, Blueprints and physics as Play in the editor.

The player is the same program for every game, so a build compiles no game code. Building is mostly copying and checking files, and takes seconds once a player is available. The editor, feather-cli and GitHub Actions all use the same build pipeline.

  1. Save any pending edits. A build validates the whole project and its scripts first, and stops on the first error.
  2. Open the Build window: choose File → Build game…, or Window → Build · Windows, macOS, Linux.
  3. Pick a platform. Each button shows This computer, Runtime ready or Needs a runtime; hover over it to see where its player comes from.
  4. Adjust the Settings section if you need to (see below). The window shows the output folder.
  5. Click Build <name> for <platform>. The Build log shows progress.
  6. When it finishes, the window shows the time, file count and size, with Show in folder, Run game (for this computer’s platform) and Copy path.

The Build button in the toolbar, and File → Build for this computer, build straight away for the platform selected in the Build window (this computer by default). Only one build runs at a time.

Settings are stored in the project and are the same in the Build window and the build.get / build.set commands.

Setting Default Notes
Game name (game_name) The project name Name of the app and its executable. 1–64 characters, without / \ : * ? " < > |, not starting with a dot and not a reserved Windows name
version 0.1.0 Three numbers, such as 1.2.0
build_number 1 1 to 1,000,000. Increase it with every release (macOS CFBundleVersion)
Bundle id (bundle_id) com.feather-engine.<game> Reverse-DNS app id, such as com.studio.game
profile release release is optimised for players. development compiles faster and runs slower
Output folder (output_dir) builds A folder inside the workspace. In the editor, it is relative to the project’s folder
Make a zip (archive) Off Also write a .zip (.tar.gz for Linux) beside the build

Each build is written to <output folder>/<game>-<platform>, for example builds/my-game-windows. Development builds add -dev.

Platform Layout
macOS <Name>.app, with the project and assets/ inside Contents/Resources/game/
Windows <Name>.exe with project.feather.json and assets/ beside it. Release builds open no console window
Linux <Name> with project.feather.json and assets/ beside it

Every build also contains README.txt (how to launch it), licenses/, release-manifest.json (every file with its size and SHA-256, plus the platform, profile and version) and SHA256SUMS. To check a build, run shasum -a 256 -c SHA256SUMS inside its folder.

On a Mac, the macOS app is code-signed ad hoc. Pass a signing identity with sign_identity (or the FEATHER_SIGN_IDENTITY environment variable) to sign with it; a named identity also turns on the hardened runtime. A macOS app built on another platform is not signed.

How a build stays reliable:

  • Every asset is hash-checked as it is copied. A standalone texture missing from the asset cache is left out with a warning instead of failing the build; import it again to include it.
  • The player’s executable must match the target platform, so a Windows player can never end up in the Linux build.
  • The build is assembled in a hidden folder and swapped in at the end. A failed build leaves the previous one untouched. Only a previous Feather build (a folder with release-manifest.json) is ever replaced; any other folder in the way is refused.

The first source that applies is used. game.platforms and the Build window’s platform buttons show which one each platform would use.

Source When
A player you supply player (--player on the CLI). It must be inside the workspace
Built with Cargo This computer’s platform, from a source checkout (cargo build --locked --no-default-features --bin feather-player, with --release for release builds). Only the first build takes minutes
The bundled player This computer’s platform, with the editor app’s own player
A player runtime Any platform: feather-player (or feather-player.exe) in runtimes/<platform>/ in the Feather data folder, or in $FEATHER_RUNTIMES/<platform>/

There is no cross-compiling. To build for another platform, put that platform’s player in the runtimes folder; a feather-player-<platform> file from a Feather release works too. The Build window’s Build on GitHub section shows the folder’s path.

Players double-click <Name>.app, <Name>.exe or <Name>, and keep the whole folder together. From a terminal you can also pass a project path to the player:

Terminal window
feather-player path/to/project.feather.json

The game starts in the project’s start level. Escape opens the pause menu, with Resume, Save game, Load game, Settings and Quit to desktop, unless the game draws its own menu. Settings, saves and crash reports are written to the Feather data folder, never into the game folder, so replacing a build keeps them. See Saves and settings.

Option Effect
PROJECT Run this project instead of the one beside the player
--verify-run JSON Run, write a verification report and exit; see Profiler
--screenshot PNG With --verify-run, also save a screenshot
--record DIR Save every frame as a numbered JPEG in DIR
--version Print the player version

From a source checkout you can run any saved project with the player directly:

Terminal window
cargo run --no-default-features --bin feather-player -- examples/paper-shredder/project.feather.json

A game can live in its own GitHub repository, without the engine source. GitHub downloads the prebuilt Feather tools and builds Windows, macOS and Linux versions; nothing is compiled.

  1. Save the project, then in the Build window’s Build on GitHub section click Set up GitHub builds (or run game.setup_github). It writes .github/workflows/feather-build.yml at the repository root, the nearest folder with .git (otherwise the project’s folder: run git init there), and adds the build folders to .gitignore.
  2. Commit the workflow with the project, including its assets/ folder, and push. The workflow checks out Git LFS files, so large models, textures and sounds can be stored in LFS.
  3. While the engine repository is private, add a repository secret FEATHER_ENGINE_TOKEN: a fine-grained token with Contents: read on the engine repository.
  4. In GitHub, open Actions → Build game → Run workflow. You can choose the platforms, a version and whether to upload to Steam. Each platform’s build is attached to the run as a game-<platform> download.
  5. Push a tag such as v1.0.0 to build that version and publish it as a GitHub release (and upload it to Steam, when Steam is set up).

The workflow is pinned to the Feather version of the editor that wrote it, so CI builds with what you tested. Run Set up GitHub builds again after updating Feather; it refreshes a workflow Feather wrote, and refuses to replace any other file unless you pass overwrite. Windows and Linux are packaged on a Linux runner and macOS on a Mac runner, so the app is signed. Each runner checks the downloaded tools against the release’s SHA256SUMS.

Any other CI can run the same build with feather-cli build; the Build window shows the exact command.

Set the ids from Steamworks (SteamPipe → Depots) in the Build window’s Steam section or with steam.set. The GitHub workflow does the upload.

Setting Range Default Notes
App ID (app_id) 0 to 4,294,967,295 0 0 turns Steam uploads off
Windows depot, macOS depot, Linux depot (depot_windows, depot_macos, depot_linux) 0 to 4,294,967,295 0 One depot per platform; 0 leaves that platform out. Each platform needs its own depot, and a depot id cannot be the app id
Live branch (branch) Letters, digits, -, _; up to 64 beta Branch the upload goes live on. Empty only uploads. default and public are refused: set the default branch live in Steamworks yourself

With an App ID set, at least one depot is required. The workflow also needs two repository secrets: STEAM_USERNAME, a Steam account for builds with permission to edit and publish the app, and STEAM_CONFIG_VDF, the base64 of an authorised steamcmd config/config.vdf, so CI never needs a Steam Guard code. Tagged builds upload automatically when the App ID and both secrets are set; without the secrets the upload is skipped with a warning. Run workflow with Upload to Steam ticked uploads any build.

  • One build at a time in the editor.
  • No cross-compiling: other platforms need a player runtime or a GitHub build.
  • Builds have no app icons yet, and are not notarised (macOS) or Authenticode-signed (Windows).
  • Steam support covers uploads only.
Terminal window
# Which platforms can this machine build for, and where would each player come from?
feather-cli --workspace ./my-game --project project.feather.json command game.platforms
# Name and version the game, then build it for this computer with a zip.
feather-cli --workspace ./my-game --project project.feather.json command build.set \
--json '{"patch":{"game_name":"My Game","version":"1.0.0"}}' --save project.feather.json
feather-cli --workspace ./my-game --project project.feather.json build --archive
# Build for Windows with a Windows player you put inside the workspace.
feather-cli --workspace ./my-game --project project.feather.json build \
--platform windows --player tools/feather-player-windows.exe --version 1.0.1
# Write the GitHub workflow and set up Steam.
feather-cli --workspace ./my-game --project project.feather.json command steam.set \
--json '{"patch":{"app_id":480,"depot_windows":481,"depot_macos":482,"depot_linux":483}}' \
--save project.feather.json
feather-cli --workspace ./my-game --project project.feather.json command game.setup_github

build runs game.export, with --output, --profile and --sign-identity as its other options. In a headless run, --version, --build-number and --name override the build settings for that build only; they are not saved. Headless, game.export returns the report (path, player, files, bytes, seconds, archive, warnings); in a live editor it returns a job_id to poll with job.inspect, and build waits for it. To build from the editor binary without opening a window, use --export; see Install.