ARCHITECTURE_HANDBOOK
Architecture Handbook
System-Based Design for Tower Defense Game
Version: 2.0 Last Updated: December 2025 Architecture Pattern: System-Based (ECS-inspired) Target Framework: Unity 2022.3 LTS
๐ Table of Contents
1. Introduction
1.1 Purpose of This Document
This Architecture Handbook serves as the authoritative reference for the tower defense game's system-based architecture. It documents:
Design philosophy and architectural decisions
Implementation patterns for core systems
Performance optimizations that achieved +125% FPS improvement
Best practices for maintaining and extending the codebase
Target Audience:
Senior/Lead developers maintaining the codebase
New team members onboarding to the project
Technical architects reviewing the system design
1.2 Project Context
Challenge: A procedurally generated tower defense game suffering from severe performance issues (63.9 FPS) due to monolithic architecture with 11,900+ Update() calls per second.
Solution: Complete architectural refactoring implementing system-based design inspired by Entity Component System (ECS) principles, without the complexity of Unity DOTS.
Results:
FPS: 63.9 โ 144.1 (+125%)
CPU Time: 15.6ms โ 6.9ms (-56%)
Update() calls: 11,900/sec โ 50/sec (-99.5%)
Maintainability: Significantly improved
Testability: 0% โ 75% coverage
1.3 Architectural Philosophy
Core Principles
1. Separation of Concerns
Entities = Data providers (implement interfaces)
Systems = Logic processors (batch operations)
Managers = High-level orchestration
2. Interface-Driven Design
All capabilities defined by interfaces (
IMoveable,IAttacker, etc.)Systems work with interfaces, not concrete classes
Easy to mock for testing
3. Batch Processing Over Individual Updates
Replace N
Update()calls with 1Tick()call per systemCache-friendly sequential iteration
Predictable, measurable performance
4. Priority-Based Execution
Systems execute in defined order (0 = Movement, 1 = Attack, etc.)
Prevents race conditions
Clear data flow
5. Lifecycle Management
Centralized initialization (GameSystemsManager)
Proper cleanup (OnEnable/OnDisable pattern)
No memory leaks
Design Goals
โ Performance: 100+ FPS sustained under heavy load โ Maintainability: Clear, documented, SOLID-compliant code โ Extensibility: Easy to add new systems, entities, behaviors โ Testability: Unit tests for all systems (75% coverage) โ Scalability: Support 200+ entities without FPS drop
2. Core Architectural Concepts
2.1 System-Based Architecture Overview
The Problem: Monolithic Update() Pattern
Traditional Unity MonoBehaviour pattern:
The Solution: System-Based Architecture
Our implementation:
Key Differences
Update Calls
N entities ร 60 FPS = 3,600/sec
1 system ร 60 FPS = 60/sec
Performance
โ Poor (high overhead)
โ Excellent (batch processing)
Testability
โ Difficult (tight coupling)
โ Easy (interface-based)
Profiling
โ Hard (scattered logic)
โ Easy (centralized methods)
Execution Order
โ Undefined
โ Explicit (priority-based)
Memory Access
โ Random (cache misses)
โ Sequential (cache-friendly)
2.2 The Three Pillars
Pillar 1: Entities (Data Providers)
Definition: GameObjects that hold data and implement capability interfaces.
Responsibilities:
Store state (health, position, speed, etc.)
Implement interfaces (
IMoveable,IAttacker,IDamageable)Provide data to systems
Register/unregister with systems in OnEnable/OnDisable
Example:
Key Points:
โ NO Update() method (logic handled by systems)
โ Implements multiple interfaces (composition over inheritance)
โ Registers with systems in OnEnable (works with object pooling)
โ Clean separation: data vs logic
Pillar 2: Systems (Logic Processors)
Definition: MonoBehaviour singletons that process batches of entities.
Responsibilities:
Implement
IGameSysteminterfaceRegister/unregister entities dynamically
Process ALL entities in single
Tick()callMaintain entity lists/dictionaries for fast lookup
Example:
Key Points:
โ Singleton managed by GameSystemsManager
โ Batch processes ALL entities in one loop
โ Priority defines execution order
โ Clean registration/unregistration API
Pillar 3: Manager (Orchestrator)
Definition: Master controller that initializes and executes systems.
Responsibilities:
Initialize all systems on scene load
Execute systems in priority order
Provide system lookup API
Handle system lifecycle
Example:
Key Points:
โ Single Update() call for entire game
โ Systems execute in priority order (sorted automatically)
โ Manages system lifecycle (Initialize โ Tick โ Shutdown)
โ Provides system lookup API
2.3 Data Flow
Execution Flow Diagram
Why This Order?
Movement First (Priority 0): All entities move to new positions. This ensures other systems see updated positions.
Attack Second (Priority 1): Towers select targets based on updated positions. No stale data.
Projectile Third (Priority 2): Projectiles move and check collision against updated positions.
Effects Last (Priority 3): Effects apply DOT/debuffs after all movement and combat resolved.
Critical: Priority order prevents race conditions and ensures predictable behavior.
2.4 Memory Layout
Cache-Friendly Iteration
Problem: Random memory access (cache misses)
Solution: Sequential iteration (cache hits)
Performance Impact:
3. Design Patterns
3.1 System Pattern (Custom ECS-like)
Intent
Centralize logic in reusable systems that batch process entities, eliminating scattered Update() calls.
Structure
Implementation
Benefits
โ Single Responsibility: Each system handles one concern โ Open/Closed Principle: Add new systems without modifying existing โ Easy Testing: Mock IGameSystem interface โ Performance: Batch processing, cache-friendly โ Predictable: Priority-based execution order
Applicability
Use when:
You have many entities performing similar operations
Performance is critical (batch processing needed)
You want centralized, testable logic
Don't use when:
Entities have completely unique behaviors (no batching benefit)
Overhead of registration outweighs batching benefits (<10 entities)
3.2 Observer Pattern (Event-Driven)
Intent
Decouple systems by using events for communication instead of direct references.
Structure
Implementation
Benefits
โ Loose Coupling: No direct references between systems โ Extensibility: Easy to add new listeners โ Testability: Can mock event handlers โ Clean Communication: Event names are self-documenting
Critical Notes
โ ๏ธ Always unsubscribe in OnDestroy to prevent memory leaks!
3.3 Object Pool Pattern
Intent
Reuse GameObjects instead of constantly creating/destroying to reduce GC pressure.
Structure
Implementation
Critical: OnEnable vs Start
โ ๏ธ Pooled objects reset in OnEnable(), NOT Start()!
Why OnEnable()?
Performance Impact
3.4 Strategy Pattern (Targeting)
Intent
Define family of algorithms (targeting strategies), encapsulate each, make them interchangeable.
Structure
Implementation
Benefits
โ Extensibility: Easy to add new targeting strategies โ Testability: Can test each strategy in isolation โ Performance: Manual iteration (NO LINQ) โ Flexibility: Tower can switch strategy at runtime
Performance Note
โ ๏ธ NEVER use LINQ in targeting strategies!
Why?
LINQ creates iterators (memory allocations)
LINQ uses delegates (virtual calls)
LINQ does full sort when only max needed
Impact:
3.5 Singleton Pattern (Managed)
Intent
Ensure class has only one instance, provide global access point.
Structure
Implementation
Benefits
โ Global Access: Any entity can access system โ Lazy Initialization: Created when first needed โ Single Instance: Guaranteed only one exists โ Managed Lifecycle: GameSystemsManager controls creation/destruction
Anti-Pattern Warning
โ ๏ธ Don't abuse singletons!
4. Interface System
4.1 Interface Hierarchy
4.2 System Interfaces
IGameSystem
Purpose: Contract for all game systems.
Usage:
4.3 Entity Interfaces
IMoveable
Purpose: Entities that can move (enemies, projectiles).
IAttacker
Purpose: Entities that can attack (towers).
ITargetable
Purpose: Entities that can be targeted by attackers.
IDamageable
Purpose: Entities that can take damage.
IProjectile
Purpose: Projectile entities.
IEffect
Purpose: Status effects (burn, slow, poison, stun).
4.4 Utility Interfaces
IPoolable
Purpose: Support for object pooling.
ITargetingStrategy
Purpose: Target selection algorithms.
4.5 Composition Example
Entity implementing multiple interfaces:
Benefits of Composition:
โ Enemy is moveable โ Enemy can take damage โ Enemy can be targeted โ Enemy supports pooling โ Single class, multiple capabilities โ Each system sees only relevant interface
5. System Implementations
5.1 MovementSystem
Purpose
Centralized batch processor for all moving entities (enemies, projectiles).
Responsibilities
Register/unregister IMoveable entities
Update positions for all entities in single pass
Handle waypoint progression
Notify entities on destination reached
Architecture
Full Implementation
Performance Metrics
Usage Example
5.2 AttackSystem
Purpose
Centralized combat processor handling targeting, cooldowns, and attack execution.
Responsibilities
Register/unregister IAttacker entities (towers)
Manage attack cooldowns
Target selection using strategies (First, Closest, Strongest, etc.)
Execute attacks (spawn projectiles)
Handle target loss/acquisition events
Architecture
Full Implementation
Performance Metrics
Targeting Strategy Example
Usage Example
5.3 ProjectileSystem
Purpose
Manages projectile lifecycle, movement, collision detection, and pooling.
Responsibilities
Register/unregister IProjectile entities
Update projectile positions
Check collision with targets
Handle projectile expiration (lifetime)
Return projectiles to pool
Architecture
Full Implementation
Performance Metrics
Usage Example
5.4 EffectSystem
Purpose
Centralized processor for status effects (burn, slow, poison, stun, etc.).
Responsibilities
Register/unregister IEffect entities
Update effect durations
Process effect ticks (DOT damage, debuffs)
Handle effect expiration
Manage effect stacking/overrides
Architecture
Full Implementation
Performance Metrics
Usage Example
6. Entity Design
6.1 Entity Principles
Entities Are Data Providers
Key Concept: Entities hold state and implement interfaces, but contain NO Update() logic.
Anti-Pattern:
Correct Pattern:
Registration Pattern
Always register in OnEnable, unregister in OnDisable:
Why OnEnable/OnDisable?
Works with object pooling: OnEnable runs every time object is pulled from pool Start runs only ONCE per GameObject lifetime
Symmetric lifecycle: OnEnable โ OnDisable are pairs Awake โ OnDestroy are pairs
Proper cleanup: OnDisable guaranteed to run before destruction OnDestroy might be too late
6.2 Enemy Entity
Complete Implementation
Enemy Variants
6.3 Tower Entity
Complete Implementation
Tower Variants
7. Performance Principles
7.1 Batch Processing
Core Concept
Replace N individual operations with 1 batch operation:
Why It's Faster
Reason 1: Reduced Function Call Overhead
Reason 2: Cache-Friendly Memory Access
Reason 3: Compiler Optimizations
Performance Impact
7.2 Cache Optimization
CPU Cache Hierarchy
Goal: Keep data in L1/L2 cache as much as possible.
Sequential vs Random Access
Data Locality
Principle: Keep related data together.
Array of Structures (AoS) vs Structure of Arrays (SoA)
Performance Impact:
7.3 Avoiding LINQ in Hot Paths
Why LINQ Is Slow
Reason 1: Memory Allocations
Reason 2: Virtual Calls & Delegates
Reason 3: Unnecessary Work
Manual Iteration Instead
Performance Comparison:
When LINQ Is Acceptable
Outside hot paths:
Rule of Thumb:
LINQ in initialization/setup: โ OK
LINQ in Update/Tick/FixedUpdate: โ NEVER
LINQ called <1 time/second: โ Probably OK
LINQ called >10 times/second: โ Replace with manual
7.4 Coroutines for Infrequent Tasks
Problem: FixedUpdate for Non-Physics Tasks
Issues:
FixedUpdate is for physics (rigidbody integration)
Target scanning doesn't need physics precision
Enemies don't move fast enough to require 50 scans/sec
Waste of CPU
Solution: Coroutine with Delay
Benefits:
90% reduction in calls (50 โ 5 per second)
No noticeable gameplay difference (targets update fast enough)
Simple to implement
Performance Impact
When to Use Coroutines
Good for:
Target scanning (5-10 times/sec sufficient)
Pathfinding requests (1-5 times/sec)
AI decision making (1-2 times/sec)
UI updates (not every frame)
Bad for:
Movement (needs every frame)
Combat resolution (must be immediate)
Physics interactions (use FixedUpdate)
Input handling (must be responsive)
Rule of Thumb:
If task needs to run every frame: Use system Tick()
If task can run 1-10 times/sec: Use coroutine
If task runs once: Use direct call
8. Best Practices
8.1 System Design Guidelines
1. Single Responsibility Principle
Each system handles ONE concern:
2. Clear Priority Order
Define explicit execution order:
3. Batch Processing Always
Process ALL entities in single pass:
4. Interface-Based Design
Work with interfaces, not concrete classes:
8.2 Entity Design Guidelines
1. No Update() in Entities
Entities provide data, systems provide logic:
2. OnEnable/OnDisable Registration
Always use OnEnable/OnDisable, not Start/OnDestroy:
3. Interface Composition
Implement multiple interfaces for multiple capabilities:
4. Minimal Public API
Expose only what systems need:
8.3 Performance Guidelines
1. Profile Before Optimizing
Always measure:
Don't optimize blindly!
2. Avoid Premature Optimization
3. Cache Component References
4. Use Object Pooling
5. Avoid Allocations in Hot Paths
8.4 Testing Guidelines
1. Unit Test Systems in Isolation
2. Integration Test System Interactions
3. Performance Test with Profiler
9. Migration Guide
9.1 From Monolithic to System-Based
Step 1: Identify Systems
Analyze your codebase:
Step 2: Create Interfaces
Define capability contracts:
Step 3: Implement Systems
Create system classes:
Step 4: Refactor Entities
Remove Update(), implement interfaces:
Step 5: Create GameSystemsManager
Orchestrate all systems:
Step 6: Test & Validate
Verify functionality:
โ Game runs without errors
โ All entities move correctly
โ Combat works as before
โ Effects apply correctly
โ Performance improved (measure FPS)
9.2 Common Migration Pitfalls
Pitfall 1: Forgetting Unregistration
Pitfall 2: Using Start Instead of OnEnable
Pitfall 3: Wrong System Priority
Pitfall 4: Keeping Update() Methods
10. Appendix
10.1 Glossary
Batch Processing: Processing multiple entities in single loop instead of individual Update() calls.
Cache Locality: Keeping frequently accessed data close together in memory for faster access.
ECS (Entity Component System): Architecture pattern separating data (Entity/Component) from logic (System).
Hot Path: Code that executes frequently (every frame). Must be highly optimized.
Interface: Contract defining capabilities an entity must provide.
Pooling: Reusing GameObjects instead of destroying/instantiating to reduce overhead.
System: Centralized logic processor that batch processes entities.
Tick(): Main update method for systems (replaces Update()).
10.2 Performance Benchmarks
Test Environment
Hardware: RTX 4060, Intel i7-12700, 32GB RAM
Unity Version: 2022.3.12f1
Build: Release (not Editor)
VSync: Disabled
Quality: Medium
Results
FPS (Wave 7)
63.9
144.1
+125%
CPU Time
15.6ms
6.9ms
-56%
Update() Calls
11,900/sec
50/sec
-99.5%
Batches
7,277
1,917
-74%
Shadow Casters
9,125
83
-99%
GC Allocations
150 KB/sec
5 KB/sec
-97%
Load Testing
50 enemies
80 FPS
144 FPS
100 enemies
45 FPS
120 FPS
200 enemies
20 FPS (unplayable)
80 FPS
10.3 References
Unity Documentation
Design Patterns
Performance Optimization
๐ Document Revision History
1.0
December 2024
Initial release
2.0
December 2025
Complete rewrite with Markdown format, expanded examples, performance benchmarks
This Architecture Handbook is a living document. Contributions and feedback welcome.
Maintainer: Senior Unity Developer Last Review: December 2025 Next Review: June 2026
Last updated