๐ Lesson 8.4: A Tested, CI-Built Release โ and Where to Go Next
This is the last lesson of the last module of Unity Advanced โ the top of the ladder you started at Fundamentals. It's a mini-project in two halves. First we combine the module into one release: a couple of tests, a build script, a CI workflow, and a real release checklist walked from version bump to smoke test. Then we turn outward โ a substantial "Where to Go Next" map of the deep topics this course deliberately left out and the frontiers of the four pillars you now know. Let's ship, and then let's talk about everything still ahead.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Assemble a test + build script + CI workflow into a single reproducible release
- Walk a professional release checklist โ version bump, quality settings, IL2CPP/stripping, test pass, tag, build, smoke test
- Explain what IL2CPP and managed code stripping do at release time
- Map the topics this course omitted โ procedural generation and advanced AI โ and know where each fits
- Identify the next frontier of each pillar you learned: DOTS Physics & Netcode for Entities, custom SRP, and remote live content
- Feel genuinely done โ and know your next move
Estimated Time: 90 minutes ยท Prerequisite: Lessons 8.1โ8.3 (this lesson combines all three)
In This Lesson
What We're Shipping
The goal of this mini-project isn't a big game โ it's a pipeline. We'll take a tiny piece of game logic, put it under test (Lesson 8.1), drive it through a build script (Lesson 8.2), gate that build on the tests in CI (Lesson 8.2), and finally walk it through the disciplined checklist that turns "a build" into "a release." By the end you'll have the exact skeleton a studio uses, sized down to fit one lesson.
Here's the whole release, drawn as the checklist we'll execute:
Step 1 โ The Tests
We'll ship a trivial but real slice: a WaveConfig that computes how many enemies spawn in a wave. Pure logic, so it's an Edit Mode test. In your Game.Runtime assembly:
namespace Game.Runtime
{
public static class WaveConfig
{
// Wave 1 spawns baseCount; each wave adds 'growth' more, capped at maxCount.
public static int EnemiesForWave(int wave, int baseCount, int growth, int maxCount)
{
int raw = baseCount + (wave - 1) * growth;
return System.Math.Min(raw, maxCount);
}
}
}
And its tests, in a Test Assembly (the asmdef with the TestAssemblies reference from Lesson 8.1):
using NUnit.Framework;
using Game.Runtime;
public class WaveConfigTests
{
[Test]
public void FirstWave_UsesBaseCount()
{
Assert.That(WaveConfig.EnemiesForWave(1, 5, 3, 30), Is.EqualTo(5));
}
[Test]
public void LaterWaves_GrowLinearly()
{
// wave 4: 5 + 3*3 = 14
Assert.That(WaveConfig.EnemiesForWave(4, 5, 3, 30), Is.EqualTo(14));
}
[Test]
public void Growth_IsCappedAtMax()
{
Assert.That(WaveConfig.EnemiesForWave(50, 5, 3, 30), Is.EqualTo(30));
}
}
Run them in the Test Runner (Lesson 8.1) and confirm three green checks. This is stage 3 of the checklist, proven locally before anything ships.
Step 2 โ The Build Script
Now the build entry point from Lesson 8.2, extended with the release-time settings that make it a release build rather than a dev one โ versioning, IL2CPP, and managed stripping:
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
public static class ReleaseBuild
{
// -executeMethod ReleaseBuild.Perform
public static void Perform()
{
// --- Stage 1: version stamp (from CI env, with fallbacks) ---
string version = System.Environment.GetEnvironmentVariable("BUILD_VERSION") ?? "1.0.0";
string run = System.Environment.GetEnvironmentVariable("GITHUB_RUN_NUMBER") ?? "0";
PlayerSettings.bundleVersion = version;
// --- Stage 5: release backend + stripping ---
var group = BuildTargetGroup.Standalone;
var named = UnityEditor.Build.NamedBuildTarget.Standalone;
PlayerSettings.SetScriptingBackend(named, ScriptingImplementation.IL2CPP);
PlayerSettings.SetManagedStrippingLevel(named, ManagedStrippingLevel.High);
string[] scenes = System.Array.ConvertAll(
System.Array.FindAll(EditorBuildSettings.scenes, s => s.enabled),
s => s.path);
var options = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = $"Builds/Win64/MyGame-{version}-{run}/MyGame.exe",
target = BuildTarget.StandaloneWindows64,
options = BuildOptions.None // no Development flag = a real release build
};
BuildReport report = BuildPipeline.BuildPlayer(options);
bool ok = report.summary.result == BuildResult.Succeeded;
Debug.Log(ok ? $"Release {version}+{run} built" : "Release build FAILED");
EditorApplication.Exit(ok ? 0 : 1); // exit code gates CI (Lesson 8.2)
}
}
๐ IL2CPP & managed stripping
IL2CPP is Unity's release scripting backend: it converts your compiled C# (IL) into C++ and then to native machine code, giving faster, harder-to-reverse-engineer builds (and it's required on many platforms). Managed code stripping then removes IL that nothing references, shrinking the binary. The catch: stripping can't see code reached only by reflection or serialization, so it may remove something you actually need โ which is why a smoke test of the stripped release build (stage 7) is non-negotiable. A link.xml file preserves types the stripper would otherwise cut.
Step 3 โ The CI Workflow
Wire it together with the GitHub Actions workflow from Lesson 8.2, now triggered by a tag so a release runs only when you deliberately tag a commit (stage 4), and passing the version through to the build:
name: Release
on:
push:
tags: [ 'v*' ] # runs only on version tags like v1.0.0
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { lfs: true }
- name: Tests
uses: game-ci/unity-test-runner@v4
env: { UNITY_LICENSE: '${{ secrets.UNITY_LICENSE }}' }
with: { testMode: editmode }
- name: Build
uses: game-ci/unity-builder@v4
env:
UNITY_LICENSE: '${{ secrets.UNITY_LICENSE }}'
BUILD_VERSION: '${{ github.ref_name }}' # the tag, e.g. v1.0.0
with:
targetPlatform: StandaloneWindows64
buildMethod: ReleaseBuild.Perform
- name: Upload versioned artifact
uses: actions/upload-artifact@v4
with:
name: MyGame-${{ github.ref_name }}
path: build/
Push a tag and the pipeline runs: check out โ tests (green, or the release stops) โ IL2CPP release build โ versioned artifact. Stages 3, 5, and 6 of the checklist now happen automatically, every time, from a clean machine.
๐ก Tag-triggered releases are a deliberate gate. Ordinary pushes can run a lighter test-only pipeline; the heavy release build fires only on a v* tag. That separation keeps everyday CI fast while making an actual release an explicit, traceable act.
Step 4 โ The Release Checklist
The tooling is built; releasing is now walking Figure 1. Do it in order, top to bottom:
- Version bump. Decide the version (semantic:
MAJOR.MINOR.PATCH). Our script stamps it from the tag. - Quality & Player settings. Confirm the release quality level, that the right scenes are enabled in Build Settings, and icons/name/company are set.
- Tests green. Run the full suite locally first โ never tag on red. The Test Runner is the gate (Lesson 8.1).
- Tag the commit.
git tag v1.0.0 && git push --tags. This exact commit is now the source of record for the release. - CI build. The tag triggers the release workflow: IL2CPP, high stripping, tests re-run on a clean checkout.
- Versioned artifact. CI uploads
MyGame-v1.0.0; upload symbol files for crash symbolication (Lesson 8.3). - Smoke test. Download the artifact onto a clean machine, install it, and play the happy path โ especially watching for anything stripping may have broken. If it fails, fix and re-run (the dashed loop).
- Release. Promote the smoke-tested artifact to testers or the store channel. Then โ Lesson 8.3 โ watch analytics and crash reports roll in.
โ ๏ธ The smoke test is the one step people skip โ and regret
A green CI build is not a working game. IL2CPP + stripping can produce a binary that compiles perfectly and crashes on launch because reflection-only code was stripped. Installing the actual artifact on a clean device and playing it is the only check that catches this class of bug before players do.
What You Built
โ You now have a real release pipeline
Sized to one lesson, but structurally complete: tested logic (Edit Mode tests), a release build script (IL2CPP, stripping, versioning), a tag-triggered CI workflow that gates the build on green tests and emits a versioned artifact, and a disciplined checklist ending in a smoke test. Swap in a real game and this same skeleton ships it.
Extend it: add a Play Mode smoke test that boots the main scene in CI; add a WebGL target to the build matrix and deploy the artifact to a static host; wire symbol upload into the workflow; add a second job that only runs Edit Mode tests on every push (fast) while the release job runs on tags (heavy). Each is a small, well-scoped addition to a pipeline you now understand end to end.
๐ Where to Go Next
This course was, by design, focused and deep rather than broad. It picked four pillars โ Performance/DOTS, Rendering, Netcode, and Tooling/Pipeline โ and went as deep as one course reasonably can. That means whole territories were left for you to explore. Here's the map.
Two big topics this course deliberately left out
๐บ๏ธ Procedural generation
Generating content โ terrain, dungeons, loot, whole galaxies โ from algorithms and seeds rather than by hand. Start with noise (Perlin/simplex) for terrain and Unity.Mathematics.Random for deterministic, seed-reproducible results. Grow into grammar/graph-based level generation, Wave Function Collapse for tile layouts, and marching cubes for voxel meshes. It pairs beautifully with what you learned: run generation in Burst jobs (Module 3) or as compute shaders (Module 5) to build worlds fast.
๐ง Advanced AI
You know the NavMesh; this is the decision-making layer above it. Learn Behaviour Trees (the industry-standard structure for reactive AI), GOAP (Goal-Oriented Action Planning โ agents that plan action sequences toward goals), and utility AI (scoring options by weighted considerations). Beyond hand-authored logic sits ML-Agents, Unity's reinforcement-learning toolkit for training behaviour. Each is a large field; behaviour trees are the highest-leverage first step.
The next frontier of each pillar you already know
- Performance & DOTS โ You learned entities, components, systems, and baking. Next: Unity Physics (the stateless, DOTS-native physics engine), Netcode for Entities (networking at ECS scale, a different stack from the Netcode for GameObjects you learned in Module 6), and the deeper corners of the Job System โ custom
IJobEntitychunking and cache-line-aware data design. - Rendering โ You wrote HLSL, a Scriptable Renderer Feature, and compute shaders on URP. Next: authoring a fully custom Scriptable Render Pipeline from scratch, advanced RenderGraph passes, and โ if your project demands cinematic fidelity โ the High Definition Render Pipeline (HDRP) with its physically-based, volumetric feature set.
- Netcode โ You built an authoritative prototype with NetworkVariables and RPCs. Next: client-side prediction and server reconciliation, lag compensation, dedicated server hosting (Unity's Game Server Hosting / Multiplay), and the wider UGS multiplayer stack โ Lobby, Relay, and Matchmaker.
- Tooling & Pipeline โ You built EditorWindows and used Addressables locally. Next: remote Addressable content served from a CDN with content catalogs and live updates, full UI Toolkit runtime UIs, and richer editor automation โ asset post-processors, custom build pipelines, and package authoring.
โ How to actually get deeper
Pick one pillar and build a small, complete project that stresses it โ a proc-gen roguelike, a behaviour-tree-driven enemy sandbox, a client-predicted twitch shooter, a moddable game on remote content. Depth comes from finishing something that forces the edges. You already have the two habits that make that sustainable: you can measure (the Profiler) and you can test and ship (this module). Those never stop paying off.
Hands-on Challenge
๐๏ธ Exercise 1: Run your own release
Objective: Execute the full pipeline on a throwaway project.
In any small Unity project: add the WaveConfig class and its three tests, add the ReleaseBuild.Perform script, and walk the checklist locally as far as you can without CI โ run the tests, set IL2CPP + stripping, build via -executeMethod ReleaseBuild.Perform from a terminal, then smoke test the resulting executable on your machine. Note anything the stripped build broke.
โ What to watch for
The batch-mode command is Unity -batchmode -quit -projectPath . -buildTarget StandaloneWindows64 -executeMethod ReleaseBuild.Perform -logFile -. Common smoke-test surprises: a NullReferenceException at launch from a type only referenced via reflection/JSON that High stripping removed โ fixed by adding it to a link.xml or lowering the stripping level. The build succeeding but the game crashing is exactly the scenario the smoke test exists to catch.
๐๏ธ Exercise 2: Chart your path
From the "Where to Go Next" map, choose one direction and sketch a one-page plan: the small complete project you'd build, which of your four pillars it leans on, and the first three things you'd need to learn. Make it concrete enough to start this week.
โ Example
Proc-gen roguelike. Leans on Performance/DOTS (generate the dungeon in Burst jobs) and a little advanced AI (behaviour-tree enemies). First three to learn: (1) seeded noise + a room-graph generation algorithm; (2) meshing a generated grid efficiently; (3) a minimal behaviour tree for enemy patrol/chase. Ship it through this module's pipeline so every build is tested and versioned.
๐ฏ Quick Quiz
Question 1: Why is a smoke test of the actual artifact essential even after CI reports a green build?
Question 2: What does IL2CPP do at release time?
Question 3: In the finale pipeline, why trigger the release workflow on a git tag rather than every push?
Question 4: Which pair names topics this course deliberately left out for you to explore next?
The Finish Line
๐ Key Takeaways
- A release pipeline combines the module: tested logic (8.1), a build script gated in CI (8.2), and live-ops to watch it after (8.3).
- A release build differs from a dev build: versioned, built with IL2CPP and managed stripping โ which makes a smoke test of the real artifact mandatory.
- The release checklist โ version, settings, tests, tag, CI build, versioned artifact, smoke test, release โ turns "a build" into a repeatable, traceable "release."
- This course was focused and deep; procedural generation and advanced AI are the big topics left for you, and each pillar has a clear next frontier.
- Depth comes from finishing a small complete project that stresses one pillar โ and you can now measure it and ship it.
๐ You finished the ladder
Fundamentals taught you to build a game. Intermediate made you fluent in Unity's systems. Advanced made you an engineer: you think in data and cache lines, schedule safe multithreaded jobs, simulate tens of thousands of entities, write HLSL and custom render passes, run compute on the GPU, synchronize an authoritative multiplayer world, bend the Editor to your workflow, stream content with Addressables, and ship a tested, CI-built release you can operate live. That's the full professional stack. Be proud of the climb โ very few people finish all three courses.
Now go build the thing only you can build. ๐
๐ What's Next?
That's the whole course. Your next step is out there in your own project โ pick a direction from the map above and start. Thank you for coming this far; it's been a genuine pleasure teaching you.