unity-csharp

SKILL.md

Unity C# Skill

Version: 2.0 Stack: Unity, C#

Patterns for writing clean, performant Unity C# code. Includes VR/mobile optimization.


Scope and Boundaries

This skill covers:

  • MonoBehaviour lifecycle and component architecture
  • Unity-specific C# patterns (caching, events, coroutines, null safety)
  • Performance optimization (draw calls, batching, LODs, pooling)
  • VR/mobile performance targets and profiling
  • ScriptableObject usage

Defers to other skills:

  • vrc-udon: VRChat-specific scripting (UdonSharp)
  • vrc-worlds: VRChat world setup and limits
  • vrc-avatars: VRChat avatar setup and limits

Use this skill when: Writing C# scripts for Unity, or optimizing Unity performance for VR/mobile.


Core Principles

  1. Composition Over Inheritance — Small, focused components.
  2. Avoid Update() When Possible — Event-driven or coroutines instead.
  3. Cache References — GetComponent is expensive; cache in Awake.
  4. ScriptableObjects for Data — Decouple data from behavior.
  5. Null-Safe Access — Unity objects can be destroyed at any time.
  6. Measure First — Profile before optimizing; gut feelings lie.
  7. Batch Aggressively — Same material = potential batch. Draw calls matter most in VR.

Patterns

Reference Caching

public class PlayerController : MonoBehaviour
{
    private Rigidbody _rb;
    private Animator _animator;

    [SerializeField] private Transform _cameraTarget;

    private void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        _animator = GetComponent<Animator>();
    }

    private void FixedUpdate()
    {
        _rb.AddForce(Vector3.up);
    }
}

Event System (ScriptableObject)

[CreateAssetMenu(menuName = "Events/Game Event")]
public class GameEvent : ScriptableObject
{
    private readonly List<GameEventListener> _listeners = new();

    public void Raise()
    {
        for (int i = _listeners.Count - 1; i >= 0; i--)
            _listeners[i].OnEventRaised();
    }

    public void RegisterListener(GameEventListener listener) =>
        _listeners.Add(listener);

    public void UnregisterListener(GameEventListener listener) =>
        _listeners.Remove(listener);
}

public class GameEventListener : MonoBehaviour
{
    [SerializeField] private GameEvent _event;
    [SerializeField] private UnityEvent _response;

    private void OnEnable() => _event.RegisterListener(this);
    private void OnDisable() => _event.UnregisterListener(this);
    public void OnEventRaised() => _response.Invoke();
}

Null-Safe Pattern

// Unity overloads == for destroyed objects
if (_target != null)
{
    _target.DoSomething();
}

// Best: explicit destroyed check
if (_target != null && !_target.Equals(null))
{
    _target.DoSomething();
}

Coroutine Pattern

private IEnumerator FadeOut(float duration)
{
    float elapsed = 0f;
    Color startColor = _renderer.material.color;

    while (elapsed < duration)
    {
        elapsed += Time.deltaTime;
        float t = elapsed / duration;
        _renderer.material.color = Color.Lerp(startColor, Color.clear, t);
        yield return null;
    }

    gameObject.SetActive(false);
}

Object Pooling

public class ObjectPool : MonoBehaviour
{
    [SerializeField] private GameObject _prefab;
    [SerializeField] private int _initialSize = 10;

    private Queue<GameObject> _pool = new();

    private void Awake()
    {
        for (int i = 0; i < _initialSize; i++)
        {
            var obj = Instantiate(_prefab);
            obj.SetActive(false);
            _pool.Enqueue(obj);
        }
    }

    public GameObject Get()
    {
        if (_pool.Count == 0)
            return Instantiate(_prefab);

        var pooled = _pool.Dequeue();
        pooled.SetActive(true);
        return pooled;
    }

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

Static Batching

gameObject.isStatic = true;

// Or specific flags
GameObjectUtility.SetStaticEditorFlags(gameObject,
    StaticEditorFlags.BatchingStatic |
    StaticEditorFlags.OcclusionStatic);

VR Performance Targets

Metric Quest 2 Quest 3 PC VR
Draw Calls <100 <150 <200
Triangles <100K <150K <1M
Frame Time <14ms (72fps) <11ms (90fps) <11ms (90fps)
Texture Memory <200MB <500MB <1GB

LOD Configuration

LOD 0: 100% triangles (0-10m)
LOD 1: 50% triangles (10-25m)
LOD 2: 25% triangles (25-50m)
Culled: 0 triangles (50m+)

Material Atlasing

Before: 20 objects x 20 materials = 20 draw calls (no batching)
After:  20 objects x 1 atlas material = 1 draw call (batched)

Anti-Patterns

Anti-Pattern Problem Fix
GetComponent in Update Expensive every frame Cache in Awake
Find or FindObjectOfType Slow, fragile Inject references or use events
Heavy Update loops Performance drain Use events, coroutines, or FixedUpdate
String comparisons for tags Typo-prone, slow Use CompareTag or constants
Public fields for everything No encapsulation Use [SerializeField] private
Unique material per object No batching possible Share materials, use atlases
No LODs Full detail at any distance Add LOD groups
Instantiate/Destroy in gameplay GC spikes, stutters Object pooling
Realtime lights everywhere Expensive shadows Bake lighting, limit realtime
No occlusion culling Render hidden objects Bake occlusion data

Checklist

Code Quality

  • References cached in Awake
  • No GetComponent in Update/FixedUpdate
  • No Find methods in runtime code
  • ScriptableObjects for shared data
  • Events for decoupled communication
  • Null checks for destroyable objects

Performance

  • Static objects marked static
  • Materials shared where possible
  • Texture atlases for small props
  • LOD groups on significant meshes
  • Occlusion culling baked
  • Object pooling for spawned objects
  • Lighting baked (not all realtime)

Profiling

  • Frame Debugger checked for draw calls
  • Profiler run for CPU spikes
  • Memory Profiler checked for leaks
  • Tested on target device (not just editor)

References

  • references/lifecycle.md — MonoBehaviour lifecycle and execution order
  • references/profiling.md — Unity Profiler usage and interpretation

Assets

  • assets/component-checklist.md — Unity component design checklist
  • assets/vr-performance-limits.md — VR platform performance limits and targets
Weekly Installs
12
GitHub Stars
1
First Seen
Feb 17, 2026
Installed on
opencode12
gemini-cli12
github-copilot12
codex12
amp11
cline11