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
}
# ❌ Naive input handling — tightly coupled, hard to extend
def handle_input(player)
  player.move_forward  if key_pressed?(Key::W)
  player.move_backward if key_pressed?(Key::S)
  player.jump          if key_pressed?(Key::SPACE)
  player.interact      if key_pressed?(Key::E)
  # Adding: remapping, macros, replay, AI control → nightmare
end

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();
    }
}
Vec3 = Struct.new(:x, :y, :z) do
  def initialize(x = 0.0, y = 0.0, z = 0.0) = super
end

Entity = Struct.new(:velocity, :speed, :jump_force, :is_grounded) do
  def initialize(velocity: nil, speed: 5.0, jump_force: 10.0, is_grounded: true)
    super(velocity, speed, jump_force, is_grounded)
  end
end

# Command interface
class Command
  def execute(entity) = raise NotImplementedError

  # Optional: for replay/time-rewind
  def undo(entity); end
end

# Concrete commands
class MoveForwardCommand < Command
  def execute(entity)
    entity.velocity.z += entity.speed * 0.016 if entity.velocity # dt ≈ 16ms
  end

  def undo(entity)
    entity.velocity.z -= entity.speed * 0.016 if entity.velocity
  end
end

class JumpCommand < Command
  def execute(entity)
    entity.velocity.y = entity.jump_force if entity.is_grounded && entity.velocity
  end
end

class InteractCommand < Command
  def execute(_entity)
    puts "Interacting with nearby object"
  end
end

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;
}
class InputHandler
  def initialize
    @key_bindings = {}
    @command_history = [] # For undo/replay
    @history_index = 0
  end

  def bind(key, cmd)
    @key_bindings[key] = cmd
  end

  def handle_input(entity)
    @key_bindings.each do |key, cmd|
      next unless pressed?(key)

      cmd.execute(entity)
      # Record for replay/undo
      @command_history = @command_history[0...@history_index]
      @command_history << cmd
      @history_index += 1
    end
  end

  def undo(entity)
    return unless @history_index > 0

    @history_index -= 1
    @command_history[@history_index].undo(entity)
  end

  def save_replay(filename); end # Serialise command_history to file
  def load_replay(filename); end # Deserialise from file

  private

  def pressed?(_key)
    false # Placeholder
  end
end

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());
    }
}
module Key
  W = 87
  S = 83
  SPACE = 32
  E = 69
  SHIFT = 16
end

PlayerConfig = Struct.new(:move_forward, :move_backward, :jump, :interact, :dodge) do
  def initialize(move_forward: Key::W, move_backward: Key::S, jump: Key::SPACE,
                 interact: Key::E, dodge: Key::SHIFT)
    super(move_forward, move_backward, jump, interact, dodge)
  end

  def apply_to(input_handler)
    input_handler.bind(move_forward, MoveForwardCommand.new)
    input_handler.bind(move_backward, MoveBackwardCommand.new)
    input_handler.bind(jump, JumpCommand.new)
    input_handler.bind(interact, InteractCommand.new)
    input_handler.bind(dodge, DodgeCommand.new)
  end
end

Command Pattern Benefits in Games

Feature Implementation
Remappable controls Swap commands in keymap at runtime
Replay system Serialise 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!
# ❌ Inheritance hierarchy hell
class Entity; end
class Actor < Entity; end
class Pawn < Actor; end
class Character < Pawn; end
class Enemy < Character; end
class FlyingEnemy < Enemy; end

# Want a flying player? Can't inherit from both!
# (Ruby modules help, but data-driven composition scales further)

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);
        }
    }
}
# Components = pure data (no behaviour)
Transform = Struct.new(:position, :rotation, :scale) do
  def initialize(position = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1]) = super
end

Velocity = Struct.new(:x, :y, :z) do
  def initialize(x = 0.0, y = 0.0, z = 0.0) = super
end

Health = Struct.new(:current, :maximum) do
  def initialize(current = 100.0, maximum = 100.0) = super
end

Sprite = Struct.new(:texture_id, :uv_rect) do
  def initialize(texture_id, uv_rect = [0, 0, 1, 1]) = super
end

AIController = Struct.new(:behaviour_tree)
PlayerControl = Struct.new(:input_handler)

# Entities = IDs (integers); registry maps entity => { ComponentClass => instance }

# Systems = behaviour (operate on component combinations)
class MovementSystem
  def update(registry, dt)
    registry.each_value do |components|
      next unless components[Transform] && components[Velocity]

      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]
    end
  end
end

class RenderSystem
  def update(registry)
    registry.each_value do |components|
      next unless components[Transform] && components[Sprite]

      # renderer.draw(components[Sprite].texture_id, components[Transform].position, ...)
    end
  end
end

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)
Serialisation 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);
    }
}
class State
  def enter(entity) = raise NotImplementedError
  def update(entity, dt) = raise NotImplementedError
  def exit(entity) = raise NotImplementedError
end

class IdleState < State
  def enter(_entity)
    puts "Entering Idle"
  end

  def update(entity, _dt)
    entity.can_see_player? ? ChaseState.new : nil
  end

  def exit(_entity)
    puts "Exiting Idle"
  end
end

class ChaseState < State
  def enter(entity)
    entity.play_animation("run")
  end

  def update(entity, _dt)
    if !entity.can_see_player?
      SearchState.new(entity.last_known_player_pos)
    elsif entity.in_attack_range?
      AttackState.new
    else
      entity.move_toward(entity.player_pos)
      nil
    end
  end

  def exit(entity)
    entity.stop_animation
  end
end

# Entity holds current state
class Entity
  def initialize
    @state = IdleState.new
  end

  def change_state(new_state)
    @state.exit(self)
    @state = new_state
    @state.enter(self)
  end

  def update(dt)
    next_state = @state.update(self, dt)
    change_state(next_state) if next_state
  end
end

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" });
module GameEvent
  PLAYER_DIED = :player_died
  ENEMY_KILLED = :enemy_killed
  ITEM_COLLECTED = :item_collected
  LEVEL_COMPLETED = :level_completed
end

class EventBus
  def initialize
    @listeners = Hash.new { |hash, key| hash[key] = [] }
  end

  def subscribe(event, &callback)
    @listeners[event] << callback
  end

  def publish(event, data = {})
    @listeners[event].each { |callback| callback.call(data) }
  end
end

# Usage
bus = EventBus.new

bus.subscribe(GameEvent::ENEMY_KILLED) do |data|
  player.add_xp(data.fetch(:xp, 0))
  ui.show_xp_popup(data.fetch(:xp, 0))
end

# Any system can publish
bus.publish(GameEvent::ENEMY_KILLED, 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;
    }
}
class ObjectPool
  def initialize(initial_size: 100, &factory)
    @factory = factory
    @pool = Array.new(initial_size) { factory.call }
    @active = []
  end

  def acquire
    obj = @pool.pop || @factory.call
    @active << obj
    obj
  end

  def release(obj)
    return unless @active.delete(obj)

    @pool << obj
  end

  def update_all(dt)
    @active.dup.each do |obj| # dup so we can release during iteration
      obj.update(dt)
      release(obj) if obj.should_destroy
    end
  end
end

# Particle example
class Particle
  attr_accessor :position, :velocity, :lifetime, :should_destroy

  def initialize
    @position = [0, 0, 0]
    @velocity = [0, 0, 0]
    @lifetime = 1.0
    @should_destroy = false
  end

  def update(dt)
    @position = @position.zip(@velocity).map { |p, v| p + v * dt }
    @lifetime -= dt
    @should_destroy = true if @lifetime <= 0
  end
end

pool = ObjectPool.new(initial_size: 10_000) { Particle.new }

def spawn_explosion(pool, pos)
  50.times do
    p = pool.acquire
    p.position = pos.map { |c| c + rand(-0.5..0.5) }
    p.velocity = Array.new(3) { rand(-5.0..5.0) }
    p.lifetime = 1.0
    p.should_destroy = false
  end
end

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);
    }
}
require "json"

class EntityFactory
  attr_reader :registry

  def initialize(registry, resources)
    @registry = registry
    @resources = resources
  end

  def create_enemy(enemy_type, pos)
    e = registry.create
    registry.emplace(e, Transform.new(pos))
    registry.emplace(e, Health.new(100, 100))
    registry.emplace(e, Sprite.new(@resources.texture("enemy_#{enemy_type}")))
    registry.emplace(e, AIController.new(load_behaviour_tree(enemy_type)))
    e
  end

  def create_player(pos, input_handler)
    e = registry.create
    registry.emplace(e, Transform.new(pos))
    registry.emplace(e, Health.new(100, 100))
    registry.emplace(e, Sprite.new(@resources.texture("player")))
    registry.emplace(e, PlayerControl.new(input_handler))
    e
  end
end

# Data-driven: load from JSON
def create_from_template(factory, template)
  e = factory.registry.create
  template.fetch("components", {}).each do |component_name, data|
    case component_name
    when "Transform"
      factory.registry.emplace(e, Transform.new(data["pos"]))
    when "Health"
      factory.registry.emplace(e, Health.new(data["current"], data["max"]))
      # ... other components
    end
  end
  e
end

# Level loading
def load_level(factory, level_path)
  level_data = JSON.parse(File.read(level_path))
  level_data.fetch("entities", []).each do |entity_template|
    create_from_template(factory, entity_template)
  end
end

Pattern Selection Guide

Problem Recommended Pattern
Input handling, replay, AI control Command
Complex entity composition ECS
AI/behaviour 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)