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 1 Tick() call per system

  • Cache-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

Aspect
MonoBehaviour Pattern
System-Based Pattern

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 IGameSystem interface

  • Register/unregister entities dynamically

  • Process ALL entities in single Tick() call

  • Maintain 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?

  1. Movement First (Priority 0): All entities move to new positions. This ensures other systems see updated positions.

  2. Attack Second (Priority 1): Towers select targets based on updated positions. No stale data.

  3. Projectile Third (Priority 2): Projectiles move and check collision against updated positions.

  4. 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?

  1. Works with object pooling: OnEnable runs every time object is pulled from pool Start runs only ONCE per GameObject lifetime

  2. Symmetric lifecycle: OnEnable โ†” OnDisable are pairs Awake โ†” OnDestroy are pairs

  3. 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:

  1. โœ… Game runs without errors

  2. โœ… All entities move correctly

  3. โœ… Combat works as before

  4. โœ… Effects apply correctly

  5. โœ… 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

Metric
Before Refactor
After Refactor
Improvement

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

Scenario
Before
After

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

Version
Date
Changes

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