Anti-Patterns
Things to never do. Each one has a "do this instead" alternative.
Polling for State Changes
// BAD
private void Update()
{
if (_player.IsDead) { ShowGameOver(); }
}
// GOOD — subscribe to event
_player.Died += ShowGameOver;
FindObjectOfType / FindObjectsByType
Expensive scene-wide search. O(n) every call.
// BAD
var manager = FindObjectOfType<GameManager>();
// GOOD — service locator or dependency injection
var manager = ServiceLocator.Get<GameManager>();
God Classes
A single class doing everything — input, movement, health, UI, audio.
// BAD — 1000-line MonoBehaviour
public class Player : MonoBehaviour
{
// handles input AND movement AND health AND UI AND audio ...
}
// GOOD — single responsibility, composed
public class PlayerHealth : MonoBehaviour { }
public class PlayerMovement : MonoBehaviour { }
public class PlayerInput : MonoBehaviour { }
Switch Statements for Type Dispatch
// BAD
switch (enemy.Type)
{
case EnemyType.Melee: HandleMelee(enemy); break;
case EnemyType.Ranged: HandleRanged(enemy); break;
}
// GOOD — polymorphism
enemy.Attack(); // each type implements its own behavior
Role Booleans
// BAD
public class Unit
{
public bool isHealer;
public bool isTank;
public bool isDps;
}
// GOOD — separate types or interface
public interface IRole { void PerformRole(); }
public class Healer : IRole { }
public class Tank : IRole { }
String Concatenation in Hot Paths
// BAD — allocates every frame
private void Update()
{
_label.text = "HP: " + _health + "/" + _maxHealth;
}
// GOOD — update only on change, use StringBuilder or cached string
private void OnHealthChanged(int current, int max)
{
_label.text = string.Format("HP: {0}/{1}", current, max);
}
new Allocations in Update
// BAD — GC pressure every frame
private void Update()
{
var results = new List<RaycastHit>();
}
// GOOD — pre-allocate, reuse
private readonly RaycastHit[] _hitBuffer = new RaycastHit[16];
Magic Numbers
// BAD
if (_health < 25) { Flee(); }
// GOOD
private const int FLEE_THRESHOLD = 25;
if (_health < FLEE_THRESHOLD) { Flee(); }
Empty Unity Callbacks
// BAD — Unity still invokes these, wasting cycles
private void Update() { }
private void FixedUpdate() { }
// GOOD — delete empty callbacks entirely
See Unity performance best practices.
SendMessage / BroadcastMessage
// BAD — string-based, no compile-time safety, uses reflection
gameObject.SendMessage("TakeDamage", 10);
// GOOD — direct call or event
_health.TakeDamage(10);
Non-Cached GetComponent in Loops
// BAD
private void Update()
{
var rb = GetComponent<Rigidbody>(); // called every frame
}
// GOOD — cache in Awake
private Rigidbody _rb;
private void Awake()
{
_rb = GetComponent<Rigidbody>();
}
Summary Table
| Anti-Pattern | Fix |
|---|---|
Polling in Update |
C# events / delegates |
FindObjectOfType |
Service locator / DI |
| God class | Single responsibility, composition |
| Switch on type | Polymorphism / interfaces |
| Role booleans | Separate types |
| String concat in hot path | Event-driven UI update |
new in Update |
Pre-allocate buffers |
| Magic numbers | Named constants |
| Empty callbacks | Delete them |
SendMessage |
Direct calls / events |
Uncached GetComponent |
Cache in Awake |