Highlights
- The FSM shows up in almost every shipped game, from player movement to enemy AI to game loop management.
- More than two or three Singletons in a codebase is where hidden dependencies start and testability ends.
- Without an FSM, every new state becomes another nested conditional inside a single update method.
Every game developer eventually hits the same wall. The project starts clean, then grows. A few hundred lines become thousands. Systems that once made sense begin to contradict each other. Bugs appear in code nobody touched.
This is not bad luck. It is the absence of game design patterns, and it happens to beginners and veterans alike.
Game programming patterns are documented solutions to problems that appear, in some form, in almost every game ever built. Learning them does not make code more complicated. It makes code readable six months later, by someone who was not there when it was written.
These six game design patterns are where the fix begins.
Why Software Patterns in Gaming are No Longer Optional
The modern game industry runs on two engines. Unity powers the majority of mobile and indie releases. Unreal Engine 5 (UE5) dominates AAA and cinematic production. Both are built on object-oriented principles, and both reward developers who understand object-oriented programming (OOP) game architecture.
A developer who can name a pattern and explain when not to use it is more valuable than one who can only implement it. This guide covers six patterns that appear constantly across shipped games, each with Unity C# and Unreal C++ examples.
1. The Singleton: Convenient Until it is Not
The Singleton ensures one instance of a class exists and is globally accessible. Developers reach for it when building audio managers, save systems, or input handlers.
Unity C#:
public class AudioManager : MonoBehaviour {
public static AudioManager Instance { get; private set; }
void Awake() {
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
Unreal C++: UGameInstance serves the same purpose natively. Register it in Project Settings and access it anywhere via ‘GetGameInstance().’
When it helps: One global audio or analytics manager who must survive scene loads.
When it hurts: Applying Singleton to every manager creates hidden dependencies. Systems become impossible to test in isolation. More than two or three singletons in a project is a signal to reconsider the architecture.
2. Observer and Event Bus: Let Systems Stop Talking Directly
The Observer pattern solves the tightest coupling problem in OOP games. When one system changes state, instead of calling others directly, it broadcasts an event. Any number of listeners subscribe and react independently.
Unity C#:
public static class EventBus {
public static event Action<int> OnPlayerDamaged;
public static void PlayerDamaged(int amount) =>
OnPlayerDamaged?.Invoke(amount);
}
// Subscribe: EventBus.OnPlayerDamaged += RefreshHealthBar;
// Always unsubscribe in OnDisable to prevent memory leaks.
Unreal C++: ‘DECLARE_DYNAMIC_MULTICAST_DELEGATE’ handles the same flow with Blueprint compatibility built in.
When it helps: User interface (UI) updates, artificial intelligence (AI) reactions, achievement triggers, sound responses, and any scenario where multiple systems react to a single game event.
When it hurts: A global event bus with no naming convention becomes harder to debug than the direct calls it replaced. Name events clearly and document what each one carries.
3. State Machine: The Pattern Every Character Controller Needs
The finite state machine (FSM) is the most consistently useful of all game programming patterns. Characters, menus, enemies, and game loops all move through discrete, exclusive states. Without an FSM, those transitions live inside a single update method as nested conditionals that grow without bound.
Unity C#:
public interface IState { void Enter(); void Tick(); void Exit(); }
public class StateMachine {
private IState _current;
public void Init(IState s) { _current = s; s.Enter(); }
public void TransitionTo(IState next) {
_current.Exit(); _current = next; _current.Enter();
}
public void Tick() => _current.Tick();
}
Unreal C++: An enum with a switch statement covers most cases. For complex AI, Unreal's Behavior Tree is a hierarchical state machine with a visual editor included.
When it helps: Player locomotion, enemy AI, UI screen navigation, and game loop phases such as menu, playing, paused, and game over.
When it hurts: Objects with two or three fixed states rarely need the full FSM structure. Add it when the number of states and transitions is growing, not when the project begins.
4. Object Pool: The Fix for Frame Rate Drops
Every call to Instantiate in Unity or SpawnActor in Unreal allocates heap memory. When a game spawns projectiles, particles, or enemies at high frequency, the garbage collector runs to clean up destroyed objects, and frame rate drops follow. The Object Pool pattern removes that cost entirely.
Unity C#:
private ObjectPool<GameObject> _pool;
void Awake() {
_pool = new ObjectPool<GameObject>(
() => Instantiate(bulletPrefab),
b => b.SetActive(true),
b => b.SetActive(false),
defaultCapacity: 20
);
}
Unreal C++: Reuse actors by toggling ‘SetActorHiddenInGame,’ ‘SetActorEnableCollision,’ and ‘SetActorTickEnabled’ rather than destroying and respawning them.
When it helps: Bullets, enemy waves, explosions, UI notifications, and anything spawned and destroyed repeatedly during runtime.
When it hurts: Pooling objects that spawn rarely adds infrastructure with no measurable performance benefit.
5. Component Pattern: Composition Over Inheritance
The component pattern is not something developers choose to apply in Unity or Unreal. It is the pattern those engines are built on. Every GameObject and every Actor is a container. Behavior comes from attached components, not from deep inheritance chains.
Unity C#:
public class HealthComponent : MonoBehaviour {
[SerializeField] private int maxHealth = 100;
private int _current;
public void Init() => _current = maxHealth;
public void TakeDamage(int dmg) {
_current -= dmg;
if (_current <= 0) GetComponent<DeathHandler>().Die();
}
}
Unreal C++: ‘UActorComponent’ with ‘GENERATED_BODY()’ achieves the same. One HealthComponent can attach to players, enemies, vehicles, and destructible props without any of those classes inheriting from each other.
When it helps: Any time two different actor types share the same behavior. Health, inventory, pathfinding, dialogue, and status effects all belong in components.
When it hurts: One-off behaviors on a single object type rarely justify a separate component. A door that opens once is fine as a single script.
6. Command Pattern: Input That Can Be Undone
The Command pattern wraps an action inside an object. Each command has an Execute method and, when needed, an Undo method. The system that triggers the command does not know what it does. This separation enables input remapping, undo systems, replay recording, and turn-based action queues.
Unity C#:
public interface ICommand { void Execute(); void Undo(); }
public class JumpCommand : ICommand {
private readonly PlayerController _p;
public JumpCommand(PlayerController p) => _p = p;
public void Execute() => _p.Jump();
public void Undo() => _p.CancelJump();
}
Unreal C++: ‘TArray<TFunction<void()>>’ stores a queue of commands that execute in sequence each frame, then empty.
When it helps: Input remapping, undo and redo in level editors, replay systems, and ability queues in turn-based games.
When it hurts: Three fixed, non-remappable inputs do not need a command object each. The pattern pays off when the number of actions or the need for reversibility grows.
How to Refactor Spaghetti Code Into a Clean State Machine
The refactoring exercise below converts a bloated update method into a clean, testable FSM in seven steps:
- Step 1: Open the problem script. Count every boolean flag used to track behavior. More than three is a reliable sign that a state machine belongs here.
- Step 2: Name the states. For a player controller, these are typically idle, walk, jump, and fall. Each must be mutually exclusive.
- Step 3: Create an IState interface with enter, tick, and exit methods.
- Step 4: Create a StateMachine class that holds the current state, calls "enter" on transition, and routes ticks each frame.
- Step 5: Move the logic from each conditional block into its own state class. Each class owns only what it needs.
- Step 6: Replace the original update method with a single line: ‘_stateMachine.Tick(Time.deltaTime).’
- Step 7: Test every transition manually. Idle to walk on input. Walk to Jump on Space. Jump to fall at the apex. Fall to idle on landing. Adding a new state now requires one new class and zero changes to existing states.
The Skill is Knowing When Not to Use Them
Game design patterns work because they match the shape of real structural problems:
- Singleton for one true global manager.
- Observer for decoupled reactions across systems.
- State machine for exclusive, growing behavior modes.
- Object pool for high-frequency runtime allocation.
- Component for shared behavior across actor types.
- Command for reversible, remappable actions.
The failure mode in software pattern gaming is applying structure for its own sake. A state machine added before complexity exists creates boilerplate. A Singleton applied to every system creates a dependency web. Each pattern in this guide earns its place when the specific problem it solves is already present, not in anticipation of a problem that may never arrive.
Write the simple version first. Reach for the pattern when the simple version starts to break.

