How: Developed by Unity Technologies, first released in 2005 at Apple’s Worldwide Developers Conference as a Mac-exclusive engine. Became cross-platform in 2008.
Who: Co-founded by David Helgason, Nicholas Francis, and Joachim Ante.
Why: To democratize game development — give indie developers access to a professional-grade engine without the cost of Unreal or proprietary tools.
Introduction
Unity is the world’s most widely used real-time 3D engine. It powers over 50% of mobile games and is used for 2D, 3D, AR/VR, and simulation. Scripting is done in C# via MonoBehaviour.
For the complete in-depth C# language reference (variables, OOP, LINQ, async/await, generics, and more), see the dedicated CSharp note.
Advantages
Massive asset store — thousands of free and paid assets.
Runtime fee controversy (partially walked back, but trust damaged).
Editor can be slow for large projects.
C# garbage collection can cause frame spikes without careful management.
HDRP and URP have different shader/material workflows — not interchangeable.
Editor & Project Setup
Editor Layout
Scene View — visual editor, place and move objects
Game View — preview of what the camera sees
Hierarchy — tree of all GameObjects in the scene
Inspector — properties of the selected GameObject/component
Project — all project assets (bottom panel)
Console — logs, warnings, errors
Key Shortcuts
Ctrl+S Save scene
Ctrl+Z / Y Undo / Redo
W / E / R / T Move / Rotate / Scale / Rect tool
F Focus on selected object
Ctrl+P Play / Stop
Ctrl+Shift+P Pause
Alt+LMB Orbit camera
RMB+WASD Fly through scene
Ctrl+D Duplicate selected
Del Delete selected
Project Structure
Assets/ All project content
Assets/Scenes/ Scene files (.unity)
Assets/Scripts/ C# scripts
Assets/Prefabs/ Reusable prefab assets
Assets/Materials/ Materials and shaders
Assets/Textures/ Images and sprites
Assets/Audio/ Sound files
Packages/ Unity Package Manager packages
ProjectSettings/ Project-wide settings
Library/ Auto-generated cache (don't commit)
Build Settings & Player Settings
File → Build Settings:
Add scenes to build list
Switch platform (PC, Android, iOS, WebGL, etc.)
Build / Build and Run
Edit → Project Settings → Player:
Company Name, Product Name, Version
Icon, Splash Screen
Scripting Backend: Mono (fast iteration) / IL2CPP (better performance)
API Compatibility Level: .NET Standard 2.1 / .NET Framework
Core Concepts — GameObjects & Components
GameObject
Everything in a Unity scene is a GameObject.
A GameObject is just a container — it does nothing by itself.
Behaviour comes from Components attached to it.
Every GameObject has:
Transform — position, rotation, scale (always present, cannot remove)
Name — string identifier
Tag — category label (Player, Enemy, Ground, etc.)
Layer — used for physics, rendering, raycasting
Active — enabled/disabled
Components
Common built-in components:
Rendering:
MeshFilter — holds the mesh data
MeshRenderer — renders the mesh with a material
SpriteRenderer — renders a 2D sprite
Camera — renders the scene from a viewpoint
Light — directional, point, spot, area light
ParticleSystem — CPU particle effects
LineRenderer — draws lines in world space
Physics (3D):
Rigidbody — physics simulation (gravity, forces)
BoxCollider — box-shaped collision
SphereCollider — sphere collision
CapsuleCollider — capsule collision (characters)
MeshCollider — mesh-shaped collision
CharacterController — kinematic character movement
Physics (2D):
Rigidbody2D — 2D physics simulation
BoxCollider2D — 2D box collision
CircleCollider2D — 2D circle collision
PolygonCollider2D — 2D polygon collision
Audio:
AudioSource — plays audio clips
AudioListener — receives audio (usually on Camera)
UI:
Canvas — root of all UI elements
Text (TMP) — TextMeshPro text
Image — UI image/sprite
Button — clickable button
Slider — value slider
Navigation:
NavMeshAgent — AI pathfinding agent
NavMeshObstacle — dynamic obstacle for NavMesh
Animation:
Animator — state machine animation controller
Animation — legacy animation (avoid for new projects)
Prefabs
Prefab — a reusable GameObject template saved as an asset.
Changes to the prefab propagate to all instances.
Create: drag GameObject from Hierarchy → Project window
Edit: double-click prefab in Project → Prefab Mode
Prefab Variants: inherit from a base prefab, override specific properties
Nested Prefabs: prefabs inside prefabs
// Instantiate a prefab at runtime[SerializeField] GameObject bulletPrefab;void Fire() { GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation); // or with parent: Instantiate(bulletPrefab, firePoint.position, Quaternion.identity, transform);}// DestroyDestroy(bullet); // immediateDestroy(bullet, 3f); // after 3 secondsDestroy(gameObject); // destroy self
MonoBehaviour — Scripting Basics
Script Structure & Lifecycle
using UnityEngine;public class PlayerController : MonoBehaviour{ // Inspector-exposed fields [SerializeField] float speed = 5f; [SerializeField] float jumpForce = 8f; // Component references Rigidbody rb; Animator anim; // Called once before first frame (before Start) void Awake() { rb = GetComponent<Rigidbody>(); anim = GetComponent<Animator>(); } // Called once on first frame (after all Awakes) void Start() { Debug.Log("Player started: " + gameObject.name); } // Called every frame void Update() { HandleInput(); } // Called every physics tick (fixed timestep, default 50/s) void FixedUpdate() { Move(); } // Called after all Updates — good for camera follow void LateUpdate() { // camera logic here } // Called when GameObject is enabled void OnEnable() { } // Called when GameObject is disabled or destroyed void OnDisable() { } // Called when GameObject is destroyed void OnDestroy() { } void HandleInput() { } void Move() { }}
Scene Load:
Awake() → all objects, any order
OnEnable() → objects that start enabled
Start() → all objects, after all Awakes
Per Frame:
Update() → every frame
LateUpdate() → after all Updates
Per Physics Tick:
FixedUpdate() → fixed timestep (default 0.02s = 50Hz)
Rendering:
OnPreRender() → before camera renders
OnRenderObject() → after camera renders scene
OnPostRender() → after camera renders
OnGUI() → immediate mode GUI (legacy)
Destruction:
OnDisable()
OnDestroy()
SerializeField & Inspector Attributes
public class Example : MonoBehaviour{ // Show private field in Inspector [SerializeField] float speed = 5f; // Hide public field from Inspector [HideInInspector] public int hiddenValue; // Clamp slider in Inspector [Range(0f, 100f)] public float volume = 50f; // Multi-line text field [Multiline(5)] public string description; // Tooltip on hover [Tooltip("Movement speed in units/second")] public float moveSpeed = 3f; // Organize with headers and spaces [Header("Combat Settings")] public int damage = 10; [Space(10)] public int health = 100; // Require a component on the same GameObject [RequireComponent(typeof(Rigidbody))] // (put this above the class declaration) // Run in edit mode // [ExecuteInEditMode] or [ExecuteAlways]}
Finding GameObjects & Components
// Get component on same GameObject (fast — use in Awake)Rigidbody rb = GetComponent<Rigidbody>();// Get component on childAnimator anim = GetComponentInChildren<Animator>();// Get component on parentHealth hp = GetComponentInParent<Health>();// Find by name (slow — avoid in Update)GameObject player = GameObject.Find("Player");// Find by tag (faster than Find)GameObject enemy = GameObject.FindWithTag("Enemy");GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");// Find by type (slowest — scans all objects)PlayerController pc = FindObjectOfType<PlayerController>();PlayerController[] all = FindObjectsOfType<PlayerController>();// Null check before useif (rb != null) rb.AddForce(Vector3.up * 5f);// TryGetComponent (no allocation on failure)if (TryGetComponent<Rigidbody>(out Rigidbody body)) { body.AddForce(Vector3.up);}
Input System
New Input System (Recommended)
Setup:
1. Install via Package Manager: Input System
2. Edit → Project Settings → Player → Active Input Handling → Input System Package
3. Create InputActionAsset → define Actions and Bindings
4. Generate C# class from the asset
void Update() { // Axes (smooth, -1 to 1) float h = Input.GetAxis("Horizontal"); // A/D or Left/Right float v = Input.GetAxis("Vertical"); // W/S or Up/Down float mouse_x = Input.GetAxis("Mouse X"); float mouse_y = Input.GetAxis("Mouse Y"); // Raw axes (no smoothing, -1, 0, or 1) float hRaw = Input.GetAxisRaw("Horizontal"); // Keys if (Input.GetKeyDown(KeyCode.Space)) Jump(); // pressed this frame if (Input.GetKey(KeyCode.LeftShift)) Sprint(); // held down if (Input.GetKeyUp(KeyCode.Space)) Land(); // released this frame // Mouse buttons (0=left, 1=right, 2=middle) if (Input.GetMouseButtonDown(0)) Shoot(); Vector3 mousePos = Input.mousePosition; // Buttons (defined in Edit → Project Settings → Input Manager) if (Input.GetButtonDown("Fire1")) Shoot(); if (Input.GetButton("Jump")) HoldJump();}
Physics
Rigidbody (3D)
public class PhysicsExample : MonoBehaviour{ Rigidbody rb; void Awake() => rb = GetComponent<Rigidbody>(); void FixedUpdate() { // Move with physics (use in FixedUpdate) rb.MovePosition(rb.position + Vector3.forward * 5f * Time.fixedDeltaTime); // Apply force rb.AddForce(Vector3.forward * 10f); // continuous rb.AddForce(Vector3.up * 500f, ForceMode.Impulse); // instant rb.AddForce(Vector3.right * 5f, ForceMode.VelocityChange); // ignore mass // Apply torque (rotation force) rb.AddTorque(Vector3.up * 10f); // Direct velocity (use sparingly) rb.linearVelocity = new Vector3(0, rb.linearVelocity.y, 0); } void Start() { rb.mass = 2f; rb.linearDamping = 0.5f; // air resistance rb.angularDamping = 0.05f; rb.useGravity = true; rb.isKinematic = false; // true = moved by script, not physics rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; }}
Animator Controller — state machine that controls animations.
States: Each state plays one AnimationClip
Transitions: Arrows between states with conditions
Parameters: Bool, Int, Float, Trigger — drive transitions
Layers: Multiple animation layers (base, upper body, face)
Blend Trees: Blend multiple clips by a float parameter (idle/walk/run)
Animator anim = GetComponent<Animator>();// Set parametersanim.SetFloat("Speed", rb.linearVelocity.magnitude);anim.SetBool("IsGrounded", isGrounded);anim.SetTrigger("Jump"); // one-shot triggeranim.ResetTrigger("Jump"); // cancel triggeranim.SetInteger("State", 2);// Get parametersfloat speed = anim.GetFloat("Speed");bool grounded = anim.GetBool("IsGrounded");// Play state directlyanim.Play("Run");anim.Play("Attack", 0, 0f); // (state, layer, normalizedTime)// Cross-fadeanim.CrossFade("Walk", 0.2f); // blend over 0.2 seconds// Check current stateAnimatorStateInfo info = anim.GetCurrentAnimatorStateInfo(0);if (info.IsName("Attack")) { }if (info.normalizedTime >= 1f) { /* animation finished */ }
Animation Events
// Add event in Animation window at a specific frame// Function must be on a MonoBehaviour on the same GameObjectvoid OnFootstep() { AudioSource.PlayClipAtPoint(footstepClip, transform.position);}void OnAttackHit() { // Enable hitbox at this frame hitbox.enabled = true;}
Root Motion
// Apply Root Motion — animator moves the character via animation curves// Enable "Apply Root Motion" on Animator component// Override root motion in scriptvoid OnAnimatorMove() { Vector3 velocity = anim.deltaPosition / Time.deltaTime; cc.Move(anim.deltaPosition);}
UI System (uGUI & TextMeshPro)
Canvas Setup
Canvas Render Modes:
Screen Space - Overlay — UI always on top, no camera needed
Screen Space - Camera — UI rendered by a specific camera
World Space — UI exists in 3D world (VR, diegetic UI)
Canvas Scaler:
Constant Pixel Size — fixed pixel size
Scale With Screen Size — scales to reference resolution (recommended)
Constant Physical Size — physical size in mm/cm
using System.IO;[System.Serializable]public class SaveData { public int score; public int level; public float[] playerPosition; public List<string> unlockedItems;}public static class SaveSystem{ static string SavePath => Application.persistentDataPath + "/save.json"; public static void Save(SaveData data) { string json = JsonUtility.ToJson(data, prettyPrint: true); File.WriteAllText(SavePath, json); Debug.Log("Saved to: " + SavePath); } public static SaveData Load() { if (!File.Exists(SavePath)) return new SaveData(); string json = File.ReadAllText(SavePath); return JsonUtility.FromJson<SaveData>(json); } public static void Delete() { if (File.Exists(SavePath)) File.Delete(SavePath); }}// Usagevar data = new SaveData { score = 500, level = 3 };SaveSystem.Save(data);SaveData loaded = SaveSystem.Load();
Shader Graph — node-based shader editor (URP and HDRP)
Create: Right-click Project → Create → Shader Graph → URP → Lit Shader Graph
Common nodes:
Sample Texture 2D — sample a texture
UV — texture coordinates
Time — current time (for animation)
Fresnel Effect — rim lighting
Normal Map — surface normals
Lerp — blend between values
Multiply / Add — math operations
Split / Combine — split/combine vectors
Vertex Color — per-vertex color data
Output:
Base Color, Metallic, Smoothness, Normal, Emission, Alpha
Material Property Block (Performance)
// Change material properties per-instance without creating new materialsMaterialPropertyBlock mpb = new MaterialPropertyBlock();Renderer rend = GetComponent<Renderer>();// Set propertiesmpb.SetColor("_BaseColor", Color.red);mpb.SetFloat("_Metallic", 0.8f);mpb.SetTexture("_BaseMap", myTexture);rend.SetPropertyBlock(mpb);// Clearrend.SetPropertyBlock(null);
Post Processing (URP)
Setup:
1. Add Volume component to a GameObject
2. Set Profile → Create New
3. Add Overrides: Bloom, Color Grading, Depth of Field, Vignette, etc.
4. Camera must have Post Processing enabled
Global Volume: affects entire scene
Local Volume: affects camera when inside the volume's collider
using UnityEngine.Rendering;using UnityEngine.Rendering.Universal;Volume volume;Bloom bloom;void Start() { volume = GetComponent<Volume>(); volume.profile.TryGet(out bloom);}void Update() { // Pulse bloom on hit bloom.intensity.value = Mathf.PingPong(Time.time, 2f);}
Debugging & Profiling
Debug Utilities
// Console logsDebug.Log("Info message");Debug.LogWarning("Warning: " + value);Debug.LogError("Error: " + error);Debug.LogFormat("Player pos: {0}", transform.position);// Conditional (only in editor/development builds)Debug.Assert(health > 0, "Health must be positive!");// Visual debug in Scene viewDebug.DrawLine(start, end, Color.red, 2f); // duration 2sDebug.DrawRay(transform.position, Vector3.up * 5f, Color.green);// Gizmos (visible in Scene view, not Game view)void OnDrawGizmos() { Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, detectionRange); Gizmos.DrawLine(transform.position, target.position);}void OnDrawGizmosSelected() { // Only when this object is selected Gizmos.color = Color.red; Gizmos.DrawWireCube(transform.position, Vector3.one);}
using Unity.Profiling;static readonly ProfilerMarker s_MyMarker = new ProfilerMarker("MySystem.Update");void Update() { s_MyMarker.Begin(); // expensive code here s_MyMarker.End();}
C# for Unity — Unity-Specific Patterns
For the full C# language reference see CSharp. For Unity-specific scripting patterns in depth, see CSharp for Unity.
Events & Delegates
// C# event pattern — decoupled communicationpublic class Health : MonoBehaviour{ public event System.Action<int> OnDamaged; public event System.Action OnDeath; public event System.Action<int, int> OnHealthChanged; // current, max int current = 100; int max = 100; public void TakeDamage(int amount) { current = Mathf.Max(0, current - amount); OnDamaged?.Invoke(amount); OnHealthChanged?.Invoke(current, max); if (current == 0) OnDeath?.Invoke(); }}// Subscribehealth.OnDeath += HandleDeath;health.OnHealthChanged += (cur, max) => healthBar.value = (float)cur / max;// Unsubscribe (important — prevents memory leaks)void OnDestroy() { health.OnDeath -= HandleDeath;}
ScriptableObjects — Data Containers
// ScriptableObject — data asset, not attached to a GameObject// Great for: item stats, enemy configs, game settings, shared events[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon")]public class WeaponData : ScriptableObject{ public string weaponName; public int damage; public float fireRate; public float range; public AudioClip fireSound; public GameObject bulletPrefab;}// Create: Right-click Project → Game → Weapon// Use in script:[SerializeField] WeaponData weaponData;void Shoot() { Debug.Log($"Firing {weaponData.weaponName} for {weaponData.damage} damage"); AudioSource.PlayClipAtPoint(weaponData.fireSound, transform.position);}// ScriptableObject Event (Ryan Hipple pattern)[CreateAssetMenu(menuName = "Game/GameEvent")]public class GameEvent : ScriptableObject{ List<GameEventListener> listeners = new(); public void Raise() => listeners.ForEach(l => l.OnEventRaised()); public void Register(GameEventListener l) => listeners.Add(l); public void Unregister(GameEventListener l) => listeners.Remove(l);}
ScriptableObject-based Variables & Events
This architecture pattern decouples code by storing runtime variables and events inside assets. Highly recommended for implementing the modular relationships in Advanced Design Systems.
// Shared Float Variable asset[CreateAssetMenu(menuName = "Variables/FloatVariable")]public class FloatVariable : ScriptableObject{ public float Value;}// Listener Componentpublic class GameEventListener : MonoBehaviour{ [SerializeField] GameEvent Event; [SerializeField] UnityEngine.Events.UnityEvent Response; void OnEnable() => Event.Register(this); void OnDisable() => Event.Unregister(this); public void OnEventRaised() => Response.Invoke();}
Interfaces for Decoupling
public interface IDamageable { void TakeDamage(int amount);}public interface IInteractable { void Interact(GameObject interactor); string GetPrompt();}// Implement on any classpublic class Enemy : MonoBehaviour, IDamageable { public void TakeDamage(int amount) { /* ... */ }}public class Barrel : MonoBehaviour, IDamageable, IInteractable { public void TakeDamage(int amount) { Explode(); } public void Interact(GameObject g) { Push(g); } public string GetPrompt() => "Push barrel";}// Use without knowing the concrete typevoid ShootAt(GameObject target) { if (target.TryGetComponent<IDamageable>(out var damageable)) damageable.TakeDamage(25);}
UniTask (Zero-Allocation Async/Await)
Standard C# Tasks allocate GC memory on every await. UniTask is a struct-based implementation optimized for Unity.
using Cysharp.Threading.Tasks;using System;using UnityEngine;public class AsyncLoadExample : MonoBehaviour{ // Zero allocation async wait public async UniTaskVoid StartGameSequenceAsync() { try { Debug.Log("Starting loading sequence..."); await UniTask.Delay(TimeSpan.FromSeconds(2f), delayType: DelayType.Realtime); // Awaiting scene load directly await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("GameScene").ToUniTask(); Debug.Log("Scene loaded successfully"); } catch (Exception ex) { Debug.LogException(ex); } }}
Non-Allocating Physics Queries
Standard queries (e.g. OverlapSphere) allocate garbage arrays. Always use the NonAlloc methods with a pre-allocated cache.
public class PhysicsScanner : MonoBehaviour{ // Static cache to prevent garbage allocation on invocation readonly Collider[] scanResults = new Collider[20]; [SerializeField] LayerMask targetLayers; void Update() { // Returns the count of colliders found, up to the size of the array int foundCount = Physics.OverlapSphereNonAlloc(transform.position, 5f, scanResults, targetLayers); for (int i = 0; i < foundCount; i++) { Debug.Log($"Found: {scanResults[i].name}"); } }}
Editor Scripting
Extending the Unity Editor to create custom tools, inspectors, and windows. Crucial for streamlining level layout and building prototyping tools during the production pipeline (see Prototyping & Production).
collapsed:: true
Custom Inspectors (IMGUI)
Customize how a script appears inside the Inspector tab.
using UnityEngine;#if UNITY_EDITORusing UnityEditor;[CustomEditor(typeof(Weapon))]public class WeaponEditor : Editor{ public override void OnInspectorGUI() { // Get reference to actual target script Weapon weapon = (Weapon)target; // Draw standard inspector variables DrawDefaultInspector(); EditorGUILayout.Space(); EditorGUILayout.LabelField("Custom Editor Tools", EditorStyles.boldLabel); if (GUILayout.Button("Refill Ammo")) { weapon.Refill(); } }}#endif// Target Component scriptpublic class Weapon : MonoBehaviour{ public int ammo = 10; public void Refill() => ammo = 100;}
Custom Editor Windows
Create persistent, docking windows within the Unity interface.
#if UNITY_EDITORusing UnityEditor;using UnityEngine;public class LevelBuilderWindow : EditorWindow{ GameObject objectToSpawn; [MenuItem("Tools/Level Builder")] public static void ShowWindow() { GetWindow<LevelBuilderWindow>("Level Builder"); } void OnGUI() { GUILayout.Label("Spawning Configuration", EditorStyles.boldLabel); objectToSpawn = (GameObject)EditorGUILayout.ObjectField("Prefab", objectToSpawn, typeof(GameObject), false); if (GUILayout.Button("Spawn at Origin")) { if (objectToSpawn != null) { Instantiate(objectToSpawn, Vector3.zero, Quaternion.identity); } } }}#endif
Property Drawers
Overriding how custom structures or serialized classes display in the editor.
using UnityEngine;#if UNITY_EDITORusing UnityEditor;[System.Serializable]public struct ScaledFloat{ public float value; public float multiplier;}[CustomPropertyDrawer(typeof(ScaledFloat))]public class ScaledFloatDrawer : PropertyDrawer{ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); // Divide drawing space into two columns float width = position.width / 2; Rect valueRect = new Rect(position.x, position.y, width - 5, position.height); Rect multRect = new Rect(position.x + width, position.y, width, position.height); EditorGUI.PropertyField(valueRect, property.FindPropertyRelative("value"), GUIContent.none); EditorGUI.PropertyField(multRect, property.FindPropertyRelative("multiplier"), GUIContent.none); EditorGUI.EndProperty(); }}#endif
UI Toolkit in the Editor
The modern layout engine replacing standard IMGUI using XML-like layout (UXML) and style sheets (USS).
Unity 6 is focused on GPU-driven workflows, visual fidelity, and high performance.
GPU Resident Drawer
What: Move the rendering submission queue from the CPU to the GPU.
graph TD
subgraph Standard Pipeline (CPU Bound)
CPU_B[CPU: Groups Meshes & Calculates Material Properties] -->|Submit Draw Call per batch| GPU_B[GPU Renders meshes]
end
subgraph GPU Resident Drawer (GPU Driven)
CPU_R[CPU: Uploads mesh/material instance buffers once] -->|Draw commands executed on-device| GPU_R[GPU: Culls, groups, and renders directly]
end
How: Instantiated mesh draws are grouped, batched, and rendered directly via GPU-driven instancing.
Advantage: Reduces CPU batching overhead and draw calls by up to 90% in dense scenes.
Setup: In Universal Render Pipeline (URP) or HDRP assets, toggle GPU Resident Drawer in the Rendering configuration settings. Requires devices supporting Shader Model 4.5+.
Concept: Rendering is defined as a directed acyclic graph (DAG) of passes. Resource allocation, dependency checking, and execution are optimized automatically.
How: Resolves a high-fidelity image from lower rendering resolutions (e.g. upscaling 1080p to 4K) using motion vectors and historical frame buffers.
Usage: Set up on the Camera component or pipeline settings. Ideal for high-end graphic pipelines where DLSS/FSR is unavailable.
Adaptive Probe Volumes (APV)
What: Automated global illumination placement replacing manual Light Probes.
How: Dynamically samples bounce light and ambient occlusion grids based on geometry density.
Advantage: Smooth lighting transitions, dynamic streaming of light probes, and prevents light leaks in interior walls.
Performance & Memory Optimization
Essential practices to maintain stable framerates (60/120 FPS) and avoid garbage collection hitching.
Garbage Collection (GC) Optimization
Avoid Boxing: Passing value types as objects allocates memory on the heap. Avoid using string concat inside Update().
Cache Component Reference: Never call GetComponent or .tag in loops. Use tags with CompareTag().
Shader Property Caching: Do not use string names for shaders in update loops. Cache the hash code.
public class OptimizedLoops : MonoBehaviour{ static readonly int BaseColorID = Shader.PropertyToID("_BaseColor"); Material propertyMaterial; void Start() { propertyMaterial = GetComponent<Renderer>().material; } void Update() { // Good: using cached integer property IDs rather than string name "_BaseColor" propertyMaterial.SetColor(BaseColorID, Color.red); // Good: CompareTag performs no GC allocation if (gameObject.CompareTag("Player")) { } }}
Dynamic GC Control: Use Incremental GC (distributes sweeps over multiple frames) or disable garbage collector manually during intensive gameplay sequences:
// Disable garbage collections during a raceUnityEngine.Scripting.GarbageCollector.GCMode = UnityEngine.Scripting.GarbageCollector.Mode.Disabled;// Re-enable when opening game menuUnityEngine.Scripting.GarbageCollector.GCMode = UnityEngine.Scripting.GarbageCollector.Mode.Enabled;
Draw Call & Batching Optimization
graph TD
Meshes[GameObjects to Render] --> Method{Optimization Method?}
Method -->|Static Meshes, Shared Material| SB[Static Batching\nCombines meshes at start into single buffer]
Method -->|Small Dynamic Meshes, Shared Material| DB[Dynamic Batching\nCPU groups meshes on-fly <300 vertices]
Method -->|Same Mesh, Different Material Data| GI[GPU Instancing\nDraws many instances using MaterialPropertyBlock]
Method -->|Different Meshes, Different Materials| SRP[SRP Batcher\nCaches materials in GPU, uploads only transforms]
SB --> Output[Reduced Draw Calls / GPU State Changes]
DB --> Output
GI --> Output
SRP --> Output
Static Batching: Combines static meshes sharing the same material into a single batch at startup.
Dynamic Batching: CPU groups small dynamic meshes on the fly (requires <300 vertices).
GPU Instancing: The GPU draws many instances of the same mesh using different parameters (using MaterialPropertyBlocks).
SRP Batcher: Keeps materials cached in GPU memory and uploads only transform properties. Works out of the box in URP/HDRP when using compatible shaders.
Sprite Atlasing: Combines multiple sprite textures into a single texture asset, preventing multiple draw calls in 2D UI.
Texture Compression
Choose the correct compression format depending on the target platform:
Format
Target Platform
Description
ASTC
Mobile (iOS/Android)
Modern block-based compression. Variable block sizing. High quality.
ETC2
Older Android
Fallback format. Requires square dimensions.
BC7 / DXT5
PC / Console
High-fidelity compression for modern desktop hardware.
Memory Management & Addressables
Resources Folder (Avoid): Assets inside the Resources folder are loaded into memory on startup and increase build sizes.
Addressable Asset System: Loads assets asynchronously and manages their reference counts.
using Unity.Collections;using Unity.Jobs;using UnityEngine;public struct CalculateDistanceJob : IJobParallelFor{ // NativeArray represents unmanaged memory [ReadOnly] public NativeArray<Vector3> startPositions; [ReadOnly] public NativeArray<Vector3> endPositions; public NativeArray<float> outputDistances; public void Execute(int index) { outputDistances[index] = Vector3.Distance(startPositions[index], endPositions[index]); }}
Burst Compiler
What: An LLVM-based compiler that converts C# IL code into highly optimized machine code.
Usage: Requires math calculations using unmanaged structures. Place [BurstCompile] above the Job structure.
Job Scheduling:
using Unity.Collections;using Unity.Jobs;using Unity.Burst;using UnityEngine;public class JobRunner : MonoBehaviour{ void Update() { int dataSize = 1000; // Allocate unmanaged arrays NativeArray<Vector3> starts = new NativeArray<Vector3>(dataSize, Allocator.TempJob); NativeArray<Vector3> ends = new NativeArray<Vector3>(dataSize, Allocator.TempJob); NativeArray<float> distances = new NativeArray<float>(dataSize, Allocator.TempJob); var job = new CalculateDistanceJob { startPositions = starts, endPositions = ends, outputDistances = distances }; // Schedule job across worker threads (inner loop batch count: 64) JobHandle handle = job.Schedule(dataSize, 64); // Block main thread until calculations are done handle.Complete(); // Dispose native memory to prevent memory leaks starts.Dispose(); ends.Dispose(); distances.Dispose(); }}
Entity Component System (ECS) Overview
Entities: Identifiers (indices) replacing standard GameObjects.
Components: Structs holding pure data (IComponentData) without any behavior.
Systems: Classes managing the data transform logic (ISystem or SystemBase). Executes over groups of entities matching component queries.
graph TD
subgraph Entity Manager
E1[Entity 1]
E2[Entity 2]
E3[Entity 3]
end
subgraph Component Data Arrays
C_Pos[Position Component Data Array]
C_Vel[Velocity Component Data Array]
C_Health[Health Component Data Array]
end
subgraph Systems
MovementSystem[Movement System]
DamageSystem[Damage System]
end
E1 --> C_Pos
E1 --> C_Vel
E2 --> C_Pos
E2 --> C_Health
E3 --> C_Pos
E3 --> C_Vel
E3 --> C_Health
MovementSystem -->|Iterates over Position + Velocity| C_Pos
MovementSystem -->|Iterates over Position + Velocity| C_Vel
DamageSystem -->|Iterates over Health| C_Health
Netcode for GameObjects (NGO)
Unity’s official high-level networking framework for multiplayer games. Used to implement the social/multiplayer mechanics defined in Game Mechanics Design.
collapsed:: true
graph TD
subgraph Client 1 (Owner)
C1_Input[Input / Movement]
end
subgraph Server (Authority)
S_State[Validate & Calculate State]
NetVar[Synced NetworkVariable]
end
subgraph Client 2 (Proxy)
C2_Visual[Visual Position / State]
end
C1_Input -->|1. ServerRpc Request| S_State
S_State -->|2. Updates| NetVar
NetVar -->|3. Auto-Replicated Sync| C2_Visual
S_State -->|4. ClientRpc Broadcast| C2_Visual
NetworkManager Setup
The root controller in the scene. Holds references to the transport layer (e.g. Unity Transport) and registered Network Prefabs.
// Quick Host/Server startup scriptpublic class ConnectUI : MonoBehaviour{ void OnGUI() { GUILayout.BeginArea(new Rect(10, 10, 300, 300)); if (!Unity.Netcode.NetworkManager.Singleton.IsClient && !Unity.Netcode.NetworkManager.Singleton.IsServer) { if (GUILayout.Button("Host (Server + Client)")) NetworkManager.Singleton.StartHost(); if (GUILayout.Button("Server Only")) NetworkManager.Singleton.StartServer(); if (GUILayout.Button("Client Join")) NetworkManager.Singleton.StartClient(); } GUILayout.EndArea(); }}
NetworkVariable & Syncing
Syncs state dynamically across connected clients.
using Unity.Netcode;using UnityEngine;public class PlayerSync : NetworkBehaviour{ // Sync variable with Owner write rights public NetworkVariable<Vector3> NetPosition = new NetworkVariable<Vector3>( writePerm: NetworkVariableWritePermission.Owner ); void Update() { if (IsOwner) { NetPosition.Value = transform.position; } else { transform.position = NetPosition.Value; } }}
Remote Procedure Calls (RPCs)
Execute methods across client-server boundaries.
ServerRpc: Sent by clients, executed on the server.
ClientRpc: Sent by the server, executed on all clients.
using Unity.Netcode;using UnityEngine;public class CombatNetwork : NetworkBehaviour{ // Client requests damage calculation on the server [ServerRpc] public void TakeDamageServerRpc(int amount) { int remainingHealth = 100 - amount; UpdateHealthUIClientRpc(remainingHealth); } // Server notifies all clients to update health interface [ClientRpc] public void UpdateHealthUIClientRpc(int currentHealth) { Debug.Log($"Update client UI. Health: {currentHealth}"); }}
Spawning Prefabs
Spawning objects on the server instantiates them on all clients.
public class Spawner : NetworkBehaviour{ [SerializeField] GameObject prefabToNetworkSpawn; [ServerRpc] public void SpawnObjectServerRpc(Vector3 pos) { GameObject instance = Instantiate(prefabToNetworkSpawn, pos, Quaternion.identity); // Register spawned entity across the network instance.GetComponent<NetworkObject>().Spawn(); }}
Library & Frameworks
Core Unity Packages
Universal Render Pipeline — Scalable, high-performance rendering from mobile devices to high-end PCs.