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() { }}
Lifecycle Order
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);}
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);}