---
description: C# Architecture
alwaysApply: false
---

# C# Architecture

Guidelines for C# scripting patterns, MonoBehaviour lifecycle, and testable code in Unity.

## Core Principles

- Prefer composition over inheritance for game object behaviour
- Keep MonoBehaviours thin; delegate logic to plain C# classes
- Use ScriptableObjects for shared data and event channels
- Favour async/await (UniTask) over coroutines for complex flows
- Design for testability: inject dependencies, avoid static singletons

## Lifecycle Method Usage

| Method | Use For | Avoid |
|--------|---------|-------|
| Awake | Cache component refs, self-init | Accessing other objects |
| OnEnable | Subscribe to events | Heavy computation |
| Start | Cross-object init | Anything needed by another Awake |
| Update | Per-frame game logic | Physics, heavy allocations |
| FixedUpdate | Physics forces, raycasts | Camera movement, input |
| LateUpdate | Camera follow | Physics manipulation |
| OnDisable | Unsubscribe events | Accessing destroyed objects |

## ScriptableObject Patterns

| Pattern | Purpose |
|---------|---------|
| Data Container | Decouple config from behaviour (WeaponData, EnemyConfig) |
| Event Channel | Decouple publishers/subscribers (OnPlayerDied) |
| Runtime Set | Track active instances without FindObjectsOfType |
| Enum Replacement | Extensible type-safe categories (DamageType) |

## Async: Coroutines vs UniTask

| Feature | Coroutines | UniTask |
|---------|------------|---------|
| Cancellation | StopCoroutine (fragile) | CancellationToken (robust) |
| Return values | Not supported | Full Task<T> support |
| Error handling | Silently swallowed | try/catch |
| GC allocation | IEnumerator allocates | Zero-alloc |

## Testable Code

- **Thin MonoBehaviours**: Cache refs, forward input, apply results, manage lifecycle
- **Pure C# classes**: State machines, damage calc, inventory logic, pathfinding
- **Edit Mode tests**: NUnit + Unity Test Framework for pure C#, ScriptableObjects
- **Play Mode tests**: Unity Test Framework for component interactions, scenes

## Common Pitfalls

**Deep Inheritance**: `BossEnemy : RangedEnemy : BaseEnemy` with 5 levels of overrides. Compose small components instead: Health, Attack, Movement.

**Singletons Everywhere**: Hidden dependencies, untestable code. Use a service locator or DI; pass deps explicitly.

**Not Caching GetComponent**: Calling `GetComponent<Rigidbody>()` every frame. Cache in Awake.
