Overview
Continuous integration (CI) means that every change pushed to the shared repository is automatically built and tested on a clean machine. For a Unity team, it answers the question “does the project still open, pass its tests and build a player on a machine that is not mine?” before a pull request is merged. It catches problems that a local editor hides: a script that only compiles because of an uncommitted file, a missing .meta file, a test that someone forgot to run, or a build that fails for the target platform.
GameCI is an open-source project that packages the Unity Editor in Docker images and provides ready-made GitHub Actions for Unity (GameCI, Getting started):
game-ci/unity-test-runnerruns the project’s Edit Mode and Play Mode tests (written with Unity’s Test Framework) and can publish the results as a check on the commit or pull request (GameCI, Test runner).game-ci/unity-builderbuilds a player for a chosentargetPlatform, for exampleWebGL,StandaloneWindows64orStandaloneLinux64, and writes it to abuild/folder (GameCI, Builder).
GameCI fits into the branching models described in git-branching: the workflow runs on every pull request into the integration branch (main in GitHub Flow, develop in Git Flow), and a branch rule makes the job a required status check, so a pull request cannot be merged while the tests or build fail.
Setup
1. Activate a Unity licence for CI
The Unity Editor will not run without an activated licence, even on a build server. GameCI reads the licence from GitHub repository secrets (GameCI, Activation):
| Licence | Secrets to create |
|---|---|
| Unity Personal (free) | UNITY_LICENSE (the full contents of the .ulf licence file), UNITY_EMAIL, UNITY_PASSWORD |
| Unity Plus / Pro | UNITY_SERIAL (from the Unity subscriptions page), UNITY_EMAIL, UNITY_PASSWORD |
To obtain the .ulf file for a Personal licence, GameCI’s instructions are: sign in to Unity Hub with the account the CI will use, go to Preferences → Licenses → Add → Get a free personal license, then copy the file from:
- Windows:
C:\ProgramData\Unity\Unity_lic.ulf - macOS:
/Library/Application Support/Unity/Unity_lic.ulf - Linux:
~/.local/share/unity3d/Unity/Unity_lic.ulf
GameCI warns that a licence can appear in Unity Hub without a .ulf file having been written, so you must actually complete the Add step; the folders may also be hidden. The licence is not tied to an editor version or operating system. Do not use the .ulf route if you hold a Plus or Pro licence (GameCI, Activation).
Practical advice (interpretation): for a student team, one person, or a dedicated team Unity account, activates the licence and creates the secrets. Never commit the .ulf file or the password to the repository.
2. Add the secrets to GitHub
On the repository: Settings → Secrets and variables → Actions → New repository secret (in the “Security” section of the sidebar), once for each secret (GitHub, Using secrets in GitHub Actions).
3. Add a workflow file
Create .github/workflows/unity-ci.yml. The example below is adapted from GameCI’s simple example (GameCI, Getting started), triggered on pull requests into main or develop:
name: Unity CI
on:
pull_request:
branches: [main, develop]
jobs:
test-and-build: # this name becomes the status check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { lfs: true }
- uses: actions/cache@v4 # reuse Unity's Library folder
with:
path: Library
key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
restore-keys: Library-
- uses: game-ci/unity-test-runner@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with: { githubToken: "${{ secrets.GITHUB_TOKEN }}" }
- uses: game-ci/unity-builder@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with: { targetPlatform: WebGL }
- uses: actions/upload-artifact@v4
with: { name: Build, path: build }Both GameCI steps need the same three licence secrets. GameCI’s example uses actions/cache@v3; @v4 is shown here as the current major version of that action. GameCI’s example assumes the Unity project is at the root of the repository; otherwise set projectPath (GameCI, Getting started; Test runner). The Unity version is read automatically from ProjectSettings/ProjectVersion.txt (unityVersion: auto, the default).
4. Make the job a required check
Open the first pull request so the workflow runs once, then add the job name (test-and-build above) under Require status checks to pass before merging in the branch’s ruleset or branch protection rule. See Protecting branches on GitHub in git-branching.
Usage
- Every pull request into
mainordevelopnow shows the test-and-build check. A red cross means “fix this before merging”; the log of the failed step usually names the failing test or compiler error. - Test results: with
githubTokenset, the test runner publishes its results as a check run on the pull request. This needs the workflow to have read and write permissions (Settings → Actions → General → Workflow permissions). Results are also written to anartifactsfolder, which you can upload withactions/upload-artifactusingif: always()so that results are kept even when tests fail (GameCI, Test runner). - Build output: the builder writes to
build/<targetPlatform>/by default; the upload step makes the build downloadable from the workflow run page, which is handy for playtesters (GameCI, Builder). - Several platforms: GameCI’s builder documentation shows a
strategy.matrix.targetPlatformjob that builds each platform in parallel withfail-fast: false(GameCI, Builder). - No tests yet? The workflow is still worth having for the build step alone. Adding even a handful of Edit Mode tests for core rules (damage, scoring, inventory) makes the check far more useful.
Gotchas
- Licence errors are the most common first failure. Check that the secret names match exactly and that
UNITY_LICENSEholds the whole.ulffile contents, not its path (GameCI, Activation). - Git LFS bandwidth.
lfs: truedownloads every LFS file on each run. GameCI notes that projects storing binaries in GitHub’s LFS hosting “will want to cache them to avoid consuming massive amounts of bandwidth” and provides an LFS-caching example (GameCI, Getting started). See the LFS quota notes in git-github-unity. - Cache the
Libraryfolder. Without it, Unity re-imports every asset on every run. GameCI’s builder docs suggest including the target platform in the cache key when building several platforms, and note that a cache entry cannot be overwritten once its exact key exists (GameCI, Builder). - Actions minutes on private repositories. GitHub Actions is free on public repositories using standard GitHub-hosted runners; private repositories draw on a monthly allowance (2,000 minutes on GitHub Free), and the minutes are charged to the repository owner (GitHub, GitHub Actions billing; GitHub’s plans). Unity builds take several minutes each, so triggering on pull requests rather than on every push saves allowance.
- Pull requests from forks get no secrets. “With the exception of
GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository” (GitHub, Using secrets in GitHub Actions). Teams that add each other as collaborators and push branches to the same repository avoid this; a fork-based workflow will fail licence activation. - Tests run on Linux runners. GameCI states that tests are currently only compatible with Linux hosts (GameCI, Getting started). A Linux runner can build a Windows Mono player, but targets needing native toolchains (for example IL2CPP for some platforms, or iOS) need a compatible runner (GameCI, Builder).
- Unique job names. Required status checks are matched by job name; reusing a job name in several workflows can leave pull requests blocked by an ambiguous check (GitHub, About protected branches).
- Editor version support (tentative). GameCI builds Docker images for specific Unity versions. If your exact editor version has no image yet, the job fails before Unity starts; check GameCI’s Docker versions list or move the project to a supported patch release.
Related
- git-branching — branching models (GitHub Flow, Git Flow, trunk-based) and protecting branches with required checks
- git-github-unity — pull requests, Git LFS and file locking for Unity projects
- build-and-release-management — release candidates and milestone builds that CI produces
- qa-and-bug-triage — where automated checks sit in a wider QA process
- source-control — choosing a version-control system for game projects
External references
- GameCI, Getting started (GitHub): https://game.ci/docs/github/getting-started
- GameCI, Activation: https://game.ci/docs/github/activation
- GameCI, Test runner: https://game.ci/docs/github/test-runner
- GameCI, Builder: https://game.ci/docs/github/builder
- GitHub, Using secrets in GitHub Actions: https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets
- GitHub, GitHub Actions billing: https://docs.github.com/en/billing/concepts/product-billing/github-actions