agentsclimarketplace

Unity manual

Skill Zions-store/unity-manual

Unity engine core concepts and best practices. Use when the user mentions Unity, GameObject, Component, MonoBehaviour, Scene, URP, HDRP, render pipeline, physics engine, animation system, Animator, UI Toolkit, uGUI, audio, Input System, Input Manager, prefab, ScriptableObject, camera, Cinemachine, 2D, Sprite, particle system, VFX, Shuriken, lighting, Light Probe, build, publish, async await, UniTask, Addressables, custom inspector, events, delegates, UnityEvent, timeScale, pause, Lerp, SmoothDamp, raycast, object pooling, singleton, or is working on Unity game development tasks.From its SKILL.md

Install
npx -y skills add Zions-store/unity-manual

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

60.1 KB, ~14.2k tokens by cl100k_base, as published. Nobody here has run it

Copyright (C) 2026 ZionXiaoxiSuOGLocGo SPDX-License-Identifier: GPL-3.0-or-later

Unity Engine Manual Skill

Covers Unity core concepts, API patterns, and best practices across versions from 2022.3 LTS to Unity 6.

When to Use This Skill

Use this skill whenever the user asks about Unity development — writing scripts, designing scenes, debugging, optimizing, or understanding engine behaviour. This skill provides the mental model of how Unity works, not just API signatures.

Editor Basics

Unity's editor is organized into dockable panels:

PanelPurpose
Scene ViewInteractive 3D/2D viewport. Click to select objects, drag to move.
Game ViewPreview what the player sees. Rendered from the active Camera.
HierarchyList of all GameObjects in the current Scene. Drag to reparent.
InspectorShows properties of the selected GameObject. Edit Components here.
Project WindowYour project's asset files. Drag assets into Scene or Hierarchy.
ConsoleLogs, warnings, errors. Debug.Log("message") prints here.

Scene navigation:

ActionKeys
Orbit around pivotHold Alt + Left Mouse Button + drag
Pan (slide view)Hold Alt + Middle Mouse Button + drag
ZoomScroll wheel or hold Alt + Right Mouse Button + drag
Focus on selected objectPress F
Fly-through modeHold Right Mouse Button + W/A/S/D (hold Shift to speed up)
Reset pivot to originDouble-click Middle Mouse Button

Transform tools:

KeyMode
WMove
ERotate
RScale
QHand tool (pan view)
TRect tool (2D)
YTransform tool (move+rotate+scale)
Ctrl+Shift+FAlign with view (points object at camera)
VVertex snapping (hold and drag a corner to snap to another vertex)

Play modes: Use the toolbar Play/Pause/Step buttons to enter Play Mode, pause execution, or advance frame-by-frame.

Project Setup

Creating a New Project

Open Unity Hub → New Project → pick a template:

TemplateWhat You Get
2D (Built-In Render Pipeline)2D project, sprite defaults, no render pipeline package
2D (URP)2D project with URP, Shader Graph, 2D lighting
3D (Built-In Render Pipeline)3D project, legacy renderer
3D (URP)3D project with Universal Render Pipeline
3D (HDRP)High-end 3D with HD Render Pipeline
Universal 3D (Unity 6+)Unified template for mobile + desktop

Project Folder Structure

ProjectName/
├── Assets/          ← All your files live here (scenes, scripts, textures...)
├── Packages/        ← Package manifest (auto-managed)
├── ProjectSettings/ ← Editor/player settings, build config
├── Library/         ← Cached imports, shader compilation (ignore in git)
├── Temp/            ← Temporary files (ignore)
├── Logs/            ← Editor logs
└── UserSettings/    ← Per-user editor preferences (ignore)

Unity Version Management

Use Unity Hub to install and switch engine versions. Projects created in newer versions can't be opened in older ones. Always make a backup before upgrading.

Version Control Setup

Set in Edit → Project Settings → Editor:

  • Asset Serialization: Force Text
  • Version Control Mode: Visible Meta Files

Required .gitignore entries:

[Ll]ibrary/
[Tt]emp/
[Oo]bj/
[Bb]uild/
[Bb]uilds/
Logs/
UserSettings/
*.csproj
*.sln

.meta files MUST be committed — they store import settings, references, and GUIDs.

Package Manager

Unity's modular system for adding features. Window → Package Manager.

PackagePurpose
Input SystemModern input handling (replaces legacy Input Manager)
CinemachineAdvanced camera system
Universal RP / High Definition RPRender pipelines
Shader GraphVisual shader creation
Visual Effect GraphGPU particle effects
ProBuilderIn-editor 3D modeling
TextMeshProHigh-quality text rendering (default in Unity 6)
AddressablesAsync asset loading and content updates
Netcode for GameObjectsMultiplayer networking
2D Tilemap Editor2D tile-based level design

Click Install to add a package, Update for newer versions, Remove to uninstall.


GameObject / Component Architecture

Every entity in Unity is a GameObject. GameObjects themselves do nothing — they are containers for Components.

Core Principles

  • GameObject = identity (name, tag, layer, active state) + transform + component list
  • Component = behaviour (Rigidbody, Collider, MeshRenderer, custom scripts)
  • Transform = always present, cannot be removed; position/rotation/scale in local and world space
  • AddComponent<T>() adds at runtime; GetComponent<T>() retrieves; TryGetComponent<T>() is allocation-free

Common Patterns

// Cache references in Awake() — not in Start(), not in Update()
Rigidbody rb;
void Awake() { rb = GetComponent<Rigidbody>(); }

// Prefer TryGetComponent to avoid null checks
if (TryGetComponent<Collider>(out var col)) { /* use col */ }

// RequireComponent enforces dependencies at edit time
[RequireComponent(typeof(Rigidbody))]
public class Player : MonoBehaviour { }

Key Rules

  • Do not use new to create GameObjects — use Instantiate(), GameObject.CreatePrimitive(), or prefabs
  • Destroy with Destroy(gameObject) or Destroy(gameObject, delay) — never null assign and expect GC
  • gameObject.SetActive(false) disables the whole object; enabled = false disables one component
  • Child GameObjects move/rotate/scale relative to their parent's Transform

MonoBehaviour Lifecycle

Execution order is fixed. Understanding it prevents 90% of Unity timing bugs.

Execution Order (simplified)

  1. Awake() — called once when object is created. Use for self-referencing, not other objects (they may not be awake yet).
  2. OnEnable() — called each time the object becomes active. Good for event subscriptions.
  3. Start() — called once before first Update, after all Awake() complete. Safe to reference other objects here.
  4. FixedUpdate() — physics timestep (default 0.02s). Put Rigidbody operations here.
  5. Update() — once per frame. Most gameplay logic.
  6. LateUpdate() — after all Update() calls. Camera follow, procedural animation.
  7. OnDisable() — when object deactivates. Unsubscribe from events here.
  8. OnDestroy() — when object is destroyed. Final cleanup.

Common Pitfalls

  • Getting another object's component in Awake() → that object's Awake() may not have run yet. Use Start() instead.
  • Physics in Update() → jittery movement. Use FixedUpdate().
  • Input in FixedUpdate() → missed inputs. Use Update().
  • Forgetting to unsubscribe events in OnDisable() → memory leaks and null reference errors.

C# Scripting Basics

Every script is a class that inherits from MonoBehaviour:

using UnityEngine;

// Note: Simplified for teaching. For production physics movement, use
// rb.velocity or rb.MovePosition in FixedUpdate instead (see §Physics).
public class Player : MonoBehaviour
{
    // === FIELDS ===
    [SerializeField] private float speed = 5f;
    [SerializeField] private GameObject bulletPrefab;
    public int health = 100;

    void Update()
    {
        float h = Input.GetAxis("Horizontal");
        transform.Translate(Vector3.forward * h * speed * Time.deltaTime);
    }
}

Field Visibility Cheat Sheet

DeclarationVisible in InspectorEditable in Inspector
public int x✅✅
[SerializeField] private int x✅✅
[HideInInspector] public int x❌❌
private int x❌❌
[NonSerialized] public int x❌❌

Common Types

Unity TypeC# EquivalentUse
Vector3(x, y, z) structPosition, direction, scale
Vector2(x, y) struct2D position, input, UV
QuaternionRotation structUse Quaternion.Euler() to create from degrees
Color(r, g, b, a) struct0–1 range for each channel
TransformComponentPosition/Rotation/Scale of a GameObject
GameObjectObjectThe GameObject itself

Time.deltaTime

Always multiply per-frame changes by Time.deltaTime (= seconds since last frame) to make movement frame-rate independent:

// ❌ Frame-rate dependent
transform.Translate(Vector3.forward * speed);

// ✅ Frame-rate independent
transform.Translate(Vector3.forward * speed * Time.deltaTime);

Lerp / SmoothDamp / MoveTowards

These functions smoothly interpolate values. The key mistake is misusing the t parameter in Lerp:

// ❌ WRONG: Passing Time.deltaTime as t — never reaches target
transform.position = Vector3.Lerp(transform.position, target, Time.deltaTime);

// ✅ FIX: t should be a progress ratio (0→1)
float t = elapsed / duration; // 0 to 1 over time
transform.position = Vector3.Lerp(start, target, t);

// ✅ Smooth damping (spring-like motion, no overshoot, frame-rate independent)
transform.position = Vector3.SmoothDamp(transform.position, target, ref velocity, 0.3f);

// ✅ Linear movement at fixed speed
transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);

// ✅ Color fade
spriteRenderer.color = Color.Lerp(Color.white, Color.clear, t);

// ✅ Easing (smoother start/stop)
float eased = Mathf.SmoothStep(0, 1, t);
FunctionUse for
LerpGeneric interpolation (position, color, float)
SmoothDampCamera follow, springy movement, no overshoot
MoveTowardsFixed-speed movement toward a target
SmoothStepEased in/out interpolation
InverseLerpConvert a value to a 0-1 ratio

Namespace

Core Unity Namespaces

NamespacePurpose
UnityEngineCore API: MonoBehaviour, GameObject, Transform, Vector3, Debug, Time, Random
UnityEditorEditor-only: CustomEditor, EditorWindow, MenuItem. NOT included in builds.
UnityEngine.UIuGUI: Button, Text, Slider, Canvas
UnityEngine.UIElementsUI Toolkit: VisualElement, Button, Label
UnityEngine.InputSystemNew Input System: InputAction, PlayerInput, Keyboard, Mouse
UnityEngine.SceneManagementScene loading: SceneManager, LoadSceneAsync
UnityEngine.AddressableAssetsAddressables resource management
CinemachineCinemachine virtual cameras
UnityEngine.VFXVFX Graph (GPU particles)
UnityEngine.EventsUnityEvent, UnityAction
Unity.MathematicsBurst-compatible math: float3, float4x4, quaternion
Unity.CollectionsHigh-performance containers: NativeArray, NativeList
SystemDateTime, Math, Action, Func, EventHandler
System.Collections.GenericList<T>, Dictionary<K,V>, Queue<T>, Stack<T>
System.LinqLINQ queries. Use sparingly — allocates GC.
System.Threading.TasksNative Task, async/await
Cysharp.Threading.TasksUniTask (recommended async library, zero-allocation)

Custom Namespace

Wrap your scripts in a namespace to prevent class name conflicts (essential when project scales up):

// ❌ Global scope — "Controller" could conflict with another "Controller"
public class Controller : MonoBehaviour { }

// ✅ Namespaced — unambiguous
namespace MyGame.Player
{
    public class Controller : MonoBehaviour
    {
        private void Update()
        {
            float h = Input.GetAxis("Horizontal");
            transform.Translate(Vector3.forward * h * Time.deltaTime);
        }
    }
}

// Usage elsewhere:
using MyGame.Player;  // then just: Controller controller;
// or without using:  MyGame.Player.Controller controller;

Namespace Limitation (Unity 2020.1–2022.1 only)

In Unity versions prior to 2022.2, a single file could not contain MonoBehaviour classes from different namespaces. This restriction was lifted in Unity 2022.2. Since this skill targets 2022.3+, the limitation no longer applies. Old Asset Store packages may still contain this issue from pre-2022.2 projects.

Null Reference Handling

The most common Unity error: NullReferenceException. You tried to use something that doesn't exist.

Causes and fixes:

CauseFix
GetComponent<T>() returned null (component not attached)Add [RequireComponent(typeof(T))] or add component manually
Inspector field not assignedAssign the reference in Inspector, or use [SerializeField]
GameObject.Find() returned nullWrong name or GameObject not in scene — prefer serialized references
Instantiate() returned nullPrefab field not assigned in Inspector
Reference destroyed between framesCheck if (obj != null) or use TryGetComponent()
Event listener on destroyed objectAlways unsubscribe in OnDisable() / OnDestroy()

Defensive pattern:

[SerializeField] private Rigidbody rb; // assign in Inspector

void Start()
{
    if (rb == null)
        rb = GetComponent<Rigidbody>(); // fallback if not assigned

    // Prefer TryGetComponent to avoid null entirely:
    if (TryGetComponent<Collider>(out var col))
        col.enabled = false;
}

Unity's special null: Unity overrides == — a destroyed GameObject is null but not C# null. ?. operator may behave unexpectedly. Stick with explicit if (obj == null) checks or if (obj) (Unity's implicit bool conversion).


Events & Delegates

Unity provides several ways for scripts to communicate:

1. Serialized Reference (simplest)

Drag one GameObject into another's Inspector field:

[SerializeField] private GameObject otherObject;
// Then: otherObject.GetComponent<Enemy>().TakeDamage(10);

2. UnityEvent (Inspector-wired, designer-friendly)

using UnityEngine.Events;

public class Health : MonoBehaviour
{
    private int health = 100;
    public UnityEvent onDeath;  // shows in Inspector

    public void TakeDamage(int amount)
    {
        health -= amount;
        if (health <= 0)
            onDeath.Invoke(); // fires all wired listeners
    }
}

In Inspector, click + on OnDeath, drag a GameObject, pick a method — no code needed.

3. C# event / Action (code-only, performant)

using System;

public class Health : MonoBehaviour
{
    public event Action OnDeath;  // C# event

    private void Die()
    {
        OnDeath?.Invoke(); // null-conditional: only fires if someone subscribed
    }
}

// Listener:
void OnEnable()  { GetComponent<Health>().OnDeath += Respawn; }
void OnDisable() { GetComponent<Health>().OnDeath -= Respawn; }
void Respawn()   { /* ... */ }

4. SendMessage (avoid for production)

// Slow, string-based, no compile-time checking. Legacy.
gameObject.SendMessage("OnDamage", 10);
// Use event/UnityEvent instead.

Communication Cheat Sheet

NeedUse
Known objects in same sceneSerialized field reference
Designer wants to wire in InspectorUnityEvent
Many listeners, code-only, high performanceC# event / Action
Global/manager singletonStatic instance or Singleton pattern
Cross-scene communicationScriptableObject event channel

Time.timeScale & Pausing

Time.timeScale controls the speed of time for the entire game:

// Pause the game (freezes Update, FixedUpdate, Coroutines, physics, audio)
Time.timeScale = 0f;

// Slow motion (half speed)
Time.timeScale = 0.5f;

// Resume normal
Time.timeScale = 1f;

Pausing done right:

bool isPaused = false;

void Update()
{
    if (Input.GetKeyDown(KeyCode.Escape))
    {
        isPaused = !isPaused;
        Time.timeScale = isPaused ? 0f : 1f;
        pauseMenu.SetActive(isPaused);
    }
}

What timeScale affects and what it doesn't:

AffectedNOT affected
Update(), FixedUpdate(), LateUpdate()Time.unscaledDeltaTime (use for pause menu UI)
Coroutines (WaitForSeconds etc.)WaitForSecondsRealtime
Physics simulationUpdate() with Time.unscaledDeltaTime
Audio (pitch changes)Animator.updateMode = UnscaledTime

Selective pausing: For per-object pause (e.g., pause enemies but not player), use a manual bool flag + if (isPaused) return; at the top of Update() rather than timeScale.


Scene Management

Core API (Unity 5.3+ SceneManager)

using UnityEngine.SceneManagement;

// Load single scene (replaces current)
SceneManager.LoadScene("Level2");
SceneManager.LoadScene(1); // build index

// Async loading with progress
var op = SceneManager.LoadSceneAsync("Level2");
op.allowSceneActivation = false; // pause before activation
while (op.progress < 0.9f) { /* show loading bar */ }
op.allowSceneActivation = true;

// Additive loading (multiple scenes at once)
SceneManager.LoadScene("UI", LoadSceneMode.Additive);
SceneManager.UnloadSceneAsync("UI");

DontDestroyOnLoad

Objects that persist across scene loads (audio managers, game state). Creates a new root GameObject on scene change.

void Awake() { DontDestroyOnLoad(gameObject); }

Version Notes

  • SceneManager API stable across 2022.3 → Unity 6
  • Scene references via build settings index or full path name

Camera

Camera Component

PropertyPurpose
Clear FlagsSkybox (default), Solid Color, Depth Only, Don't Clear
ProjectionPerspective (3D) or Orthographic (2D, isometric)
Field of ViewVertical FOV in degrees (perspective only)
SizeHalf-height in world units (orthographic only)
Clipping PlanesNear (default 0.3) and Far (default 1000) — objects outside this range are culled
Culling MaskWhich layers the camera renders (e.g., hide UI from main camera)
DepthRendering order (higher = renders on top)
Target TextureRender to a RenderTexture instead of the screen (minimaps, portals, CCTV)

Camera Scripting

Camera cam = Camera.main; // returns first camera tagged "MainCamera"

// Convert screen point to world ray
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
    Debug.Log(hit.point); // world position under cursor

// Convert world to screen (for health bars, nameplates)
Vector3 screenPos = cam.WorldToScreenPoint(enemy.position);
if (screenPos.z > 0) // in front of camera
    UIElement.position = screenPos;

Multiple Cameras

SetupHow
MinimapSecond camera → Target Texture → RawImage in Canvas
Weapon overlayCamera with Culling Mask = "Weapon" layer only, Depth = 1
Split-screenTwo cameras, each with rect set to half the screen
UI cameraCamera with Culling Mask = "UI" layer, Clear Flags = Depth Only

Cinemachine

For advanced camera behaviors (follow, look-at, shake, transitions):

using Cinemachine; // requires Cinemachine package

// Virtual camera that follows a target
CinemachineVirtualCamera vcam;
vcam.Follow = player.transform;
vcam.LookAt = player.transform;

// Camera shake
vcam.GetCinemachineComponent<CinemachineBasicMultiChannelPerlin>().m_AmplitudeGain = 2f;

Cinemachine handles blending, collision detection, and dead zones — use it instead of manual camera scripting for complex setups.


Render Pipelines

Unity has three render pipelines. The user's project determines which to use.

Pipeline Overview

PipelineUse CasePipeline Asset Type
Built-inLegacy projects, quick prototypingNone (default)
URPMost projects (2D, 3D, mobile, cross-platform)UniversalRenderPipelineAsset
HDRPHigh-end 3D (PC, console, realistic graphics)HDRenderPipelineAsset

URP Key Concepts

  • Forward/Deferred rendering: Forward for most cases; Deferred for many dynamic lights
  • Renderer Features: Custom pass injection without full SRP coding
  • Shader Graph: Visual shader creation, URP-native
  • 2D Renderer: Dedicated 2D lighting and shadow system
  • Camera Stacking: Overlay/Base camera system for UI overlays, minimaps

HDRP Key Concepts

  • Physically-based rendering: Realistic materials, lighting, volumetrics
  • HDRP Asset configuration: Global rendering settings (shadows, LOD, decals)
  • Volume framework: Post-processing, sky, fog applied per-zone via volumes
  • Ray tracing: Hardware-accelerated reflections, shadows, GI (Unity 6+)

URP Version Differences

  • 2022.3: RenderGraph API experimental; URP 14.x; Forward+ path available
  • Unity 6: RenderGraph default; URP 17.x; GPU Resident Drawer; Probe Volumes for GI
  • See references/urp-2022.md and references/urp-unity6.md for detailed differences

Shader Basics

// URP shader include (HLSL)
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

// Accessing main light in URP
Light mainLight = GetMainLight();
half3 lightDir = mainLight.direction;
half3 lightColor = mainLight.color;

Lighting

Light Types

TypeUse Case
Directional LightSun/moon. Affects everything regardless of position.
Point LightLight bulb, explosion. Emits in all directions, has range.
Spot LightFlashlight, headlight. Cone-shaped, range + angle.
Area LightSoft lighting from a rectangular surface. Baked only (not real-time).

Real-time vs Baked

ModePerformanceQualityDynamic Objects
RealtimeExpensiveGoodLit and cast shadows
Baked (Lightmap)Cheap at runtimeBest (precomputed)Only receive indirect light, no shadows
MixedMediumGoodStatic objects baked, dynamic objects get real-time

Baking requires objects to be marked Static (Inspector → Static checkbox) and having Generate Lighting enabled.

Light Probes & Reflection Probes

  • Light Probes: Sample lighting at discrete positions in a scene. Dynamic objects use the nearest probes for indirect lighting. Essential when mixing baked lighting with moving characters.
  • Reflection Probes: Capture the environment for reflections. Place near reflective surfaces (metal, water, glass).

Tags & Layers

Tags and Layers are metadata for identifying and filtering GameObjects:

Tag — identifies a specific object:

if (other.CompareTag("Player")) { /* hit the player */ }
GameObject player = GameObject.FindWithTag("Player"); // returns one object
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); // all objects

// Set tag: Inspector → Tag dropdown, or script:
gameObject.tag = "Enemy";

Layer — filters rendering and collision:

// Check if an object is on a specific layer
if (other.gameObject.layer == LayerMask.NameToLayer("Enemy"))
    return; // ignore enemies

// LayerMask for raycast filtering
int enemyMask = LayerMask.GetMask("Enemy", "Obstacle");
Physics.Raycast(origin, dir, out hit, 100f, enemyMask);

Define tags and layers in Edit → Project Settings → Tags and Layers.


Physics

Rigidbody vs Rigidbody2D

3D physics (NVIDIA PhysX) → Rigidbody + Collider (Box/Sphere/Capsule/Mesh/Terrain) 2D physics (Box2D) → Rigidbody2D + Collider2D

Rigidbody Properties

PropertyPurpose
massAffects collision response, not gravity speed
dragAir resistance (0 = no resistance)
angularDragRotational resistance
useGravityEnable/disable gravity
isKinematicPhysics-driven or script-driven
interpolationSmooth movement for jitter reduction
collisionDetectionModeContinuous for fast objects (prevents tunnelling)

Applying Forces

// In FixedUpdate() only
void FixedUpdate() {
    rb.AddForce(Vector3.forward * speed);        // continuous force
    rb.AddForce(Vector3.up * 10f, ForceMode.Impulse);      // instant push
    rb.MovePosition(targetPos);                    // kinematic movement
}

Collision Detection

void OnCollisionEnter(Collision col) { /* first contact */ }
void OnCollisionStay(Collision col) { /* persistent contact */ }
void OnCollisionExit(Collision col) { /* contact ends */ }

// Trigger variant (IsTrigger = true on collider)
void OnTriggerEnter(Collider other) { }

Layers & Collision Matrix

Use Edit → Project Settings → Physics → Layer Collision Matrix to disable collisions between specific layers. Performance-critical for complex scenes.

Physics vs Transform Movement

The #1 beginner pitfall: moving a Rigidbody by changing transform.position directly.

// ❌ BROKEN: Bypasses physics, causes tunneling, ignores collisions
transform.position += Vector3.forward * speed * Time.deltaTime;

// ❌ ALSO BROKEN: Same problem
rb.position = newPosition; // Better but still teleports (instant, no velocity transfer)

// ✅ CORRECT: Use velocity or forces (in FixedUpdate)
rb.velocity = new Vector3(input.x * speed, rb.velocity.y, input.y * speed);

// ✅ OR: Use forces for acceleration-based movement
rb.AddForce(Vector3.forward * force);

// ✅ Kinematic (non-physics) object: use MovePosition in FixedUpdate
void FixedUpdate() { rb.MovePosition(rb.position + moveDir * speed * Time.fixedDeltaTime); }
When to useMethod
Player/enemy with physics interactionrb.velocity or AddForce() in FixedUpdate
Platform/trigger/kinematic objectrb.MovePosition() in FixedUpdate
Purely visual (no collision, no physics)transform.Translate() in Update
Character controller (no Rigidbody)CharacterController.Move()

Raycasting

Fire a ray into the scene and get what it hits — essential for shooting, interaction, AI vision:

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 100f))
{
    Debug.Log($"Hit: {hit.collider.name} at {hit.point}");

    // Get the object hit
    GameObject obj = hit.collider.gameObject;

    // Check tag
    if (hit.collider.CompareTag("Enemy"))
        hit.collider.GetComponent<Enemy>().TakeDamage(10);

    // Visualize in Scene view
    Debug.DrawRay(ray.origin, ray.direction * hit.distance, Color.red, 1f);
}

// Filter by layer mask
int enemyMask = LayerMask.GetMask("Enemy", "Obstacle");
if (Physics.Raycast(ray, out hit, 100f, enemyMask)) { /* only hits Enemies/Obstacles */ }

// Sphere/box casts (like a thick ray)
if (Physics.SphereCast(ray.origin, 0.5f, ray.direction, out hit, 10f)) { }

// RaycastAll (get all hits along ray, sorted by distance)
RaycastHit[] hits = Physics.RaycastAll(ray, 100f);

Unreal equivalent: UWorld::LineTraceSingleByChannel() in C++, or the Line Trace By Channel node in Blueprints.

Common Pitfall

Ignoring the raycast origin — ScreenPointToRay starts at camera position. For FPS shooting from the player's gun barrel, use gunBarrel.position as origin rather than camera.

2D Basics

Unity has a dedicated 2D mode with specialized tools:

Core Components

Component3D EquivalentPurpose
Sprite RendererMeshRendererRenders a 2D sprite (image)
SpriteTexture/MaterialThe 2D image asset
Rigidbody2DRigidbody2D physics body (Box2D engine)
Collider2D (Box/Circle/Polygon)Collider (Box/Sphere)2D collision shape
Tilemap—Grid-based tile placement
Tilemap Renderer—Renders tilemap grids

Sprite Editor

Select a sprite asset → Sprite Editor button in Inspector. Slice sprite sheets into individual sprites via Slice → Grid by Cell Size or Automatic.

2D Physics Differences

// 2D physics uses different APIs
Rigidbody2D rb2d;
void OnCollisionEnter2D(Collision2D col) { }  // note the "2D" suffix
void OnTriggerEnter2D(Collider2D other) { }

// Movement in 2D
rb2d.velocity = new Vector2(moveSpeed, rb2d.velocity.y); // horizontal movement only

// 2D raycast
RaycastHit2D hit = Physics2D.Raycast(origin, direction, distance, layerMask);

Sorting Order

Sprites render in order controlled by:

  1. Sorting Layer (background, midground, foreground)
  2. Order in Layer (number within the layer, higher = on top)
  3. Distance from camera (for transparent sort axis)

Set in SpriteRenderer or via script: spriteRenderer.sortingOrder = 5;


Animation

Animator Controller

State machine-based animation system. Components: Animator component + AnimatorController asset.

Animator anim;
void Awake() { anim = GetComponent<Animator>(); }

// Parameter types: Float, Int, Bool, Trigger
anim.SetFloat("Speed", moveSpeed);
anim.SetBool("IsGrounded", isGrounded);
anim.SetTrigger("Jump");

// State info
AnimatorStateInfo state = anim.GetCurrentAnimatorStateInfo(0);
if (state.IsName("Run")) { /* ... */ }

// Cross-fade with duration
anim.CrossFade("Death", 0.1f);

// Immediate state transition (no blend)
anim.Play("Death");

// Cache hash for performance (avoid string lookup per call)
int deathHash = Animator.StringToHash("Death");
if (state.shortNameHash == deathHash) { /* in Death state */ }

Animation Curves

Expose parameters to curves for precise timeline control. Useful for root motion, hitbox timing, footstep events.

Legacy Animation Component

Still functional but deprecated. Use Animator for new projects.

Humanoid vs Generic Rigs

  • Humanoid: Avatar system for retargeting animations between characters. Required for Mecanim.
  • Generic: For non-human characters, simpler setup.

Animation Retargeting (Humanoid Avatar)

Unity's Humanoid Avatar system enables reusing animations across different characters, similar to Unreal's Compatible Skeleton retargeting:

  1. Configure Avatar: Select imported model → Rig tab → Animation Type: Humanoid → Apply. Unity auto-maps bones.
  2. Verify mapping: Click "Configure" → check bone assignment (head, spine, arms, legs). Fix unmapped bones (green = good, grey = missing).
  3. Retarget to another model: Select animator's Avatar → set it to any Humanoid skeleton. Animations auto-retarget — different proportions handled.

Sharing animations across models:

// Import animation once, use on multiple characters:
// Set "Copy From Other Avatar" in the animation import settings
// Point to any model with a valid Humanoid Avatar

Muscle space control: Use Avatar Mask to control which body parts receive animation (e.g., upper body running + lower body custom IK).

// Apply animation with mask
animator.SetLayerWeight(layerIndex, 1f); // enable layer
// Layer mask in Animator Controller controls which body parts animate

Common issues:

  • T-pose instead of animation: Avatar not configured Humanoid for source model
  • Bone stretching: bone mapping incorrect in Avatar configuration
  • Model scale mismatch: check "Use Normalized" for muscle interpolation

VFX (Particle System)

Shuriken Particle System

Unity's built-in CPU particle system. Add a Particle System component to any GameObject.

ModuleWhat It Controls
MainDuration, looping, start lifetime/speed/size/color, gravity modifier
EmissionRate over time or distance, burst (one-time spawn)
ShapeEmission volume: sphere, cone, box, circle, mesh
Color over LifetimeGradient across particle lifespan
Size over LifetimeScale curve
Velocity over LifetimeSpeed changes over time
NoiseTurbulence, random wobble
RendererHow particles look: billboard, mesh, stretched billboard
// Control via script
ParticleSystem ps;
ps.Play();          // start emission
ps.Stop();          // stop emitting (existing particles continue)
ps.Clear();         // remove all particles
ps.emissionRate = 50f;

// One-shot burst at position
ParticleSystem.EmitParams emitParams = new ParticleSystem.EmitParams();
emitParams.position = transform.position;
emitParams.velocity = Vector3.up * 5f;
ps.Emit(emitParams, 10); // emit 10 particles

VFX Graph (GPU)

Package: Visual Effect Graph. GPU-based, handles millions of particles. Visual node-based editor. Use for complex VFX (swarms, trails, reactive particles). Requires URP or HDRP — not available in Built-in Render Pipeline.

Choosing Shuriken vs VFX Graph

ScenarioUse
Simple effects (< 1000 particles)Shuriken
Complex behavior, GPU computeVFX Graph
Mobile / Built-in RPShuriken (VFX Graph not supported)
Millions of particlesVFX Graph

UI Toolkit vs uGUI

Unity has two UI systems. Both are actively supported.

uGUI (UnityEngine.UI)

The original Canvas-based UI system. Simpler, more tutorials available.

using UnityEngine.UI;

Button btn = GetComponent<Button>();
btn.onClick.AddListener(() => Debug.Log("Clicked"));

Text label = GetComponent<Text>();
label.text = "Hello";

Slider slider = GetComponent<Slider>();
slider.onValueChanged.AddListener(OnVolumeChange);

Canvas render modes: Screen Space Overlay, Screen Space Camera, World Space.

Anchors and scaling: Select a UI element → Rect Transform → Anchor Presets. Anchors define how the element resizes/stretches when screen resolution changes. Hold Shift to set pivot, Alt to set position. For responsive layouts: Unity uses stretch anchors (the 4-arrow icon) to make UI fill a percentage of the screen.

Canvas Scaler: Controls how UI scales across resolutions → Constant Pixel Size (fixed), Scale With Screen Size (responsive, recommended), Constant Physical Size.

UI Toolkit (UIElements)

Newer retained-mode UI system. CSS-like styling (USS), XML-like layout (UXML). Better for complex editor tools and scalable game UI.

using UnityEngine.UIElements;

var root = GetComponent<UIDocument>().rootVisualElement;
var btn = root.Q<Button>("start-btn");
btn.clicked += () => Debug.Log("Clicked");

var label = root.Q<Label>("title");
label.text = "Hello";

When to Use Which

  • Rapid prototyping, mobile UI, simple menus → uGUI
  • Complex data-driven UI, editor tools, scalable runtime UI → UI Toolkit
  • Unity 6 pushes UI Toolkit more aggressively, but uGUI is not deprecated

Audio

Audio Source + Audio Listener

  • AudioSource: Plays audio clips (2D/3D, spatial blend, volume, pitch, loop)
  • AudioListener: Usually on Main Camera, receives audio
AudioSource source;

// One-shot sound effects (no component needed)
AudioSource.PlayClipAtPoint(clip, position, volume);

// Play on existing source
source.clip = clip;
source.Play();
source.PlayOneShot(clip, volume);

// 3D audio settings
source.spatialBlend = 1.0f; // 0=2D, 1=3D
source.minDistance = 1f;
source.maxDistance = 50f;

Audio Mixer

Route audio through mixer groups for volume control, effects (reverb, EQ), and snapshot transitions. Useful for volume settings, pause menus, and dynamic mixing.

Input System

Unity has two input systems. Both are currently supported.

Old Input Manager (Input class)

// In Update()
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
bool jump = Input.GetButtonDown("Jump");
bool fire = Input.GetButton("Fire1");
Vector3 mousePos = Input.mousePosition;
  • Configured in Edit → Project Settings → Input Manager
  • Simple, quick to use
  • No rebinding support, not device-agnostic

New Input System (Input System Package)

using UnityEngine.InputSystem;

// Action-based (C# events)
PlayerInput playerInput;
void Awake() {
    playerInput = GetComponent<PlayerInput>();
    playerInput.actions["Jump"].performed += OnJump;
}

// Direct read (polling)
Gamepad gamepad = Gamepad.current;
if (gamepad != null) {
    Vector2 move = gamepad.leftStick.ReadValue();
}

// Keyboard + Mouse
Keyboard keyboard = Keyboard.current;
Mouse mouse = Mouse.current;
if (keyboard.spaceKey.wasPressedThisFrame) { /* jump */ }

Version Notes

  • 2022.3: Both systems available; New Input System is the recommended default
  • Unity 6: New Input System is the default for new projects

Prefabs

Prefabs are reusable GameObject templates stored as assets. Changes to the prefab asset propagate to all instances.

Key Operations

  • Create: Drag GameObject from Hierarchy to Project window
  • Open: Double-click prefab to edit in isolation (Prefab Mode)
  • Override: Instance changes can be applied back to the prefab or reverted
  • Variants: Create derived prefabs that inherit from a base prefab
  • Unpack: Break prefab connection for full independence
// Instantiate from prefab reference
[SerializeField] GameObject bulletPrefab;
void Fire() { Instantiate(bulletPrefab, muzzle.position, muzzle.rotation); }

ScriptableObject

Data container assets that live as project files, not scene objects. Ideal for configuration, item databases, skill definitions.

[CreateAssetMenu(fileName = "NewItem", menuName = "Game/Item")]
public class ItemData : ScriptableObject {
    public string itemName;
    public Sprite icon;
    public int maxStack;
    public GameObject prefab;
}

// Usage: reference in any MonoBehaviour
[SerializeField] ItemData healingPotion;

Use ScriptableObject for data that multiple objects share — avoid duplicating data across prefabs.

ScriptableObject as Event Channel

A powerful decoupling pattern: use ScriptableObject as an intermediary that components listen to without knowing each other:

// IntEventSO.cs — a ScriptableObject that holds an integer + an event
[CreateAssetMenu(menuName = "Events/Int Event")]
public class IntEventSO : ScriptableObject
{
    public int value;
    public UnityAction<int> OnValueChanged;

    public void SetValue(int newValue)
    {
        value = newValue;
        OnValueChanged?.Invoke(value);
    }
}

// PlayerHealth.cs — broadcasts health change
public class PlayerHealth : MonoBehaviour
{
    public IntEventSO healthEvent; // assign in Inspector
    public void TakeDamage(int dmg) { healthEvent.SetValue(currentHealth); }
}

// HealthBarUI.cs — listens for health change (no reference to PlayerHealth needed!)
public class HealthBarUI : MonoBehaviour
{
    public IntEventSO healthEvent;
    void OnEnable() { healthEvent.OnValueChanged += UpdateBar; }
    void OnDisable() { healthEvent.OnValueChanged -= UpdateBar; }
    void UpdateBar(int hp) { slider.value = hp; }
}

This pattern eliminates GetComponent chains and spaghetti references.

Singleton

A global manager that persists across scenes:

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject); // destroy duplicate
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public void RestartLevel() { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); }
}

// Usage from anywhere:
GameManager.Instance.RestartLevel();

When to Singleton: GameManager, AudioManager, SaveManager, InputManager — exactly ONE per project. Not for enemies, UI, or gameplay objects.

When NOT to Singleton: Don't use Singleton as a get-out-of-jail-free card instead of proper references. If only 2-3 scripts need the reference, use a serialized field. Reserve Singletons for truly global systems.

Object Pooling

Reuse GameObjects instead of Instantiating/Destroying repeatedly — essential for bullets, enemies, VFX:

// Basic pool pattern
public class ObjectPool : MonoBehaviour
{
    public GameObject prefab;
    public int poolSize = 20;
    private Queue<GameObject> pool = new Queue<GameObject>();

    void Start()
    {
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            pool.Enqueue(obj);
        }
    }

    public GameObject Get()
    {
        if (pool.Count == 0)
        {
            // Expand pool: instantiate + enqueue before dequeueing
            GameObject newObj = Instantiate(prefab);
            newObj.SetActive(false);
            pool.Enqueue(newObj);
        }
        GameObject obj = pool.Dequeue();
        obj.SetActive(true);
        return obj;
    }

    public void Return(GameObject obj)
    {
        obj.SetActive(false);
        pool.Enqueue(obj);
    }
}

Unity's built-in: UnityEngine.Pool.ObjectPool<T> and UnityEngine.Pool.GenericPool<T> (Unity 2021+) provide allocation-free pools with less boilerplate.

When to Pool

PoolDon't Pool
Bullets, projectilesOne-off level geometry
Enemy spawns (hordes)Cutscene-specific objects
VFX (explosions, impacts)Player character
UI elements (scrolling list)Objects with complex Awake setup

Addressables

Modern asset management system (replaces the old Resources folder). Assets are loaded asynchronously by address, not by file path.

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

// Load by address (set in asset's Addressable settings)
Addressables.LoadAssetAsync<GameObject>("Enemy_Goblin").Completed += handle =>
{
    if (handle.Status == AsyncOperationStatus.Succeeded)
        Instantiate(handle.Result);
};

// Load by asset reference (type-safe Inspector field)
[SerializeField] private AssetReferenceGameObject enemyPrefab;
// Load: enemyPrefab.InstantiateAsync().Completed += ...

// Load a scene
Addressables.LoadSceneAsync("Level2");

Benefits over Resources:

  • No monolithic Resources folder — any asset can be addressable
  • Remote asset bundles for live content updates
  • Memory management: Addressables.Release() to unload
  • Dependency tracking: shared assets loaded once

Build & Publish

Build Settings

File → Build Settings or Ctrl+Shift+B:

  1. Add scenes to Scenes in Build (drag from Project window)
  2. Select target Platform (PC/Mac/Linux, Android, iOS, WebGL)
  3. Click Switch Platform (first time takes a while — assets reimport)
  4. Click Build or Build and Run

Player Settings

Configure app name, icon, resolution, splash screen, and platform-specific options:

SettingWhere
Company/Product NamePlayer Settings → Player
Default iconPlayer Settings → Icon
Resolution / FullscreenPlayer Settings → Resolution and Presentation
Quality levelsProject Settings → Quality
Android package namePlayer Settings → Other Settings → Identification
iOS bundle identifierPlayer Settings → Other Settings → Identification

Development Build

Tick Development Build in Build Settings for debug logs, profiling, and console access in standalone builds.

Build Folder Structure

After building, the output folder contains:

  • ProjectName.exe (Windows) or .app (Mac)
  • ProjectName_Data/ (all game assets, Mono runtime, managed assemblies)
  • MonoBleedingEdge/ (.NET runtime)

Distribute the entire folder — they all depend on each other.

Custom Inspector (Editor Scripting)

Create custom Inspector UI for your scripts:

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Player))]
public class PlayerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        Player player = (Player)target;

        // Draw default inspector fields first
        DrawDefaultInspector();

        // Add custom buttons
        if (GUILayout.Button("Reset Player"))
            player.ResetToSpawn();

        // Add help box
        EditorGUILayout.HelpBox("Player settings affect all instances.", MessageType.Info);
    }
}

Place Editor scripts in an Editor/ folder (anywhere under Assets). They won't be included in builds.

Simpler alternative — [ContextMenu]: For simple right-click context actions on a component without creating an Editor script:

[ContextMenu("Reset Player")]
void ResetToSpawn() { transform.position = Vector3.zero; }

Right-click the component in Inspector → Reset Player. Zero Editor scripting needed.


Coroutines

void Start() { StartCoroutine(FadeOut()); }

IEnumerator FadeOut() {
    Renderer renderer = GetComponent<Renderer>();
    float t = 0;
    while (t < 1f) {
        t += Time.deltaTime / duration;
        renderer.material.color = Color.Lerp(Color.white, Color.clear, t);
        yield return null; // wait one frame
    }
}

// Wait for seconds (cache to avoid GC allocation)
WaitForSeconds wait = new WaitForSeconds(2f);
yield return wait;

// Wait until condition
yield return new WaitUntil(() => health <= 0);

// Chain coroutines
yield return StartCoroutine(AnotherRoutine());

Coroutine Pitfalls

  • Coroutines stop when the GameObject is disabled/destroyed — handle cleanup
  • WaitForSeconds creates garbage; cache the instance: WaitForSeconds wait = new(1f);
  • Don't mix async/await with coroutines — choose one pattern per system

Coroutines vs Update vs Invoke

UpdateCoroutineInvokeRepeating
RunsEvery frameOver time, frame by frameFixed interval
Stops whenObject destroyed / disabledObject destroyed / disabled + manual StopCoroutineObject destroyed / disabled
Delay supportManual timer variableyield return new WaitForSeconds(n)Built-in: Invoke("Method", delay)
GC allocationNone if coded cleanlyModerate (yield instructions)String-based (slow)
ReadabilityState machines get messyClean sequenced logicFragmented (scattered methods)
Use forContinuous input, animationSequenced events, fading, cutscenesSimple repeating tasks (avoid for new code)

Decision rule: If it's a sequence of events over time → Coroutine. If it's continuous per-frame logic → Update. Avoid Invoke — it's string-based and has no cancellation except CancelInvoke() which cancels all at once.

async/await

Modern C# async patterns. Use UniTask (recommended, zero-allocation) or native Task.

UniTask (Recommended)

Install UniTask via Package Manager (by URL: https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask).

using Cysharp.Threading.Tasks;

// Note: async void Start is required by Unity's MonoBehaviour lifecycle.
// Prefer async UniTaskVoid for non-lifecycle methods for proper exception handling.
async void Start()
{
    // Wait 2 seconds
    await UniTask.Delay(2000);

    // Wait until condition
    await UniTask.WaitUntil(() => health <= 0);

    // Wait for next frame
    await UniTask.Yield();

    // Load scene async
    await SceneManager.LoadSceneAsync("Level2");

    // Run on main thread (from background task)
    await UniTask.SwitchToMainThread();
}

Native Task (Unity 2022.2+ / Unity 6)

using System.Threading.Tasks;

async void Start()
{
    await Task.Delay(2000);
    // Caution: Task.Delay uses thread pool, returns on different thread
    // Most Unity API calls require main thread
}

Coroutine vs async/await

Coroutineasync/await
Return value❌ No✅ Yes (async Task<int>)
Exception handling❌ Try/catch doesn't work across yield✅ Full try/catch support
GC allocationModerateMinimal (UniTask)
Main thread guarantee✅ Always⚠️ Must manually switch back
Error on destroy❌ Silent stop⚠️ Silent stop (fix with CancellationToken, see below)
Use forSimple sequencesComplex async logic, value returns

Cancellation

When an object is destroyed, async operations should clean up:

CancellationTokenSource cts = new CancellationTokenSource();

async UniTaskVoid DoWork(CancellationToken token)
{
    await UniTask.Delay(5000, cancellationToken: token);
    // Won't execute if cancelled
}

void OnDestroy()
{
    cts.Cancel();
    cts.Dispose();
}

Performance Best Practices

Memory / GC

  • Avoid GetComponent<T>() in Update() — cache in Awake()
  • Avoid GameObject.Find() / FindObjectOfType() — use serialized references or dependency injection
  • Avoid new in Update() — allocate upfront and reuse
  • Use StringBuilder for frequent string concatenation
  • Use structs carefully — boxing allocates

Rendering Performance

  • Use GPU Instancing or SRP Batcher for repeated meshes
  • Limit real-time lights, use baked lighting when possible
  • Use LOD groups for distant objects
  • Occlusion culling for complex indoor scenes
  • Profile with Profiler window, not guesswork

Physics Performance

  • Use primitive colliders over MeshCollider
  • Set Rigidbody to kinematic when physics response is not needed
  • Keep Fixed Timestep at 0.02s (50 Hz) for most projects. Only lower for high-precision physics simulations. Do NOT tie to display framerate.
  • Disable collision between layers that don't interact

Additional References

  • references/urp-2022.md — URP specifics for 2022.3 LTS
  • references/urp-unity6.md — URP specifics for Unity 6

Common Anti-Patterns

Physics & Movement

// ❌ Direct transform.position on Rigidbody — bypasses physics engine
transform.position += Vector3.forward * speed * Time.deltaTime;

// ❌ rb.position for continuous movement — teleports, no velocity transfer
rb.position = newPosition;

// ✅ Use velocity (FixedUpdate)
rb.velocity = new Vector3(input.x * speed, rb.velocity.y, input.y * speed);

// ✅ Kinematic: MovePosition in FixedUpdate
rb.MovePosition(rb.position + moveDir * speed * Time.fixedDeltaTime);

Unity API Misuse

// ❌ FindObjectOfType / GameObject.Find in Update — O(n) every frame
// ❌ GetComponent<T>() in Update — cache in Awake
// ❌ CompareTag(string) in Update — use CompareTag() (no GC) or cached bool
// ❌ string comparison for state — use enum or hash code
// ❌ LINQ in Update — allocates GC, use foreach or for loop

// ✅ Cache references
Rigidbody rb;
void Awake() { rb = GetComponent<Rigidbody>(); }

// ✅ Pre-cached tag comparison
bool IsPlayer(Collider other) => other.CompareTag("Player");

// LINQ only in editor scripts or one-time init (prefer Addressables for runtime asset loading)
void SetupWeapons() { weapons = Resources.LoadAll<WeaponData>("Weapons").ToList(); }

Event Leaks

// ❌ Subscribe without unsubscribe — memory leak, null ref after destroy
void OnEnable() { otherHealth.OnDeath += HandleDeath; }
// Missing: void OnDisable() { otherHealth.OnDeath -= HandleDeath; }

// ✅ Always pair subscribe + unsubscribe
void OnEnable()  { button.onClick.AddListener(HandleClick); }
void OnDisable() { button.onClick.RemoveListener(HandleClick); }

GC Allocation Hotspots

PatternGC AllocFix
new WaitForSeconds(1f) every frameHighCache: WaitForSeconds wait = new(1f);
string + string in loopHighUse StringBuilder
foreach over non-genericMedPrefer for or List<T>.GetEnumerator
Boxing value typesHighAvoid casting structs to object/interface
Anonymous methodsMedCache delegate if reused

Game Architecture Patterns

Finite State Machine

Use enum-based state machines for character/entity behaviour:

public enum PlayerState { Idle, Moving, Jumping, Attacking, Dead }

PlayerState currentState;

void Update()
{
    switch (currentState)
    {
        case PlayerState.Idle:     IdleUpdate(); break;
        case PlayerState.Moving:   MoveUpdate(); break;
        case PlayerState.Jumping:  JumpUpdate(); break;
        case PlayerState.Attacking: AttackUpdate(); break;
        case PlayerState.Dead:     return; // no updates when dead
    }
}

void ChangeState(PlayerState newState)
{
    ExitState(currentState);
    currentState = newState;
    EnterState(newState);
}

void ExitState(PlayerState state)
{
    switch (state)
    {
        case PlayerState.Attacking:
            weaponCollider.enabled = false;
            break;
    }
}

For complex FSMs, use Animator as a state machine backbone (parameter-driven transitions).

Strategy Pattern (via Interface)

public interface IMovementStrategy
{
    void Move(Rigidbody rb, Vector2 input);
}

public class WalkMovement : IMovementStrategy
{
    public void Move(Rigidbody rb, Vector2 input)
    {
        rb.velocity = new Vector3(input.x * 5f, rb.velocity.y, input.y * 5f);
    }
}

public class FlyMovement : IMovementStrategy
{
    public void Move(Rigidbody rb, Vector2 input)
    {
        rb.velocity = input * 8f; // full 3D movement
    }
}

// Usage: Swap movement mode at runtime
IMovementStrategy movement = new WalkMovement();
movement = new FlyMovement(); // power-up changes behaviour

Observer via ScriptableObject Event Channel

See §ScriptableObject → ScriptableObject as Event Channel for the full pattern with IntEventSO, PlayerHealth, and HealthBarUI examples. The same decoupling pattern applies to any event-driven architecture.

Input System Advanced

Action Map Switching

using UnityEngine.InputSystem;

PlayerInput playerInput;

void Start()
{
    playerInput = GetComponent<PlayerInput>();
    playerInput.SwitchCurrentActionMap("UI"); // switch to UI controls
    playerInput.SwitchCurrentActionMap("Gameplay"); // switch back
}

// Multiple control schemes per action map:
// Edit InputAction asset → Control Schemes → add Keyboard&Mouse, Gamepad, Touch
// PlayerInput auto-switches scheme based on active device

Runtime Rebinding

using UnityEngine.InputSystem;

InputAction moveAction;

void StartRebind()
{
    moveAction.PerformInteractiveRebinding()
        .WithControlsExcluding("Mouse") // don't rebind mouse
        .OnMatchWaitForAnother(0.1f)    // wait for combo inputs
        .OnComplete(op => {
            Debug.Log($"Rebound to: {op.selectedControl}");
            SaveBindingOverride(moveAction);
            op.Dispose();
        })
        .Start();
}

void SaveBindingOverride(InputAction action)
{
    var rebinds = action.actionMap.SaveBindingOverridesAsJson();
    PlayerPrefs.SetString("rebinds", rebinds);
}

void LoadBindingOverrides()
{
    var rebinds = PlayerPrefs.GetString("rebinds");
    moveAction.actionMap.LoadBindingOverridesFromJson(rebinds);
}

Touch Input

// Direct device access
Touchscreen touch = Touchscreen.current;
if (touch != null && touch.primaryTouch.press.isPressed)
{
    Vector2 touchPos = touch.primaryTouch.position.ReadValue();
    Ray ray = Camera.main.ScreenPointToRay(touchPos);
    if (Physics.Raycast(ray, out RaycastHit hit))
        HandleTouch(hit.point);
}

// Enhanced Input touch bindings:
// Bind Touchscreen's Primary Touch/Position to a Vector2 action
// Tap/gesture detection via Input Actions' Interactions (Tap, Hold, MultiTap)

Shader Graph Basics

Creating a Shader

  1. Create: Assets → Create → Shader Graph → URP → Lit Shader Graph (or Unlit, Sprite Lit, Decal)
  2. Open: Double-click → Shader Graph editor opens
  3. Core nodes: Add node (Spacebar) → type name
  4. Save: Shader auto-updates in scene

Common Node Reference

NodePurpose
Sample Texture 2DSample a texture at UV coordinates
ColorConstant RGBA color output (HDR-compatible in Unity 6)
Add / Multiply / LerpMath on color/float/vector values
Fresnel EffectEdge glow, rim lighting
TimeAnimated shader values (scrolling, pulsing)
Normal From HeightBump from heightmap
Scene ColorSample what's behind this object (transparency, distortion)
Vertex ColorMesh vertex color input
Position (Object/World)Object world/relative position

Key Material Properties

// Expose parameters to Inspector — drag to Blackboard
// Float, Vector2/3/4, Color, Texture2D, Cubemap

// URP include for light access in custom nodes:
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
Light mainLight = GetMainLight();
half3 lightDir = mainLight.direction;
half3 lightColor = mainLight.color;

Common Shader Graph Effects

EffectNodes
DissolveSimple Noise → Step → Alpha Clip Threshold
Outline (inverted hull)Vertex Position offset along normal, separate pass
Ripple (UV distortion)Time + Sine → Add to UV → Sample Texture 2D
Fresnel glowFresnel Effect → Multiply with Color → Emission
Toon/Cel shadingStep on Dot(Normal, LightDir) for hard light falloff

Mobile Optimization

Unity 2022.3 / Unity 6 Mobile Checklist

SettingRecommendationWhere
Graphics APIOpenGL ES 3.2 (broad compat) or Vulkan (newer devices)Player Settings → Other Settings
Color SpaceLinear (HDR rendering)Player Settings → Other Settings
Texture compressionASTC (Android), PVRTC (iOS)Per-texture override
Shadow qualityLow to MediumQuality Settings → Shadows
Anti-aliasingFXAA (cheap) or offURP Asset → Anti-aliasing
Maximum LOD levelSet per-platform LOD biasQuality Settings → LOD Bias
VSyncOn for mobile (battery), off for benchmarksQuality Settings
Target framerate30fps default, 60fps optionalApplication.targetFrameRate = 60;
Strip engine codeEnablePlayer Settings → Strip Engine Code
// Runtime platform check
#if UNITY_ANDROID
    QualitySettings.vSyncCount = 1;
    Application.targetFrameRate = 30;
#elif UNITY_IOS
    QualitySettings.vSyncCount = 0;
    Application.targetFrameRate = 60;
#endif

// Detect device capability
if (SystemInfo.supportsComputeShaders)
    EnableAdvancedEffects();

// Memory warning callback
void Start() {
    Application.lowMemory += OnLowMemory;
}
void OnLowMemory() {
    Resources.UnloadUnusedAssets();
    textureQuality = 0; // reduce texture quality
}

Mobile-Specific Pitfalls

  • Overdraw: Transparent UI/particles kill mobile GPU. Use Frame Debugger → check overdraw.
  • Memory: 1-2GB budget. Profile with Memory Profiler package.
  • Battery: VSync on limits unnecessary frames. Avoid while(true) or unbounded Update.
  • Touch vs mouse: Use Input.touchSupported to switch between touch/mouse paths.
  • Notch/Cutout: Use Screen.safeArea to constrain UI to non-obscured screen regions.


Profiling & Debugging

Profiler Window

Window → Analysis → Profiler or Ctrl+7. Records CPU/GPU/memory/audio usage per frame:

ModuleShows
CPU UsageScripts, physics, rendering — sorted by time cost
GPU UsageDraw calls, shader passes, screen effects
MemoryTextures, meshes, audio, GC allocations
RenderingBatches, set passes, VBO uploads
AudioAudio clip playback, mixers, voices

Workflow:

  1. Click Record (red circle) → run the scene → stop recording
  2. Click on CPU spikes to inspect per-frame script calls
  3. Deep Profile: enable Deep Profile in Build Settings to see per-method timing (slower, development only)

Frame Debugger

Window → Analysis → Frame Debugger. Captures a single frame's draw call sequence:

Draw Call #1: Skybox
Draw Call #2: Background meshes (StaticBatching)
Draw Call #3: Character_SK (SkinnedMeshRenderer)
Draw Call #4: UIPanel (Canvas)
...

Use for:

  • Finding unnecessary draw calls (overdraw)
  • Checking batching (static/dynamic/GUI)
  • Verifying shader variants per object
  • Spotting hidden objects still rendering (Check isVisible)

Common Commands

Stats window → toggle real-time performance overlay
Profiler → CPU Usage → search by MonoBehaviour name
Frame Debugger → click draw call → highlights object in Scene View

Maintaining This Skill

Update this skill when:

  • User reports a recurring bug pattern → add to relevant section
  • User discovers a non-obvious Unity behaviour → document
  • User switches Unity versions with breaking changes → update version notes
  • New Unity feature becomes part of user's workflow → add section

What ships with it: 6 files

42.3 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.