🎮 Game Development

Game Programming Patterns

Why Patterns Matter in Games

"Patterns are not invented; they are discovered. They are the vocabulary of experienced developers." — Robert Nystrom, Game Programming Patterns

Games are unique software systems: real-time, state-heavy, performance-critical, and content-driven. Patterns help manage this complexity.


1. Command Pattern

The Problem

# ❌ Naive input handling — tightly coupled, hard to extend
def handle_input(player):
    if key_pressed(Key.W):
        player.move_forward()
    if key_pressed(Key.S):
        player.move_backward()
    if key_pressed(Key.SPACE):
        player.jump()
    if key_pressed(Key.E):
        player.interact()
    # Adding: remapping, macros, replay, AI control → nightmare
// ❌ Naive input handling — tightly coupled, hard to extend
void handleInput(Player& player) {
    if (keyPressed(Key::W)) player.moveForward();
    if (keyPressed(Key::S)) player.moveBackward();
    if (keyPressed(Key::Space)) player.jump();
    if (keyPressed(Key::E)) player.interact();
    // Adding: remapping, macros, replay, AI control → nightmare
}
// ❌ Naive input handling — tightly coupled, hard to extend
void handleInput(Player player) {
    if (keyPressed(Key.W)) player.moveForward();
    if (keyPressed(Key.S)) player.moveBackward();
    if (keyPressed(Key.SPACE)) player.jump();
    if (keyPressed(Key.E)) player.interact();
    // Adding: remapping, macros, replay, AI control → nightmare
}
// ❌ Naive input handling — tightly coupled, hard to extend
void HandleInput(Player player)
{
    if (KeyPressed(Key.W)) player.MoveForward();
    if (KeyPressed(Key.S)) player.MoveBackward();
    if (KeyPressed(Key.Space)) player.Jump();
    if (KeyPressed(Key.E)) player.Interact();
    // Adding: remapping, macros, replay, AI control → nightmare
}

The Solution: Command Pattern

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional

@dataclass
class Entity:
    velocity: 'Vec3' = None
    speed: float = 5.0
    jump_force: float = 10.0
    is_grounded: bool = True

@dataclass
class Vec3:
    x: float = 0.0
    y: float = 0.0
    z: float = 0.0

# Command interface
class Command(ABC):
    @abstractmethod
    def execute(self, entity: Entity) -> None:
        pass

    def undo(self, entity: Entity) -> None:
        """Optional: for replay/time-rewind"""
        pass

# Concrete commands
class MoveForwardCommand(Command):
    def execute(self, entity: Entity) -> None:
        if entity.velocity:
            entity.velocity.z += entity.speed * 0.016  # dt ≈ 16ms

    def undo(self, entity: Entity) -> None:
        if entity.velocity:
            entity.velocity.z -= entity.speed * 0.016

class JumpCommand(Command):
    def execute(self, entity: Entity) -> None:
        if entity.is_grounded and entity.velocity:
            entity.velocity.y = entity.jump_force

class InteractCommand(Command):
    def execute(self, entity: Entity) -> None:
        print("Interacting with nearby object")
#include <memory>
#include <vector>

struct Vec3 {
    float x = 0.0f, y = 0.0f, z = 0.0f;
};

struct Entity {
    Vec3 velocity{};
    float speed = 5.0f;
    float jumpForce = 10.0f;
    bool isGrounded = true;
};

// Command interface
class Command {
public:
    virtual ~Command() = default;
    virtual void execute(Entity& entity) = 0;
    virtual void undo(Entity& entity) {}  // Optional: for replay/time-rewind
};

// Concrete commands
class MoveForwardCommand : public Command {
public:
    void execute(Entity& entity) override {
        entity.velocity.z += entity.speed * 0.016f;  // dt ≈ 16ms
    }
    void undo(Entity& entity) override {
        entity.velocity.z -= entity.speed * 0.016f;
    }
};

class JumpCommand : public Command {
public:
    void execute(Entity& entity) override {
        if (entity.isGrounded) entity.velocity.y = entity.jumpForce;
    }
};

class InteractCommand : public Command {
public:
    void execute(Entity& entity) override {
        // entity.interactWithNearby();
    }
};
import java.util.*;

public final class Vec3 {
    public float x, y, z;
    public Vec3() {}
    public Vec3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; }
}

public class Entity {
    public final Vec3 velocity = new Vec3();
    public float speed = 5.0f;
    public float jumpForce = 10.0f;
    public boolean isGrounded = true;
}

// Command interface
public interface Command {
    void execute(Entity entity);
    default void undo(Entity entity) {}  // Optional: for replay/time-rewind
}

// Concrete commands
public final class MoveForwardCommand implements Command {
    @Override
    public void execute(Entity entity) {
        entity.velocity.z += entity.speed * 0.016f;
    }
    @Override
    public void undo(Entity entity) {
        entity.velocity.z -= entity.speed * 0.016f;
    }
}

public final class JumpCommand implements Command {
    @Override
    public void execute(Entity entity) {
        if (entity.isGrounded) entity.velocity.y = entity.jumpForce;
    }
}

public final class InteractCommand implements Command {
    @Override
    public void execute(Entity entity) {
        // entity.interactWithNearby();
    }
}
using System;

public readonly struct Vec3 {
    public readonly float X, Y, Z;
    public Vec3(float x = 0, float y = 0, float z = 0) { X = x; Y = y; Z = z; }
}

public class Entity {
    public Vec3 Velocity { get; set; } = new Vec3();
    public float Speed = 5.0f;
    public float JumpForce = 10.0f;
    public bool IsGrounded = true;
}

public interface ICommand {
    void Execute(Entity entity);
    void Undo(Entity entity);
}

public abstract class Command : ICommand {
    public virtual void Undo(Entity entity) {}  // Optional: for replay/time-rewind
    public abstract void Execute(Entity entity);
}

public sealed class MoveForwardCommand : Command {
    public override void Execute(Entity entity) {
        entity.Velocity = new Vec3(entity.Velocity.X, entity.Velocity.Y, entity.Velocity.Z + entity.Speed * 0.016f);
    }
    public override void Undo(Entity entity) {
        entity.Velocity = new Vec3(entity.Velocity.X, entity.Velocity.Y, entity.Velocity.Z - entity.Speed * 0.016f);
    }
}

public sealed class JumpCommand : Command {
    public override void Execute(Entity entity) {
        if (entity.IsGrounded) entity.Velocity = new Vec3(entity.Velocity.X, entity.JumpForce, entity.Velocity.Z);
    }
}

public sealed class InteractCommand : Command {
    public override void Execute(Entity entity) {
        // entity.InteractWithNearby();
    }
}

Input Mapping (Decoupled)

from typing import Dict
from abc import ABC

class InputHandler:
    def __init__(self):
        self.key_bindings: Dict[int, Command] = {}
        self.command_history: list[Command] = []  # For undo/replay
        self.history_index = 0

    def bind(self, key: int, cmd: Command) -> None:
        self.key_bindings[key] = cmd

    def handle_input(self, entity) -> None:
        for key, cmd in self.key_bindings.items():
            if is_pressed(key):
                cmd.execute(entity)
                # Record for replay/undo
                if self.history_index < len(self.command_history):
                    self.command_history = self.command_history[:self.history_index]
                self.command_history.append(cmd)
                self.history_index += 1

    def undo(self, entity) -> None:
        if self.history_index > 0:
            self.history_index -= 1
            self.command_history[self.history_index].undo(entity)

    def save_replay(self, filename: str) -> None:
        pass  # Serialize command_history to file

    def load_replay(self, filename: str) -> None:
        pass  # Deserialize from file

def is_pressed(key: int) -> bool:
    return False  # Placeholder
#include &lt;unordered_map&gt;
#include &lt;vector&gt;
#include &lt;memory&gt;

class InputHandler {
    std::unordered_map&lt;int, std::unique_ptr&lt;Command&gt;&gt; keyBindings;
    std::vector&lt;std::unique_ptr&lt;Command&gt;&gt; commandHistory;  // For undo/replay
    size_t historyIndex = 0;

public:
    void bind(int key, std::unique_ptr&lt;Command&gt; cmd) {
        keyBindings[key] = std::move(cmd);
    }

    void handleInput(Entity& player) {
        for (auto& [key, cmd] : keyBindings) {
            if (isPressed(key)) {
                cmd->execute(player);
                // Record for replay/undo
                if (historyIndex < commandHistory.size()) {
                    commandHistory.resize(historyIndex);
                }
                commandHistory.push_back(cmd->clone());  // Need virtual clone()
                historyIndex++;
            }
        }
    }

    void undo(Entity& entity) {
        if (historyIndex > 0) {
            historyIndex--;
            commandHistory[historyIndex]->undo(player);
        }
    }

    // Replay system: serialize commandHistory to file
    void saveReplay(const std::string& filename);
    void loadReplay(const std::string& filename);

private:
    bool isPressed(int key) { return false; }  // Placeholder
};
import java.util.*;

public class InputHandler {
    private final Map<Integer, Command> keyBindings = new HashMap<>();
    private final List<Command> commandHistory = new ArrayList<>();
    private int historyIndex = 0;

    public void bind(int key, Command cmd) {
        keyBindings.put(key, cmd);
    }

    public void handleInput(Entity player) {
        for (var entry : keyBindings.entrySet()) {
            if (isPressed(entry.getKey())) {
                Command cmd = entry.getValue();
                cmd.execute(player);
                // Record for replay/undo
                if (historyIndex < commandHistory.size()) {
                    commandHistory.subList(historyIndex, commandHistory.size()).clear();
                }
                commandHistory.add(cmd);
                historyIndex++;
            }
        }
    }

    public void undo(Entity entity) {
        if (historyIndex > 0) {
            historyIndex--;
            commandHistory.get(historyIndex).undo(entity);
        }
    }

    public void saveReplay(String filename) { /* Serialize commandHistory */ }
    public void loadReplay(String filename) { /* Deserialize from file */ }

    private boolean isPressed(int key) { return false; }
}
using System;
using System.Collections.Generic;

public class InputHandler {
    private readonly Dictionary<int, ICommand> keyBindings = new();
    private readonly List<ICommand> commandHistory = new();
    private int historyIndex = 0;

    public void Bind(int key, ICommand cmd) {
        keyBindings[key] = cmd;
    }

    public void HandleInput(Entity player) {
        foreach (var kvp in keyBindings) {
            if (IsPressed(kvp.Key)) {
                kvp.Value.Execute(player);
                // Record for replay/undo
                if (historyIndex < commandHistory.Count) {
                    commandHistory.RemoveRange(historyIndex, commandHistory.Count - historyIndex);
                }
                commandHistory.Add(kvp.Value);
                historyIndex++;
            }
        }
    }

    public void Undo(Entity entity) {
        if (historyIndex > 0) {
            historyIndex--;
            commandHistory[historyIndex].Undo(entity);
        }
    }

    public void SaveReplay(string filename) { /* Serialize commandHistory */ }
    public void LoadReplay(string filename) { /* Deserialize from file */ }

    private bool IsPressed(int key) => false;
}

Player Remapping (Runtime)

from dataclasses import dataclass
from enum import IntEnum

class Key(IntEnum):
    W = 87
    S = 83
    SPACE = 32
    E = 69
    SHIFT = 16

@dataclass
class PlayerConfig:
    move_forward: int = Key.W
    move_backward: int = Key.S
    jump: int = Key.SPACE
    interact: int = Key.E
    dodge: int = Key.SHIFT

    def apply_to(self, input_handler: InputHandler) -> None:
        input_handler.bind(self.move_forward, MoveForwardCommand())
        input_handler.bind(self.move_backward, MoveBackwardCommand())
        input_handler.bind(self.jump, JumpCommand())
        input_handler.bind(self.interact, InteractCommand())
        input_handler.bind(self.dodge, DodgeCommand())
enum class Key : int {
    W = 87, S = 83, Space = 32, E = 69, Shift = 16
};

struct PlayerConfig {
    Key moveForward = Key::W;
    Key moveBackward = Key::S;
    Key jump = Key::Space;
    Key interact = Key::E;
    Key dodge = Key::Shift;

    void applyTo(InputHandler& input) {
        input.bind(moveForward, std::make_unique&lt;MoveForwardCommand&gt;());
        input.bind(moveBackward, std::make_unique&lt;MoveBackwardCommand&gt;());
        input.bind(jump, std::make_unique&lt;JumpCommand&gt;());
        input.bind(interact, std::make_unique&lt;InteractCommand&gt;());
        input.bind(dodge, std::make_unique&lt;DodgeCommand&gt;());
    }
};
public enum Key {
    W(87), S(83), SPACE(32), E(69), SHIFT(16);
    public final int value;
    Key(int v) { value = v; }
}

public record PlayerConfig(
    Key moveForward,
    Key moveBackward,
    Key jump,
    Key interact,
    Key dodge
) {
    public PlayerConfig() {
        this(Key.W, Key.S, Key.SPACE, Key.E, Key.SHIFT);
    }

    public void applyTo(InputHandler input) {
        input.bind(moveForward.value, new MoveForwardCommand());
        input.bind(moveBackward.value, new MoveBackwardCommand());
        input.bind(jump.value, new JumpCommand());
        input.bind(interact.value, new InteractCommand());
        input.bind(dodge.value, new DodgeCommand());
    }
}
public enum Key { W = 87, S = 83, Space = 32, E = 69, Shift = 16 }

public record PlayerConfig(
    Key MoveForward = Key.W,
    Key MoveBackward = Key.S,
    Key Jump = Key.Space,
    Key Interact = Key.E,
    Key Dodge = Key.Shift
) {
    public void ApplyTo(InputHandler input) {
        input.Bind((int)MoveForward, new MoveForwardCommand());
        input.Bind((int)MoveBackward, new MoveBackwardCommand());
        input.Bind((int)Jump, new JumpCommand());
        input.Bind((int)Interact, new InteractCommand());
        input.Bind((int)Dodge, new DodgeCommand());
    }
}

Command Pattern Benefits in Games

Feature Implementation
Remappable controls Swap commands in keymap at runtime
Replay system Serialize command history to file
Time rewind Execute undo() in reverse (Braid-style)
Macro/scripting Sequence commands, bind to single key
AI control AI outputs same Command interface
Multiplayer Send commands over network (deterministic)
Undo/Redo editor Level editor uses same system

2. Entity-Component-System (ECS)

The Problem with Deep Inheritance

# ❌ Inheritance hierarchy hell
class Entity: ...
class Actor(Entity): ...
class Pawn(Actor): ...
class Character(Pawn): ...
class Enemy(Character): ...
class FlyingEnemy(Enemy): ...

# Want flying player? Can't inherit from both!
// ❌ Inheritance hierarchy hell
class Entity { /* ... */ };
class Actor : public Entity { /* ... */ };
class Pawn : public Actor { /* ... */ };
class Character : public Pawn { /* ... */ };
class Enemy : public Character { /* ... */ };
class FlyingEnemy : public Enemy { /* ... */ };
// Want flying player? Can't inherit from both!
// ❌ Inheritance hierarchy hell
class Entity { /* ... */ }
class Actor extends Entity { /* ... */ }
class Pawn extends Actor { /* ... */ }
class Character extends Pawn { /* ... */ }
class Enemy extends Character { /* ... */ }
class FlyingEnemy extends Enemy { /* ... */ }
// Want flying player? Can't inherit from both!
// ❌ Inheritance hierarchy hell
class Entity { /* ... */ }
class Actor : Entity { /* ... */ }
class Pawn : Actor { /* ... */ }
class Character : Pawn { /* ... */ }
class Enemy : Character { /* ... */ }
class FlyingEnemy : Enemy { /* ... */ }
// Want flying player? Can't inherit from both!

ECS Solution: Composition Over Inheritance

from dataclasses import dataclass
from typing import Dict, Type, Any

# Components = pure data (no behavior)
@dataclass
class Transform:
    position: tuple = (0, 0, 0)
    rotation: tuple = (0, 0, 0)
    scale: tuple = (1, 1, 1)

@dataclass
class Velocity:
    x: float = 0.0
    y: float = 0.0
    z: float = 0.0

@dataclass
class Health:
    current: float = 100.0
    maximum: float = 100.0

@dataclass
class Sprite:
    texture_id: str
    uv_rect: tuple = (0, 0, 1, 1)

@dataclass
class AIController:
    behavior_tree: str

@dataclass
class PlayerControl:
    input_handler: Any = None

# Entities = IDs (integers)
Entity = int

# Systems = behavior (operate on component combinations)
class MovementSystem:
    def update(self, registry: Dict[Entity, Dict[Type, Any]], dt: float) -> None:
        for entity, components in registry.items():
            if Transform in components and Velocity in components:
                transform = components[Transform]
                velocity = components[Velocity]
                x, y, z = transform.position
                transform.position = (
                    x + velocity.x * dt,
                    y + velocity.y * dt,
                    z + velocity.z * dt
                )

class RenderSystem:
    def update(self, registry: Dict[Entity, Dict[Type, Any]]) -> None:
        for entity, components in registry.items():
            if Transform in components and Sprite in components:
                transform = components[Transform]
                sprite = components[Sprite]
                # renderer.draw(sprite.texture_id, transform.position, ...)
#include &lt;unordered_map&gt;
#include &lt;vector&gt;
#include &lt;typeindex&gt;
#include &lt;memory&gt;

// Components = pure data (no behavior)
struct Transform { Vec3 pos, rot, scale; };
struct Velocity { Vec3 value; };
struct Health { float current, max; };
struct Sprite { std::string tex; Rect uv; };
struct AIController { BehaviorTree bt; };
struct PlayerControl { InputHandler* input; };

// Entities = IDs
using Entity = uint64_t;

// Archetype-based storage (EnTT style)
class Registry {
    std::unordered_map&lt;Entity, std::unordered_map&lt;std::type_index, std::unique_ptr&lt;void&gt;&gt;&gt; components;

public:
    template&lt;typename T, typename... Args&gt;
    T& emplace(Entity e, Args&&... args) {
        auto& map = components[e];
        auto ptr = std::make_unique&lt;T&gt;(std::forward&lt;Args&gt;(args)...);
        T* ref = ptr.get();
        map[std::type_index(typeid(T))] = std::move(ptr);
        return *ref;
    }

    template&lt;typename T&gt;
    T* get(Entity e) {
        auto it = components.find(e);
        if (it == components.end()) return nullptr;
        auto cit = it->second.find(std::type_index(typeid(T)));
        return cit != it->second.end() ? static_cast&lt;T*&gt;(cit->second.get()) : nullptr;
    }

    template&lt;typename... Components&gt;
    class View {
        Registry& reg;
    public:
        View(Registry& r) : reg(r) {}

        template&lt;typename Func&gt;
        void each(Func&& f) {
            // Iterate entities with all Components...
        }
    };

    template&lt;typename... Components&gt;
    View&lt;Components...&gt; view() { return View&lt;Components...&gt;(*this); }
};

// Systems = behavior
class MovementSystem {
    void update(Registry& reg, float dt) {
        reg.view&lt;Transform, Velocity&gt;().each([&](Entity e, Transform& t, Velocity& v) {
            t.pos += v.value * dt;
        });
    }
};

class RenderSystem {
    void update(Registry& reg) {
        reg.view&lt;Transform, Sprite&gt;().each([&](Entity e, Transform& t, Sprite& s) {
            renderer.draw(s.tex, t.pos, t.rot, s.uv);
        });
    }
};

class AISystem {
    void update(Registry& reg, float dt) {
        reg.view&lt;Transform, AIController&gt;().each([&](Entity e, Transform& t, AIController& ai) {
            ai.bt.tick(e, reg, dt);
        });
    }
};
import java.util.*;
import java.util.function.*;

// Components = pure data (records for immutability)
public record Transform(Vec3 pos, Vec3 rot, Vec3 scale) {}
public record Velocity(Vec3 value) {}
public record Health(float current, float max) {}
public record Sprite(String tex, Rect uv) {}
public record AIController(BehaviorTree bt) {}
public record PlayerControl(InputHandler input) {}

// Entity = ID
public record Entity(long id) {}

// Archetype-based storage (similar to flecs/EnTT)
public class Registry {
    private final Map&lt;Entity, Map&lt;Class&lt;?&gt;, Object&gt;&gt; components = new HashMap&lt;&gt;();

    public &lt;T&gt; T emplace(Entity e, T component) {
        components.computeIfAbsent(e, k -> new HashMap&lt;&gt;())
                  .put(component.getClass(), component);
        return component;
    }

    public &lt;T&gt; T get(Entity e, Class&lt;T&gt; type) {
        var map = components.get(e);
        return map != null ? type.cast(map.get(type)) : null;
    }

    public &lt;T&gt; Iterable&lt;Entity&gt; entitiesWith(Class&lt;T&gt;... types) {
        return () -> components.entrySet().stream()
            .filter(e -> Arrays.stream(types).allMatch(t -> e.getValue().containsKey(t)))
            .map(Map.Entry::getKey)
            .iterator();
    }
}

// Systems = behavior
public class MovementSystem {
    public void update(Registry reg, float dt) {
        for (Entity e : reg.entitiesWith(Transform.class, Velocity.class)) {
            Transform t = reg.get(e, Transform.class);
            Velocity v = reg.get(e, Velocity.class);
            t.pos = t.pos.add(v.value.mul(dt));
        }
    }
}

public class RenderSystem {
    public void update(Registry reg) {
        for (Entity e : reg.entitiesWith(Transform.class, Sprite.class)) {
            Transform t = reg.get(e, Transform.class);
            Sprite s = reg.get(e, Sprite.class);
            renderer.draw(s.tex, t.pos, t.rot, s.uv);
        }
    }
}
using System;
using System.Collections.Generic;

// Components = pure data (records/structs)
public readonly record struct Transform(Vec3 Pos, Vec3 Rot, Vec3 Scale);
public readonly record struct Velocity(Vec3 Value);
public readonly record struct Health(float Current, float Max);
public readonly record struct Sprite(string Tex, Rect UV);
public readonly record struct AIController(BehaviorTree BT);
public readonly record struct PlayerControl(InputHandler Input);

public readonly record struct Entity(ulong ID);

// Archetype-based storage
public class Registry {
    private readonly Dictionary&lt;Entity, Dictionary&lt;Type, object&gt;&gt; components = new();

    public T Emplace&lt;T&gt;(Entity e, T component) where T : notnull {
        if (!components.TryGetValue(e, out var map)) {
            map = new Dictionary&lt;Type, object&gt;();
            components[e] = map;
        }
        map[typeof(T)] = component;
        return component;
    }

    public T Get&lt;T&gt;(Entity e) {
        return components.TryGetValue(e, out var map) && map.TryGetValue(typeof(T), out var obj)
            ? (T)obj : default;
    }

    public IEnumerable&lt;Entity&gt; EntitiesWith(params Type[] types) {
        foreach (var kvp in components) {
            if (types.All(t => kvp.Value.ContainsKey(t))) yield return kvp.Key;
        }
    }
}

// Systems = behavior
public class MovementSystem {
    public void Update(Registry reg, float dt) {
        foreach (var e in reg.EntitiesWith(typeof(Transform), typeof(Velocity))) {
            var t = reg.Get&lt;Transform&gt;(e);
            var v = reg.Get&lt;Velocity&gt;(e);
            t.Pos += v.Value * dt; // Requires mutable or replace
        }
    }
}

public class RenderSystem {
    public void Update(Registry reg) {
        foreach (var e in reg.EntitiesWith(typeof(Transform), typeof(Sprite))) {
            var t = reg.Get&lt;Transform&gt;(e);
            var s = reg.Get&lt;Sprite&gt;(e);
            Renderer.Draw(s.Tex, t.Pos, t.Rot, s.UV);
        }
    }
}

ECS vs Traditional OOP

Aspect OOP (Inheritance) ECS (Composition)
Adding features New subclass, modify hierarchy New component + system
Flying player Impossible (diamond) Add Flight component
Cache performance Poor (scattered vtables) Excellent (SoA layout)
Parallelism Hard (shared mutable state) Easy (disjoint components)
Scripting integration Complex Natural (data-driven)
Serialization Complex (pointers, vtables) Trivial (POD components)

3. State Pattern for AI/Game States

from abc import ABC, abstractmethod
from typing import Optional

class State(ABC):
    @abstractmethod
    def enter(self, entity: Entity) -> None:
        pass

    @abstractmethod
    def update(self, entity: Entity, dt: float) -> Optional['State']:
        pass

    @abstractmethod
    def exit(self, entity: Entity) -> None:
        pass

class IdleState(State):
    def enter(self, entity: Entity) -> None:
        print("Entering Idle")

    def update(self, entity: Entity, dt: float) -> Optional[State]:
        if entity.can_see_player():
            return ChaseState()
        return None

    def exit(self, entity: Entity) -> None:
        print("Exiting Idle")

class ChaseState(State):
    def enter(self, entity: Entity) -> None:
        entity.play_animation("run")

    def update(self, entity: Entity, dt: float) -> Optional[State]:
        if not entity.can_see_player():
            return SearchState(entity.last_known_player_pos)
        elif entity.in_attack_range():
            return AttackState()
        else:
            entity.move_toward(entity.player_pos)
            return None

    def exit(self, entity: Entity) -> None:
        entity.stop_animation()

# Entity holds current state
class Entity:
    def __init__(self):
        self.state: State = IdleState()

    def change_state(self, new_state: State) -> None:
        self.state.exit(self)
        self.state = new_state
        self.state.enter(self)

    def update(self, dt: float) -> None:
        next_state = self.state.update(self, dt)
        if next_state:
            self.change_state(next_state)
#include &lt;memory&gt;

class Entity;

class State {
public:
    virtual ~State() = default;
    virtual void enter(Entity& entity) = 0;
    virtual std::unique_ptr&lt;State&gt; update(Entity& entity, float dt) = 0;
    virtual void exit(Entity& entity) = 0;
};

class IdleState : public State {
    void enter(Entity& entity) override { /* play idle anim */ }
    std::unique_ptr&lt;State&gt; update(Entity& entity, float dt) override {
        if (entity.canSeePlayer()) return std::make_unique&lt;ChaseState&gt;();
        return nullptr;
    }
    void exit(Entity& entity) override {}
};

class ChaseState : public State {
    void enter(Entity& entity) override { entity.playAnimation("run"); }
    std::unique_ptr&lt;State&gt; update(Entity& entity, float dt) override {
        if (!entity.canSeePlayer()) 
            return std::make_unique&lt;SearchState&gt;(entity.getLastKnownPlayerPos());
        if (entity.inAttackRange()) 
            return std::make_unique&lt;AttackState&gt;();
        entity.moveToward(entity.getPlayerPos());
        return nullptr;
    }
    void exit(Entity& entity) override { entity.stopAnimation(); }
};

class Entity {
    std::unique_ptr&lt;State&gt; state = std::make_unique&lt;IdleState&gt;();

    void changeState(std::unique_ptr&lt;State&gt; newState) {
        state->exit(*this);
        state = std::move(newState);
        state->enter(*this);
    }

    void update(float dt) {
        if (auto next = state->update(*this, dt)) changeState(std::move(next));
    }
};
interface State {
    void enter(Entity entity);
    State update(Entity entity, float dt);
    void exit(Entity entity);
}

class IdleState implements State {
    public void enter(Entity e) { /* play idle anim */ }
    public State update(Entity e, float dt) { 
        return e.canSeePlayer() ? new ChaseState() : null; 
    }
    public void exit(Entity e) {}
}

class ChaseState implements State {
    public void enter(Entity e) { e.playAnimation("run"); }
    public State update(Entity e, float dt) {
        if (!e.canSeePlayer()) return new SearchState(e.getLastKnownPlayerPos());
        if (e.inAttackRange()) return new AttackState();
        e.moveToward(e.getPlayerPos());
        return null;
    }
    public void exit(Entity e) { e.stopAnimation(); }
}

public class Entity {
    private State state = new IdleState();

    public void changeState(State newState) {
        state.exit(this);
        state = newState;
        state.enter(this);
    }

    public void update(float dt) {
        State next = state.update(this, dt);
        if (next != null) changeState(next);
    }
}
public interface IState {
    void Enter(Entity entity);
    IState Update(Entity entity, float dt);
    void Exit(Entity entity);
}

public class IdleState : IState {
    public void Enter(Entity e) { /* play idle anim */ }
    public IState Update(Entity e, float dt) => 
        e.CanSeePlayer() ? new ChaseState() : null;
    public void Exit(Entity e) {}
}

public class ChaseState : IState {
    public void Enter(Entity e) => e.PlayAnimation("run");
    public IState Update(Entity e, float dt) {
        if (!e.CanSeePlayer()) return new SearchState(e.LastKnownPlayerPos);
        if (e.InAttackRange()) return new AttackState();
        e.MoveToward(e.PlayerPos);
        return null;
    }
    public void Exit(Entity e) => e.StopAnimation();
}

public class Entity {
    private IState state = new IdleState();

    public void ChangeState(IState newState) {
        state.Exit(this);
        state = newState;
        state.Enter(this);
    }

    public void Update(float dt) {
        var next = state.Update(this, dt);
        if (next != null) ChangeState(next);
    }
}

4. Observer Pattern (Event System)

from enum import Enum
from typing import Callable, Dict, List
from collections import defaultdict

class GameEvent(Enum):
    PLAYER_DIED = "player_died"
    ENEMY_KILLED = "enemy_killed"
    ITEM_COLLECTED = "item_collected"
    LEVEL_COMPLETED = "level_completed"

class EventBus:
    def __init__(self):
        self._listeners: Dict[GameEvent, List[Callable]] = defaultdict(list)

    def subscribe(self, event: GameEvent, callback: Callable) -> None:
        self._listeners[event].append(callback)

    def publish(self, event: GameEvent, data: dict = None) -> None:
        for callback in self._listeners[event]:
            callback(data or {})

# Usage
bus = EventBus()

bus.subscribe(GameEvent.ENEMY_KILLED, lambda data: {
    player.add_xp(data.get("xp", 0)),
    ui.show_xp_popup(data.get("xp", 0))
})

# Any system can publish
bus.publish(GameEvent.ENEMY_KILLED, {"xp": 100, "enemy_type": "goblin"})
#include &lt;functional&gt;
#include &lt;unordered_map&gt;
#include &lt;vector&gt;
#include &lt;any&gt;

enum class GameEvent { PlayerDied, EnemyKilled, ItemCollected, LevelCompleted };

class EventBus {
    std::unordered_map&lt;GameEvent, std::vector&lt;std::function&lt;void(const std::any&)&gt;&gt;&gt; listeners;
public:
    template&lt;typename F&gt;
    void subscribe(GameEvent event, F&& callback) {
        listeners[event].push_back(std::forward&lt;F&gt;(callback));
    }

    void publish(GameEvent event, const std::any& data = {}) {
        for (auto& cb : listeners[event]) cb(data);
    }
};

// Usage
EventBus bus;
bus.subscribe(GameEvent::EnemyKilled, [](const std::any& data) {
    auto d = std::any_cast&lt;std::unordered_map&lt;std::string, int&gt;&gt;(data);
    player.addXP(d.at("xp"));
    ui.showXPPopup(d.at("xp"));
});

// Any system can publish
bus.publish(GameEvent::EnemyKilled, 
    std::unordered_map&lt;std::string, int&gt;{{"xp", 100}, {"enemy_type", "goblin"}});
import java.util.*;
import java.util.function.*;

enum GameEvent { PlayerDied, EnemyKilled, ItemCollected, LevelCompleted }

class EventBus {
    private final Map&lt;GameEvent, List&lt;Consumer&lt;Map&lt;String, Object&gt;&gt;&gt;&gt; listeners = new EnumMap&lt;&gt;(GameEvent.class);

    public void subscribe(GameEvent event, Consumer&lt;Map&lt;String, Object&gt;&gt; callback) {
        listeners.computeIfAbsent(event, k -> new ArrayList&lt;&gt;()).add(callback);
    }

    public void publish(GameEvent event, Map&lt;String, Object&gt; data) {
        for (var cb : listeners.getOrDefault(event, List.of())) cb.accept(data);
    }
}

// Usage
EventBus bus = new EventBus();
bus.subscribe(GameEvent.EnemyKilled, data -> {
    player.addXP((int) data.get("xp"));
    ui.showXPPopup((int) data.get("xp"));
});

bus.publish(GameEvent.EnemyKilled, Map.of("xp", 100, "enemy_type", "goblin"));
using System;
using System.Collections.Generic;

public enum GameEvent { PlayerDied, EnemyKilled, ItemCollected, LevelCompleted }

public class EventBus {
    private readonly Dictionary&lt;GameEvent, List&lt;Action&lt;Dictionary&lt;string, object&gt;&gt;&gt;&gt; _listeners = new();

    public void Subscribe(GameEvent evt, Action&lt;Dictionary&lt;string, object&gt;&gt; cb) {
        if (!_listeners.TryGetValue(evt, out var list)) _listeners[evt] = list = new();
        list.Add(cb);
    }

    public void Publish(GameEvent evt, Dictionary&lt;string, object&gt; data = null) {
        if (_listeners.TryGetValue(evt, out var list))
            foreach (var cb in list) cb(data ?? new());
    }
}

// Usage
var bus = new EventBus();
bus.Subscribe(GameEvent.EnemyKilled, data => {
    player.AddXP((int)data["xp"]);
    ui.ShowXPPopup((int)data["xp"]);
});

bus.Publish(GameEvent.EnemyKilled, new Dictionary&lt;string, object&gt; { ["xp"] = 100, ["enemy_type"] = "goblin" });

5. Object Pool Pattern

from typing import TypeVar, Generic, List, Callable
T = TypeVar('T')

class ObjectPool(Generic[T]):
    def __init__(self, factory: Callable[[], T], initial_size: int = 100):
        self._factory = factory
        self._pool: List[T] = [factory() for _ in range(initial_size)]
        self._active: List[T] = []

    def acquire(self) -> T:
        if self._pool:
            obj = self._pool.pop()
        else:
            obj = self._factory()
        self._active.append(obj)
        return obj

    def release(self, obj: T) -> None:
        if obj in self._active:
            self._active.remove(obj)
            self._pool.append(obj)

    def update_all(self, dt: float) -> None:
        for obj in self._active[:]:  # Copy to allow removal during iteration
            obj.update(dt)
            if obj.should_destroy:
                self.release(obj)

# Particle example
class Particle:
    def __init__(self):
        self.position = (0, 0, 0)
        self.velocity = (0, 0, 0)
        self.lifetime = 1.0
        self.should_destroy = False

    def update(self, dt: float):
        x, y, z = self.position
        vx, vy, vz = self.velocity
        self.position = (x + vx*dt, y + vy*dt, z + vz*dt)
        self.lifetime -= dt
        if self.lifetime <= 0:
            self.should_destroy = True

pool = ObjectPool(Particle, initial_size=10000)

def spawn_explosion(pos: tuple):
    import random
    for _ in range(50):
        p = pool.acquire()
        p.position = (pos[0] + random.uniform(-0.5, 0.5),
                      pos[1] + random.uniform(-0.5, 0.5),
                      pos[2] + random.uniform(-0.5, 0.5))
        p.velocity = (random.uniform(-5, 5), random.uniform(-5, 5), random.uniform(-5, 5))
        p.lifetime = 1.0
        p.should_destroy = False
#include &lt;vector&gt;
#include &lt;memory&gt;
#include &lt;functional&gt;

template&lt;typename T&gt;
class ObjectPool {
    std::vector&lt;T&gt; pool;
    std::vector&lt;T*&gt; active;
    std::function&lt;T()&gt; factory;

public:
    ObjectPool(std::function&lt;T()&gt; f, size_t initial = 100) : factory(f) {
        pool.reserve(initial);
        for (size_t i = 0; i < initial; ++i) pool.emplace_back(f());
    }

    T* acquire() {
        T* obj;
        if (!pool.empty()) {
            obj = &amp;pool.back();
            pool.pop_back();
        } else {
            pool.emplace_back(factory());
            obj = &amp;pool.back();
        }
        active.push_back(obj);
        return obj;
    }

    void release(T* obj) {
        obj->~T();
        // Move-released object back to pool
        std::swap(*obj, pool.emplace_back());
        pool.pop_back();
        // Remove from active
        auto it = std::find(active.begin(), active.end(), obj);
        if (it != active.end()) active.erase(it);
    }

    template&lt;typename F&gt;
    void updateAll(float dt, F&& updateFn) {
        for (size_t i = 0; i < active.size(); ) {
            updateFn(*active[i], dt);
            if (active[i]->shouldDestroy) release(active[i]);
            else ++i;
        }
    }
};

// Particle system — 10,000 particles
struct Particle {
    Vec3 position, velocity;
    float lifetime = 1.0f;
    bool shouldDestroy = false;
};

ObjectPool&lt;Particle&gt; particlePool(10000, []{ return Particle{}; });

void spawnExplosion(Vec3 pos) {
    for (int i = 0; i < 50; ++i) {
        Particle* p = particlePool.acquire();
        p->position = pos + randomVec3(0.5f);
        p->velocity = randomVec3(5.0f);
        p->lifetime = 1.0f;
        p->shouldDestroy = false;
    }
}
import java.util.*;
import java.util.function.*;

public class ObjectPool&lt;T&gt; {
    private final Supplier&lt;T&gt; factory;
    private final List&lt;T&gt; pool = new ArrayList&lt;&gt;();
    private final List&lt;T&gt; active = new ArrayList&lt;&gt;();

    public ObjectPool(Supplier&lt;T&gt; factory, int initial) {
        this.factory = factory;
        for (int i = 0; i < initial; i++) pool.add(factory.get());
    }

    public T acquire() {
        T obj = pool.isEmpty() ? factory.get() : pool.remove(pool.size() - 1);
        active.add(obj);
        return obj;
    }

    public void release(T obj) {
        active.remove(obj);
        pool.add(obj);
    }

    public void updateAll(float dt, Consumer&lt;T&gt; updateFn) {
        for (int i = 0; i < active.size(); ) {
            T obj = active.get(i);
            updateFn.accept(obj);
            if (obj.shouldDestroy) release(obj);
            else i++;
        }
    }
}

// Particle system — 10,000 particles
public class Particle {
    public Vec3 position, velocity;
    public float lifetime = 1.0f;
    public boolean shouldDestroy = false;
}

ObjectPool&lt;Particle&gt; particlePool = new ObjectPool&lt;&gt;(Particle::new, 10000);

void spawnExplosion(Vec3 pos) {
    Random rng = new Random();
    for (int i = 0; i < 50; i++) {
        Particle p = particlePool.acquire();
        p.position = pos.add(randomVec3(0.5f));
        p.velocity = randomVec3(5.0f);
        p.lifetime = 1.0f;
        p.shouldDestroy = false;
    }
}
using System;
using System.Collections.Generic;

public class ObjectPool&lt;T&gt; where T : class, new() {
    private readonly Func&lt;T&gt; _factory;
    private readonly List&lt;T&gt; _pool = new();
    private readonly List&lt;T&gt; _active = new();

    public ObjectPool(Func&lt;T&gt; factory, int initial = 100) {
        _factory = factory;
        for (int i = 0; i < initial; i++) _pool.Add(factory());
    }

    public T Acquire() {
        var obj = _pool.Count > 0 ? _pool[_pool.Count - 1] : _factory();
        if (_pool.Count > 0) _pool.RemoveAt(_pool.Count - 1);
        _active.Add(obj);
        return obj;
    }

    public void Release(T obj) {
        _active.Remove(obj);
        _pool.Add(obj);
    }

    public void UpdateAll(float dt, Action&lt;T&gt; updateFn) {
        for (int i = 0; i < _active.Count; ) {
            var obj = _active[i];
            updateFn(obj);
            if (obj.ShouldDestroy) Release(obj);
            else i++;
        }
    }
}

// Particle system — 10,000 particles
public class Particle {
    public Vec3 Position, Velocity;
    public float Lifetime = 1.0f;
    public bool ShouldDestroy = false;
}

var particlePool = new ObjectPool&lt;Particle&gt;(() => new Particle(), 10000);

void SpawnExplosion(Vec3 pos) {
    var rng = new Random();
    for (int i = 0; i < 50; i++) {
        var p = particlePool.Acquire();
        p.Position = pos + RandomVec3(0.5f);
        p.Velocity = RandomVec3(5.0f);
        p.Lifetime = 1.0f;
        p.ShouldDestroy = false;
    }
}

6. Factory Pattern for Level Loading

from typing import Dict, Any
import json

class EntityFactory:
    def __init__(self, registry, resources):
        self.registry = registry
        self.resources = resources

    def create_enemy(self, enemy_type: str, pos: tuple) -> int:
        e = self.registry.create()
        self.registry.emplace(e, Transform(pos))
        self.registry.emplace(e, Health(100, 100))
        self.registry.emplace(e, Sprite(self.resources.get_texture(f"enemy_{enemy_type}")))
        self.registry.emplace(e, AIController(load_behavior_tree(enemy_type)))
        return e

    def create_player(self, pos: tuple) -> int:
        e = self.registry.create()
        self.registry.emplace(e, Transform(pos))
        self.registry.emplace(e, Health(100, 100))
        self.registry.emplace(e, Sprite(self.resources.get_texture("player")))
        self.registry.emplace(e, PlayerControl(input_handler))
        return e

# Data-driven: load from JSON
def create_from_template(factory: EntityFactory, template: dict) -> int:
    e = factory.registry.create()
    for component_name, data in template.get("components", {}).items():
        if component_name == "Transform":
            factory.registry.emplace(e, Transform(data["pos"]))
        elif component_name == "Health":
            factory.registry.emplace(e, Health(data["current"], data["max"]))
        # ... other components
    return e

# Level loading
def load_level(factory: EntityFactory, level_path: str) -> None:
    with open(level_path) as f:
        level_data = json.load(f)

    for entity_template in level_data.get("entities", []):
        create_from_template(factory, entity_template)
#include &lt;nlohmann/json.hpp&gt;
#include &lt;string&gt;
#include &lt;unordered_map&gt;

using json = nlohmann::json;

class EntityFactory {
    Registry& reg;
    ResourceManager& resources;

public:
    EntityFactory(Registry& r, ResourceManager& res) : reg(r), resources(res) {}

    Entity createEnemy(const std::string& type, Vec3 pos) {
        Entity e = reg.create();
        reg.emplace&lt;Transform&gt;(e, pos);
        reg.emplace&lt;Health&gt;(e, 100, 100);
        reg.emplace&lt;Sprite&gt;(e, resources.getTexture("enemy_" + type));
        reg.emplace&lt;AIController&gt;(e, loadBehaviorTree(type));
        return e;
    }

    Entity createPlayer(Vec3 pos) {
        Entity e = reg.create();
        reg.emplace&lt;Transform&gt;(e, pos);
        reg.emplace&lt;Health&gt;(e, 100, 100);
        reg.emplace&lt;Sprite&gt;(e, resources.getTexture("player"));
        reg.emplace&lt;PlayerControl&gt;(e, &amp;inputHandler);
        return e;
    }
};

// Data-driven: load from JSON
Entity createFromTemplate(EntityFactory& factory, const json& tmpl) {
    Entity e = factory.reg.create();
    for (auto& [compName, data] : tmpl["components"].items()) {
        if (compName == "Transform") {
            factory.reg.emplace&lt;Transform&gt;(e, data["pos"]);
        } else if (compName == "Health") {
            factory.reg.emplace&lt;Health&gt;(e, data["current"], data["max"]);
        }
        // ... other components
    }
    return e;
}

// Level loading
void loadLevel(EntityFactory& factory, const std::string& path) {
    std::ifstream f(path);
    json levelData = json::parse(f);

    for (auto& entityTmpl : levelData["entities"]) {
        createFromTemplate(factory, entityTmpl);
    }
}
import com.fasterxml.jackson.databind.JsonNode;
import java.io.*;
import java.util.*;

public class EntityFactory {
    private final Registry reg;
    private final ResourceManager resources;

    public EntityFactory(Registry reg, ResourceManager res) { this.reg = reg; this.resources = res; }

    public Entity createEnemy(String type, Vec3 pos) {
        Entity e = reg.create();
        reg.emplace(e, new Transform(pos));
        reg.emplace(e, new Health(100, 100));
        reg.emplace(e, new Sprite(resources.getTexture("enemy_" + type)));
        reg.emplace(e, new AIController(loadBehaviorTree(type)));
        return e;
    }

    public Entity createPlayer(Vec3 pos) {
        Entity e = reg.create();
        reg.emplace(e, new Transform(pos));
        reg.emplace(e, new Health(100, 100));
        reg.emplace(e, new Sprite(resources.getTexture("player")));
        reg.emplace(e, new PlayerControl(inputHandler));
        return e;
    }
}

// Data-driven: load from JSON
Entity createFromTemplate(EntityFactory factory, JsonNode tmpl) {
    Entity e = factory.reg.create();
    var components = tmpl.get("components");
    if (components != null) {
        components.fields().forEachRemaining(entry -> {
            String compName = entry.getKey();
            JsonNode data = entry.getValue();
            if (compName.equals("Transform")) {
                factory.reg.emplace(e, new Transform(data.get("pos")));
            } else if (compName.equals("Health")) {
                factory.reg.emplace(e, new Health(data.get("current").asInt(), data.get("max").asInt()));
            }
        });
    }
    return e;
}

void loadLevel(EntityFactory factory, String path) throws IOException {
    var mapper = new ObjectMapper();
    JsonNode levelData = mapper.readTree(new File(path));
    for (JsonNode entityTmpl : levelData.get("entities")) {
        createFromTemplate(factory, entityTmpl);
    }
}
using System.Text.Json;
using System.IO;

public class EntityFactory {
    private readonly Registry _reg;
    private readonly ResourceManager _resources;

    public EntityFactory(Registry reg, ResourceManager res) {
        _reg = reg; _resources = res;
    }

    public Entity CreateEnemy(string type, Vec3 pos) {
        var e = _reg.Create();
        _reg.Emplace(e, new Transform(pos));
        _reg.Emplace(e, new Health(100, 100));
        _reg.Emplace(e, new Sprite(_resources.GetTexture($"enemy_{type}")));
        _reg.Emplace(e, new AIController(LoadBehaviorTree(type)));
        return e;
    }

    public Entity CreatePlayer(Vec3 pos) {
        var e = _reg.Create();
        _reg.Emplace(e, new Transform(pos));
        _reg.Emplace(e, new Health(100, 100));
        _reg.Emplace(e, new Sprite(_resources.GetTexture("player")));
        _reg.Emplace(e, new PlayerControl(_inputHandler));
        return e;
    }
}

Entity CreateFromTemplate(EntityFactory factory, JsonElement tmpl) {
    var e = factory._reg.Create();
    foreach (var prop in tmpl.GetProperty("components").EnumerateObject()) {
        if (prop.Name == "Transform") {
            factory._reg.Emplace(e, new Transform(prop.Value.GetProperty("pos")));
        } else if (prop.Name == "Health") {
            factory._reg.Emplace(e, new Health(prop.Value.GetProperty("current").GetInt32(), prop.Value.GetProperty("max").GetInt32()));
        }
    }
    return e;
}

void LoadLevel(EntityFactory factory, string path) {
    var json = JsonDocument.Parse(File.ReadAllText(path));
    foreach (var entityTmpl in json.RootElement.GetProperty("entities").EnumerateArray()) {
        CreateFromTemplate(factory, entityTmpl);
    }
}

Pattern Selection Guide

Problem Recommended Pattern
Input handling, replay, AI control Command
Complex entity composition ECS
AI/behavior states State
Decoupled game events Observer/Event Bus
Frequent allocation (particles, bullets) Object Pool
Data-driven entity creation Factory
Cross-cutting global services Service Locator

Resources

  • Game Programming Patterns — Robert Nystrom (free: gameprogrammingpatterns.com)
  • Entity Component SystemsECS FAQ
  • EnTT — Fast, header-only C++ ECS
  • flecs — C/C++ ECS with queries, modules, stats
  • bevy_ecs — Rust ECS (inspires modern C++ designs)