During summer 2025, I gave a course about “advanced” programming practices in general and in the Unity 6 engine
for the Swiss Game Academy (SGA) at the HEIA-FR University
Core Concepts Covered
This course was designed to go beyond basic scripting and teach students how to think like software architects. The Key topics included were the following:
S.O.L.I.D. Principles: Practical, in-engine examples of Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion.
Unity Attributes: Using attributes like [Header], [SerializeField], [Tooltip], and [Range] to create clean, designer-friendly inspectors.
Advanced C# Features: Delegates, Action events, the Observer Pattern, switch expressions with type pattern matching, and coroutines for asynchronous operations.
Architectural Patterns: Implementing Singletons, data-driven design with Scriptable Objects, and decoupled UI systems.
Robust Save/Load System: Building a flexible persistence system using interfaces (ISaveable), JSON serialization (Newtonsoft.Json), and Data Transfer Objects (DTOs).
Editor Tooling: Using Gizmos (OnDrawGizmosSelected) to provide clear visual feedback for designers and developers.
How ?
Code
To achieve my course in the best possible way, I decided to create a 3D project where students would have to complete, add, refactor, and delete code
so that the project would include all the necessary features.
Art
The art was defined and created by Jo A.. Thanks to her I wasn’t obliged to rely
on primitive shapes and materials and my project was brought to life in 3 days.
External assets
The only part that was not done either by me nor by the artist was a fire shader
The project was divided into branches or folders, each representing a stage of the course. The learning path was designed to be completed sequentially from V1.0 to V5.0. Each version builds upon the last, with students filling in missing code to complete the exercises.
Student Objective: Implement a system to save and load the player’s state.
PlayerHealth.cs:
Implement the ISaveable interface (CaptureState and RestoreState).
Create and invoke a delegate/event for health changes.
PlayerHealthBarUI.cs: Subscribe to the PlayerHealth event to update the UI automatically.
WorldItemManager.cs: Implement the Singleton pattern to track all WorldItem objects.
WorldItem.cs: In OnEnable() and OnDisable(), register/unregister with the WorldItemManager.
GameManager.cs: Uncomment the code and fill in the SaveGame() and LoadGame() methods to handle saving player data and world item locations.
V5.0: The Final Polish
Focus: Reviewing all concepts and ensuring the final project is robust and complete.
Student Objective: This version represents the completed project. It serves as a reference and a demonstration of how all the systems work together in harmony.
To prevent students from losing too much time in the implementation and correction of the code, I included a Powershell script that would automatically close Unity
change tags to the given version and reopen it at the desired chapter.
To do so, the students just had to enter
.\gototag.bat X.0
replacing the X by the chapter number.
The .ps1 code was wrapped in a .bat script for Window to bypass ExecutionPolicy restrictions
The .bat script
@echo off:: Windows batch wrapper for gototag PowerShell script:: This script is designed to be placed in your Unity project's root folder.:: Get the tag name from the first argumentset TAG=%1:: Call PowerShell script with ExecutionPolicy Bypasspowershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0gototag.ps1" %TAG%
The .ps1 script
param ( [string]$TagName, [switch]$Clean)# This script is designed to be placed in your Unity project's root folder.# It automates checking out a specific Git tag and launching the corresponding Unity Editor version.if (-not $TagName) { Write-Host "Usage: gototag <tag-name> [--clean]" Write-Host "" Write-Host "Arguments:" Write-Host " <tag-name> The Git tag to checkout (e.g., v1.0, development)" Write-Host "" Write-Host "Options:" Write-Host " --clean Performs a 'git clean -xfd' before checking out the tag." Write-Host " Use with caution: this will remove untracked files and directories." Write-Host "" Write-Host "Example:" Write-Host " gototag v2.5 --clean" Write-Host " gototag feature/new-ui" exit 1}Write-Host "`Closing Unity if running..."Get-Process Unity -ErrorAction SilentlyContinue | Stop-ProcessStart-Sleep -Seconds 2Write-Host "Cleaning working directory (pre-checkout)..."git reset --hardif($Clean){ Write-Host "Performing git clean -xfd..." git clean -xfd}Write-Host "`Checking out tag: $TagName"git checkout tags/$TagNameif ($LASTEXITCODE -ne 0) { Write-Host "Failed to checkout tag '$TagName'. Does it exist?" exit 1}Write-Host "`Final reset and clean after switching..."git reset --hardif($Clean){ Write-Host "Performing git clean -xfd..." git clean -xfd}# Read Unity version from ProjectSettings/ProjectVersion.txt$versionFile = Join-Path -Path (Get-Location) -ChildPath "ProjectSettings\ProjectVersion.txt"if (-Not (Test-Path $versionFile)) { Write-Host "Cannot find ProjectVersion.txt. Are you in a Unity project folder?" exit 1}$content = Get-Content $versionFile$versionLine = $content | Where-Object { $_ -match 'm_EditorVersion:' }if (-not $versionLine) { Write-Host "Could not find Unity version line in ProjectVersion.txt" exit 1}$unityVersion = $versionLine -replace 'm_EditorVersion:\s*',''Write-Host "`Detected Unity version: $unityVersion"# Construct Unity Editor path (adjust if your install path differs)$unityPath = "C:\Program Files\Unity\Hub\Editor\$unityVersion\Editor\Unity.exe"if (-Not (Test-Path $unityPath)) { Write-Host "Unity Editor not found at $unityPath" Write-Host "Please install Unity version $unityVersion via Unity Hub or update this path." exit 1}Write-Host "`Launching Unity Editor..."Start-Process -FilePath $unityPath -ArgumentList "-projectPath `"$PWD`""Write-Host "`Done. Tag '$TagName' is now active.`n"
Since some students used MacOS computers, I also created a .sh Terminal script
#!/bin/bash# This script is designed to be placed in your Unity project's root folder.# It automates checking out a specific Git tag and launching the corresponding Unity Editor version.## Configuration (Optional)# If your Unity Hub Editor installations are in a non-standard location, you can uncomment and modify this line:# UNITY_HUB_EDITORS_PATH="/Applications/Unity/Hub/Editor"# For Windows, this would typically be handled by the PowerShell script.## Script Logicif [ -z "$1" ]; then echo "Error: No tag name pro12313vided." echo "Usage: gototag <tag-name>" exit 1fiTAG=$1echo "Closing Unity if running..."pkill -x "Unity" 2>/dev/nullsleep 2echo "Cleaning working directory (pre-checkout)..."git reset --hardgit clean -xfdecho "Checking out tag: $TAG"git checkout tags/$TAGif [ $? -ne 0 ]; then echo "Failed to checkout tag \'$TAG\'. Does the tag exist?" exit 1fiecho "Final reset and clean after switching..."git reset --hardgit clean -xfd# Extract Unity version from ProjectSettingsUNITY_VERSION_FILE="ProjectSettings/ProjectVersion.txt"if [ ! -f "$UNITY_VERSION_FILE" ]; then echo "Cannot find $UNITY_VERSION_FILE. Is this a Unity project?" exit 1fiUNITY_VERSION=$(grep -oP 'm_EditorVersion: \K.*' "$UNITY_VERSION_FILE" || grep -o 'm_EditorVersion: .*' "$UNITY_VERSION_FILE" | cut -d' ' -f2)# Determine Unity Editor path based on OSif [[ "$(uname)" == "Darwin" ]]; then # macOS path UNITY_PATH="${UNITY_HUB_EDITORS_PATH:-/Applications/Unity/Hub/Editor}/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity"elif [[ "$(uname -s)" == "Linux" ]]; then # Linux path (common for Unity on Linux, adjust if needed) UNITY_PATH="${UNITY_HUB_EDITORS_PATH:-/opt/Unity/Hub/Editor}/$UNITY_VERSION/Editor/Unity"else echo "Unsupported operating system for direct Unity launch via this script." echo "Please use the Windows PowerShell script (gototag.ps1) on Windows." exit 1fiif [ ! -f "$UNITY_PATH" ]; then echo "Unity Editor not found at: $UNITY_PATH" echo "Please make sure that version $UNITY_VERSION is installed via Unity Hub and the path is correct." exit 1fiecho "Launching Unity Editor version $UNITY_VERSION...""$UNITY_PATH" -projectPath "$(pwd)"echo "Done. Tag \'$TAG\' is now active."
Try it yourself !
If you want to try this course you can clone it from git using