This repository showcases the core design patterns I’ve learned.
In this adventure, I followed Robert Nystrom’s Game Programming Patterns book through small, hands-on Unity projects.
In each scene, you’ll find example code that illustrates a pattern (such as Command, Component, Object Pool, etc.) both in theory and in practice.
My goal is to show how these patterns can be integrated into games and to share the best practices I discovered along the way.
This repo is open to anyone looking for a reference or inspiration when learning design patterns.
You can also explore the "Sources & Assets" section for detailed information on the sources and assets I used.
You can expand the dropdown menu by clicking on the relevant heading in the list below to get detailed information about the project. ⬇️
Command Pattern – Maze Replay & Undo/Redo System
This Unity project demonstrates the Command Design Pattern through an interactive maze game.
The player controls a red cube and must navigate it across valid tiles to reach the star.
Every movement is stored as a command, enabling two key features:
- Undo: Reverse the last move.
- Redo: Reapply a previously undone move.
When the player reaches the star, the entire move history is automatically replayed, showcasing how the Command Pattern can store, reverse, and re-execute actions.
🎥 Demo:
CommandPattern.mov
- Movement control using the Command Pattern.
- Undo and Redo functionality for player moves.
- Automatic replay of all moves upon reaching the goal.
- Clear example of decoupling input handling from execution logic.
- Input Handling – Player input is translated into movement commands.
- Command Execution – The player cube moves according to the executed command.
- History Tracking – Commands are stored in a stack for undo/redo operations.
- Replay – When the star is reached, commands are executed in sequence to replay the path.
Flyweight Pattern – Performance Comparison
This Unity project demonstrates the Flyweight Design Pattern by comparing two versions of a simple carrot spawning system:
- Non-Flyweight Version – Each object holds its own unique data, resulting in higher memory usage and draw calls.
- Flyweight Version – Shared intrinsic data between objects reduces memory usage and improves rendering performance.
The purpose of this project is to show how applying the Flyweight Pattern can optimize memory consumption and batch rendering in Unity.
- Flyweight Pattern is highly effective for scenarios where many similar objects share common data.
- This optimization is particularly useful for games with large numbers of repeated objects, such as bullets, tiles, or vegetation.
Observer Pattern – Event-Driven Achievements & HUD
This Unity scene applies the Observer Design Pattern to keep achievements and HUD updates decoupled from the event producers.
When the player collects carrots/cauliflowers, jumps 12 times, or checks the mailbox, the relevant achievement icon switches from grayscale to colored, and the HUD counters update in real time.
Progress persists visually even if the UI panel was closed; once opened, it reflects the correct state immediately thanks to a lightweight “replay on subscribe” mechanism.
🎥 Demo:
ObserverPatternDemo.mov
- Loose coupling: Producers (Subjects) and listeners (Observers) are independent.
- Achievements:
- Collect 9 carrots
- Collect 9 cauliflowers
- Jump 12 times
- Check the mailbox (press E near it)
- HUD: Carrot/cauliflower counters update instantly.
- Visual state: Start with grayscale sprites, switch to colored on completion.
- Replay on subscribe: New listeners receive the current state right away.
- Minimal core:
ISubject<T>/IObserver<T>only; no event bus, no third-party libs.
- Subjects
JumpSubject→ increments and notifies on each successful jump.CollectSubject→ tracks carrot/cauliflower counts and notifies.MailboxSubject→ one-time mailbox check, then notifies.
- Observers
HUDCounter→ updates HUD texts.AchievementIcon_CollectThreshold→ unlocks at 9/9 for the configured item type.AchievementIcon_JumpThreshold→ unlocks at 12 jumps.AchievementIcon_Mailbox→ unlocks on mailbox check.
- Replay
Each Subject replays its current state to new subscribers so the UI shows correct progress even if the panel was previously inactive.
Prototype Pattern – Basic Dungeon Enemy Spawner (Data Clone + Prefab Clone)
This demo shows the Prototype Pattern in two layers:
- Data Prototype (ScriptableObject):
EnemyData.Clone()creates a deep copy of enemy stats. - Prefab Prototype:
Instantiate(prefab)creates scene copies of the enemy object.
🎥 Demo:
PrototypePattern.mov
EnemySpawnerclones theBaseEnemydata prototype.WaveModifierapplies wave-based changes (HP, speed, color).- The prefab is instantiated, and
Enemy.Init(data)injects the cloned values. EnemyMoveuses the speed value to move the enemy towards theGoal.
- Open the project → load the
Scenes/Prototypescene → press Play. - The top UI shows the current wave and total spawned enemies.
- Prefab = practical prototype clone for objects.
- ScriptableObject = data prototype.
- Deep copy prevents runtime changes from affecting the original asset.
Singleton & Service Locator – Minimal Logging
This scene implements the same logging task with two patterns:
- Singleton:
SingletonLoggerexposes a single global instance. - Service Locator:
ILogis resolved throughServices.Logwithout coupling to a concrete class.
UI: one Text element, two buttons
- Singleton button → writes via the Singleton path
- Locator button → writes via the Service Locator path
- On scene load,
Installer.Awake()registers a provider:Services.Provide(new MemoryLog()). - Press Singleton:
SingletonLogger.Instance.Write("...")→logText.text = SingletonLogger.Instance.ReadAll(). - Press Locator:
Services.Log.Write("...")→logText.text = Services.Log.ReadAll(). - If no provider is registered,
Services.Logfalls back to NullLog (no-op), so calls are safely ignored.
State Pattern – Enemy AI
This project demonstrates a grid-based enemy AI built with the State Pattern.
Each state inherits from a shared base class (EnemyState) and follows a lifecycle of Enter, Exit, UpdateState, and DecideDir.
🎥 Demo:
StatePattern.mov
- Wander: Picks a random passable direction (avoids turning back if possible).
- Chase: Moves toward the player, minimizing Manhattan distance.
- Frightened: Moves away from the player. When entered, the enemy turns cyan. When the timer ends, the state is popped off the stack.
- The enemy starts in Wander.
- After each tile step, the distance to the player is checked:
dist ≤ chaseRangeCells→ Chasedist > chaseRangeCells→ Wander
- If Frightened is active, automatic transitions are ignored.
- Collecting a big score pushes Frightened; when time is up, it pops.
- The active state decides direction using
DecideDir. TryStartStepchecks collisions; if clear, the enemy starts moving to the target tile.FixedUpdatemoves the enemy until it reaches the tile, then ends the step.
This project focuses on demonstrating the State Pattern itself.
Pathfinding algorithms (e.g., A*, BFS, DFS) were intentionally left out to keep the focus clear, but they can be added later to achieve more advanced chasing and evasion behavior.
Decorator Pattern – Dynamic Spell Modification with ScriptableObjects
This Unity project demonstrates the Decorator Design Pattern by creating a dynamic spell system where a base spell’s properties can be altered at runtime.
The system uses ScriptableObjects to define a base spell (SpellDefinitionSO) and a series of modifications (SpellModSO). These modifications act as decorators, wrapping the base spell to change its Damage and ManaCost without altering its core class. The UI updates in real-time to reflect the final stats of the decorated spell.
🎥 Demo:
DecoratorPattern.mov
Dynamic Modification: Add functionalities like damage boosts or mana discounts to a spell at runtime.
Data-Driven Design: Base spells and modifications are managed as ScriptableObject assets, allowing for easy configuration and reuse.
Loose Coupling: The base spell (BasicSpell) is completely unaware of the decorators (DamageBoost, ManaDiscount) that wrap it.
Real-Time UI Feedback: The UI instantly reflects the combined effects of all applied decorators, showing the final stats and highlighting changes.
Base Object: A SpellDefinitionSO creates the initial ISpell object (a BasicSpell) with default stats.
Decorators as Assets: Each modification, like DamageBoostSO or ManaDiscountSO, is a ScriptableObject that knows how to "wrap" an existing ISpell object with its corresponding decorator class.
Runtime Wrapping: The CardPresenter class builds the final spell by starting with the base spell and sequentially wrapping it with decorators based on UI interactions (e.g., button toggles).
Property Delegation: When a property like Damage is accessed on the final object, the call is delegated down the chain. Each decorator modifies the result from the object it wraps before passing it back up.
UI Update: The CardPresenter calculates the final stats from the fully decorated spell object and updates the CardViewUnity to display the results, providing immediate visual feedback.
Object Pool Pattern – Carrot Spawner
This Unity project demonstrates the Object Pool Design Pattern for performance optimization within a carrot spawning system.
Creating (Instantiate) and destroying (Destroy) objects repeatedly, especially in quick succession, can be a performance bottleneck and lead to hitches. The Object Pool pattern eliminates these costly operations.
In this project, instead of being continuously destroyed and recreated, carrots are stored in a pool. They are then retrieved and reused as needed. When a carrot goes off-screen or enters a designated zone, it is not destroyed but is simply returned to the pool for later use.
🎥 Demo:
ObjectPool.mov
Pool Initialization: When the game starts, the CarrotPool class pre-creates a specific number of carrot objects and stores them in a pool.
Object Retrieval (Get): When the CarrotSpawner needs to launch a new carrot, it retrieves an available object from the pool. This process is much faster than Instantiate.
Object Release (Release): When a carrot becomes invisible or collides with the KillZone, it signals the pool to be returned, instead of being destroyed.
Reusability: Once a carrot is returned to the pool, it is ready to be reused for the next spawn.
Performance: It avoids expensive Instantiate and Destroy operations, which can increase frame rate (FPS) and prevent hitches.
Memory Management: It reduces memory allocation, leading to less frequent garbage collection.
Flexibility: It provides greater control over the objects' lifecycle.
Strategy Pattern – Dynamic Character Movement
This Unity project demonstrates the Strategy Design Pattern by allowing a character's movement behavior to be changed dynamically at runtime.
The project showcases how different movement behaviors (walking, running, and zigzagging) can be encapsulated into separate classes, making the character's core movement logic independent of the specific movement strategy being used. This approach provides great flexibility and a clean architecture.
By using the UI buttons, you can instantly change how the character moves.
🎥 Demo:
StrategyPattern.mov
Movement Interface (IMovementStrategy): All movement behaviors (Walk, Run, ZigZag) conform to this interface, ensuring they all have a common UpdatePosition method.
WalkStrategy: Implements slow, steady movement.
RunStrategy: Implements fast, linear movement.
ZigZagStrategy: Implements sinusoidal movement for a wavy path.
Character Controller (Mover): This main class holds a reference to the current movement strategy. It doesn't know the specifics of how to move, it simply calls the UpdatePosition method of the active strategy.
UI Integration: When a button is clicked, it tells the Mover to switch to a new movement strategy instance, changing the character's behavior immediately.
Flexibility: Easily add new movement behaviors without modifying the main character's code.
Maintainability: The code is more organized and easier to manage, as each behavior is isolated in its own class.
Scalability: Allows for a clean and efficient way to handle multiple behaviors for different character types or game states.
Factory Pattern – Data-Driven Product Spawner
This Unity project demonstrates a Factory-Method-flavored Template Method approach: an abstract base factory standardizes core creation steps (instantiate the prefab, obtain the IProduct component, call Initialize()), while concrete factories customize the final product via an override hook.
Note: The base type here is an abstract base class, not the “Abstract Factory” design pattern.
🎥 Demo:
FactoryPattern.mov
Factory Hierarchy: A single abstract base (ProductFactoryBaseSO) encapsulates the common creation pipeline; specialized factories (e.g., RedFactorySO, GreenFactorySO) supply variant-specific behavior.
Customization via Inheritance: Each concrete factory overrides a customization hook (ApplyVariant) to assign its unique color or other properties to the product.
Open/Closed Principle (OCP): Adding a new variation (e.g., YellowFactorySO) requires no changes to existing code in ProductFactoryBaseSO or SpawnerController. You simply add another subclass.
High Decoupling: Client code (SpawnerController) only depends on the abstract factory type and calls Create(...). It never needs to know which color or variant is being applied.
Request: SpawnerController detects a click, selects a concrete factory (random or chosen) from a list, and invokes its Create(...) method.
Template Execution: Inside the base class (ProductFactoryBaseSO), Create(...) performs the standardized steps: a) Instantiate the configured prefab b) Retrieve the IProduct component c) Invoke Initialize()
Customization Hook: As the last step, Create(...) calls the abstract ApplyVariant(product) method.
Concrete Behavior: The selected concrete factory (e.g., RedFactorySO) implements ApplyVariant and applies its dedicated color to the product.
Extensibility: New product variations arrive as new subclasses; the creation pipeline remains stable.
Clear Responsibility: The base class owns “how to create,” subclasses own “how to customize.”
Maintainability: The structure aligns with OCP and keeps client code simple, predictable, and testable.
Model-View-Presenter (MVP) Pattern – Health Management System
This Unity project demonstrates the Model-View-Presenter (MVP) Design Pattern using a character's health (HP) management system. MVP ensures a strict separation of business logic, data, and visualization, especially for in-game user interfaces and data displays.
This demo clearly shows how the core health bar and text display, along with button-triggered data updates, can be made completely independent of the complex game logic.
🎥 Demo:
MVP.mov
- Loose Coupling: The View (Unity UI components) does not directly depend on the Model (health data).
- Testability: The Model and Presenter are pure C# classes and can be unit-tested without requiring Unity.
- Presenter: Handles user input (button clicks), updates the Model, and refreshes the View.
- Model: Stores health values (current, max) and enforces rules like taking damage or healing. Notifies the Presenter on change events.
- View (HealthView): User clicks the Damage button.
- Presenter (HealthPresenter): Receives the event, calls
Model.Damage(step). - Model (HealthModel): Updates HP and raises an
OnChangedevent. - Presenter: Reacts to the event, formats the values, and updates the View.
- View: Displays the new health bar fill and text.
- Clean Architecture: Each layer has a single responsibility.
- Ease of Change: The UI can be redesigned without touching the Model or Presenter.
- Data Safety: The business logic (Model) stays protected from Unity-specific side effects.
You can find all the sources and assets I used in this Word document:
Sources & Assets



