Loading...
Loading...
C# patterns for Unity - MonoBehaviour, async, architecture, and VR/mobile performance optimization
npx skill4agent add alexanderstephenthompson/claude-hub unity-csharpPatterns for writing clean, performant Unity C# code. Includes VR/mobile optimization.
vrc-udonvrc-worldsvrc-avatarspublic 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);
}
}[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();
}// Unity overloads == for destroyed objects
if (_target != null)
{
_target.DoSomething();
}
// Best: explicit destroyed check
if (_target != null && !_target.Equals(null))
{
_target.DoSomething();
}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);
}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);
}
}gameObject.isStatic = true;
// Or specific flags
GameObjectUtility.SetStaticEditorFlags(gameObject,
StaticEditorFlags.BatchingStatic |
StaticEditorFlags.OcclusionStatic);| 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 0: 100% triangles (0-10m)
LOD 1: 50% triangles (10-25m)
LOD 2: 25% triangles (25-50m)
Culled: 0 triangles (50m+)Before: 20 objects x 20 materials = 20 draw calls (no batching)
After: 20 objects x 1 atlas material = 1 draw call (batched)| Anti-Pattern | Problem | Fix |
|---|---|---|
| Expensive every frame | Cache in Awake |
| 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 |
references/lifecycle.mdreferences/profiling.mdassets/component-checklist.mdassets/vr-performance-limits.md