Unity
The game development tool, not the desktop environment.
Installation instructions (via Unity Hub)
In Windows and MacOS, pretty straightforward.
For Ubuntu 24.04, will need to run the Unity Hub binary in unconfined mode (trust them...?):
# Install instructions: https://docs.unity3d.com/hub/manual/InstallHub.html#install-hub-linux
# Add signing key
user:~$ wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | sudo tee /usr/share/keyrings/Unity_Technologies_ApS.gpg > /dev/null
# Add repository
user:~$ sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/Unity_Technologies_ApS.gpg] https://hub.unity3d.com/linux/repos/deb stable main" > /etc/apt/sources.list.d/unityhub.list'
# Install
user:~$ sudo apt update && sudo apt install unityhub
# Run in unconfined mode, otherwise will fail to start
# See: https://discussions.unity.com/t/unity-hub-and-x-ubuntu-24-04-not-starting-workaround/945633
user:~$ cat /etc/apparmor.d/unityhub
abi <abi/4.0>,
include <tunables/global>
profile unityhub /opt/unityhub/unityhub-bin flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/unityhub>
}
user:~$ sudo systemctl restart apparmor.service
Note the information on this page may not be straightforward to understand without some basic prior knowledge on how Unity works. For tutorials catered to the inexperienced, there are many Youtube videos etc. out there.
Setting up a new project
Project setup
Add a gitignore catered for Unity before pushing to a repository.
Sample gitignore
### Unity ###
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/
# Recordings can get excessive in size
/[Rr]ecordings/
# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*
# Autogenerated Jetbrains Rider plugin
/[Aa]ssets/Plugins/Editor/JetBrains*
# Visual Studio cache directory
.vs/
.vscode/
.vsconfig
# IntelliJ
.idea/
# Temporary Files
Temp/
/Temp/
# Gradle cache directory
.gradle/
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
sysinfo.txt
# Builds
*.apk
*.aab
*.unitypackage
*.app
# Crashlytics generated file
crashlytics-build.properties
# Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
# Never ignore DLLs in the FMOD subfolder.
!/[Aa]ssets/Plugins/FMOD/**/lib/*
# Don't ignore images and gizmos used by FMOD in the Unity Editor.
!/[Aa]ssets/Gizmos/FMOD/*
!/[Aa]ssets/Editor Default Resources/FMOD/*
# Ignore the Cache folder since it is updated locally.
/[Aa]ssets/Plugins/FMOD/Cache/*
# Ignore bank files in the StreamingAssets folder.
/[Aa]ssets/StreamingAssets/**/*.bank
/[Aa]ssets/StreamingAssets/**/*.bank.meta
# If the source bank files are kept outside of the StreamingAssets folder then these can be ignored.
# Log files can be ignored.
fmod_editor.log
# Ignore FMOD plugin build settings across workstations
[Aa]ssets/Plugins/FMOD/Resources/FMODStudioSettings.asset
Art and music assets may be placed separately, preferably in a Git-LFS submodule to decouple the assets. Note that metadata associated with each asset will be generated, so the art/music liaison should be responsible for loading these assets to Unity prior to updating the asset repo.
Nuke option when loading all submodule files, in case of some corruption:
user:~$ git submodule update --recursive --remote --force
Basic structure in Unity
Unity gameplay is grouped into individual Scenes, which contain a set of GameObjects. Note that game objects do not have to represent something tangible in the scene: it can hold game logic, or hold a collection of assets, or even just function as an item separator in the editor's scene menu. Some discipline/framework should be used to organize these objects.
Built-in Unity is plenty capable, especially considering the built-in physics engine, so no scripting is required. For custom behaviour such as user-scripted behaviour upon physics collisions, multiple scripts can be assigned to the same object. The base structure is a C# script inheriting from a "MonoBehaviour" class that exposes the Unity API (and boy, the syntax in C# makes it much, much more similar to Java than it is to C++):
Sample code
using UnityEngine;
public class FireballMovement : MonoBehaviour
{
private Rigidbody2D rb;
public float fallSpeed = 20f;
public float despawnMargin = 0.5f;
private float destroyHeight;
void Start()
{
rb = GetComponent<Rigidbody2D>();
destroyHeight = -Camera.main.orthographicSize - despawnMargin;
}
private void FixedUpdate()
{
rb.MovePosition(rb.position + Vector2.down * fallSpeed * Time.fixedDeltaTime);
if (rb.position.y < destroyHeight) {
Destroy(gameObject);
}
}
// Affect the score in the statemanager
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player")) {
PersistentState.state.GetScoreManager()
.SetIncrement(scoreKey);
}
}
}
Scripts belong to a larger umbrella which includes colliders, transforms, renderers, as well: these are collectively called Components.
Some integrations afforded by Unity:
Public variables exposed can be modified from the Unity Editor.
Game object components can be directly cross-referenced via script if the are attached to the same game object.
Game objects can be directly referenced by using tags (functions as a global state table).
The main logic happens in:
Awake()
: Initialization happens, even if the object is not active.
Start()
: Further intialization after the object is made active.
Update()/FixedUpdate()
: State modifications per frame/fixed time interval.
OnDestroy()
: Release of resources cached by the object.
-
Some early notes to be aware of:
// Coordinates relative to Camera center
Camera camera = Camera.main;
float height = 2 * camera.orthographicSize;
float width = height * camera.aspect;
// Spawn and despawning
GameObject obj = Instantiate(spawnObject, spawnObject.transform);
obj.SetActive(true);
Destroy(other);
Some other design patterns / code blocks for future reference:
Delayed children game object activation
using UnityEngine;
public class DelayedActivation : MonoBehaviour
{
public float delayTime = 1f;
private float elapsedTime = 0.0f;
void FixedUpdate()
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= delayTime) {
foreach (Transform child in transform)
child.gameObject.SetActive(true);
}
}
}
Timed scene transitions
using System.Collections;
using UnityEngine;
public class TimedTransition : MonoBehaviour
{
public string nextSceneName; // Set the next scene's name in the Unity Inspector
public float waitDuration = 5f;
void Start()
{
StartCoroutine(WaitAndLoadScene());
}
IEnumerator WaitAndLoadScene()
{
yield return new WaitForSeconds(waitDuration); // Wait for 5 seconds
Initiate.Fade(nextSceneName, Color.black, 1);
}
}
Singletons for global states
using System;
using UnityEngine;
public class PersistentState : MonoBehaviour
{
public static PersistentState state;
private MusicManager music; // or use arrays to manage concurrent
void Awake()
{
if (state != null && state != this) {
Destroy(this.gameObject);
return;
}
DontDestroyOnLoad(this.gameObject);
state = this;
// Bootstrap
music = GetComponent<MusicManager>();
}
public MusicManager GetMusicManager() {
return music;
}
}
Project management
More experienced people can give better tips, but some things I found useful from one of my game jams:
Team leads must be present to coordinate between each department, to minimize conflicting information.
Meeting agenda must be agree beforehand to avoid unexpected meeting overruns.
Shot and asset lists should be prepared for asynchronous communication with developers on what assets can be integrated.
An all-hands physical meeting should be done at least once for introductions and communicating expectations on commitment and experience levels.
Opportunity to have the development environment set up as well, e.g. game engine, packages, version control.
Build and playtest frequently after an initial MVP, to debug possible bugs and/or technical limitations from deployment.
Example shot and asset lists
Shot lists with individual scene numbers for easier reference, a sketch of the visuals, description of expected gameplay:
Art asset lists for tracking of assets available for integration. Recommended to generate quick sprites for backgrounds and character sprites for programmers to use as placeholders (which can then be asynchronously updated by the developers). Need to factor in creation of spritesheets for eventual animations as well, and marketing material.
Music asset list for tracking of music assets as well. For tracks with more involved automation/parameter controls, placeholders should be created early on as well. Art to provide music team with initial concept art for tailoring music. Note there needs to be tighter communication especially if using video/animation requiring SFX/music integration.
Code repository should be as neatly organized as possible, with logical groupings for scripts, and clear scene naming according to the game design specifications:
External packages/plugins
FMOD for Unity
FMOD is a game audio integration tool. It is relatively easy and intuitive to pick up given its DAW-like interface. FMOD integration in Unity is pretty seamless, since the concepts are essentially the same: audio emitters and listeners are attached to game objects, and can be controlled programmatically.
The strength of FMOD lies in offloading audio automation logic to the audio engineer, rather than doing all these manually in Unity, to achieve adaptive music. This includes effects, fades, transitions, asynchronous playback, and spatialization. The basic FMOD interface for audio playback on the Unity side are:
All the game devs need to know is what knobs can be tweaked on the FMOD event in Unity, e.g. "play the main battle music now upon event trigger, with an intensity value of medium". Corresponding automation logic in FMOD may include:
Gradual un-muting of background effects (
seek speed)
Send music through auxiliary sound effects/filters (
effects)
-
-
Overlapping of concurrent auxiliary music stems for continuity depending on an intensity parameter (
automation)
This significantly reduces development burden.
The composer working on FMOD will build the tracks, and push these to the music repo (the whole project does not need to be committed, look up the corresponding gitignore). Unity developers only need to perform initial linking to these built assets during the FMOD for Unity setup window, pointing to the "Build/" assets folder, before invoking the FMOD Unity package to interface with these tracks.
Sample code for managing a music track
using System;
using UnityEngine;
public class MusicManager : MonoBehaviour
{
public FMODUnity.EventReference music;
private FMOD.Studio.EventInstance instance;
void Awake() {
Load(music);
}
void OnDestroy() {
Unload();
}
public void Reload(FMODUnity.EventReference other) {
if (other.Guid == music.Guid) return;
Unload();
Load(other);
}
void Load(FMODUnity.EventReference other) {
music = other;
instance = FMODUnity.RuntimeManager.CreateInstance(music);
}
void Unload() {
instance.release();
}
public void Play() {
if (IsPlaying()) return;
instance.start();
}
public void Stop() {
instance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
}
public bool IsPlaying() {
// Note this is conceptually different from PLAYBACK_STATE.PLAYING
FMOD.Studio.PLAYBACK_STATE playbackState;
instance.getPlaybackState(out playbackState);
return playbackState != FMOD.Studio.PLAYBACK_STATE.STOPPED;
}
public void SetParameter(string name, int value, bool play = false) {
instance.setParameterByName(name, value);
if (play) Play();
}
}
Downsides do exist, mainly that FMOD Audio and the Built-In Audio are not compatible. This may pose an issue with libraries that were written only with the built-in audio in mind, e.g. VideoPlayer scripts do not support emitting FMOD-compatible audio events.