Use this guide to learn how TowerClash is structured at runtime and in the codebase: dependency injection, game states, gameplay modules, managers, and how models, views, and controllers fit together. Read it when onboarding as a programmer, tracing flow from GameStartBehaviour through GameState, or changing shared services and module lifecycles.
Overview
TowerClash is a Unity tower defense/strategy game built with a modular, state-driven architecture using dependency injection and MVC-like patterns.
Core Architecture Patterns
- Dependency Injection (DI) System
- Custom DI framework via Context and Injector classes
- Service locator pattern with type-based registration
- Supports hierarchical contexts (parent-child relationships)
- Automatic dependency injection via [Inject] attributes
- All managers and services registered in the main Context at startup
Context.Install() and InstallByType()
- Install(objects) registers instances in the context map by their concrete type so Context.Get<T>() can resolve them.
- InstallByType(obj, baseType) registers one object under a base or interface type when multiple services share a shape or you need a stable lookup key.
- Nothing is wired into [Inject] fields yet; registration only fills the map.
Context.ApplyInstall()
- After you have installed the objects you need (Injector, managers, helpers), ApplyInstall() takes the Injector from the context and calls Injector.Inject() once on every object currently stored in the map.
- That pass assigns all private fields marked [Inject] on those objects from the map. Call it only when the dependencies for that pass are already installed.
Injector.Inject(target)
- Walks one object's private instance fields that have the [Inject] attribute and sets each field to context.Get(fieldType).
- Use it for objects created after ApplyInstall(), or for short-lived instances that are never added to the map.
Why Install and Inject are both needed
- Install answers "what exists in this context". Inject answers "fill this object's [Inject] fields from that context".
- ApplyInstall is a bulk Inject over everything registered so far (startup wiring). Per-object Inject handles things created later.
Examples from this project
- Startup (GameStartBehaviour): new Context(), Install(Injector, GameStateManager, EventManager, HudManager, ...), Install(MonoBehaviour components on the same GameObject), Install(Timer), then ApplyInstall(). After that, managers can use [Inject] for each other and for the timer.
- Gameplay modules (GameModulesState.AddModule): creates a module instance with Activator, then Injector.Inject(module) so the module's [Inject] fields (EventManager, GameConfig, etc.) resolve before module.Initialize().
- StateManager: StateManager is often created after ApplyInstall on a sub-context, so the controller calls Injector.Inject(stateManager) once; each SwitchToState then Inject(state) before state.Initialize() so UnitMoveState gets Timer and GameConfig.
- WeaponsModule: bullet controllers are created at runtime; Inject(bullet) hooks the new controller into the active context the same way as pre-registered objects.
- HudManager: when opening a screen, Inject(mediator) wires the new Mediator instance to shared services before it binds to its view.
- Entity sub-context (BuildingController, PathController, UnitController): new Context(parent) copies the parent's map, Install(this), InstallByType(this, typeof(BuildingController)) where needed, Install(new Injector(subContext)), ApplyInstall(). The entity and its Injector are registered; copied entries still resolve global services. Objects created after that pass (for example StateManager) receive an explicit Inject(stateManager).
- State Management
- Game State Machine: GameStateManager manages high-level game states:
- GameTrackingTransparencyState - Initial iOS tracking authorization
- GameInitializeState - Game initialization and config loading
- GameMenuState - Main menu with shop, battle selection
- GameLoadLevelState - Level loading
- GamePlayState - Active gameplay
- Entity State Machines: Individual entities (Structures, Paths, Units) use StateManager<T> for behavior states
- PathState - Path construction, finish construction, live states
- StructureState - Structure live state (unit generation)
- UnitState - Unit movement states (UnitMoveState)
- States handle initialization and cleanup lifecycle
- Observer Pattern
- Observable base class for model classes
- Models notify observers of changes via SetChanged()
- Used for UI updates when game data changes (e.g., GameModel, StructureModel)
Project Structure
Managers (Scripts/Managers/)
Core singleton services managing game systems:
- GameManager - Core gameplay logic, structure management
- HudManager - UI screen management (Mediator pattern)
- MenuManager - Menu state and navigation
- ResourcesManager - Resource management
- EventManager - Event system (ON_PATH_CREATED, ON_PATH_REMOVED, ON_STRUCTURE_CAPTURED)
- AdsManager - Advertisement integration
- IAPManager - In-app purchases
- TutorialManager - Tutorial system
- TouchManager - Input handling
- iOSAuthorizationTrackingManager - iOS privacy compliance
Modules (Scripts/Modules/)
Level-specific feature modules loaded during gameplay:
- StructuresModule - Manages all structures (towers, cannontowers)
- PathsModule - Manages paths between structures, handles bidirectional path visualization
- UnitsModule - Unit spawning and lifecycle management, continuous spawning along active paths
- EffectsModule - Spawns gameplay effects and owns camera-shake triggering when effects request it
- Modules are initialized per-level and disposed when level ends
Game Entities (MVC-like Pattern)
- Models: Data and business logic (StructureModel, TowerModel, GameModel)
- Views: Unity MonoBehaviour components (StructureView, PathView, UnitView)
- Controllers: Logic controllers (StructureController, PathController, UnitController, TowerController)
- Controllers coordinate between models and views, manage state machines
- PathController: Uses StructureController references (not StructureView), maintains path endpoints
- UnitController: Manages unit lifecycle, movement along paths, and arrival logic
UI Architecture (Scripts/UI/)
- Mediator Pattern: Each UI screen has a Mediator (logic) and View (presentation)
- HudManager manages screen lifecycle (show/hide)
- Supports single-screen and additional overlay screens
- Examples: GamePlayHudMediator, MenuButtonsHudMediator, ShopHudMediator, BattleHudMediator
Configuration System (Scripts/Config/)
- ScriptableObject-based configuration
- GameConfig - Main game configuration
- LevelConfig - Level-specific settings
- MenuHudConfig - UI configuration
- ShopProductConfig - Shop item definitions
- ResourceConfig - Resource definitions
- InventoryConfig - Inventory settings
Data Persistence (Scripts/Domain/)
- GameModel - Persistent game state (level, gems, cash, tutorial progress)
- JSON serialization via Newtonsoft.Json
- PlayerPrefs for local storage
- Observable pattern for change notifications
Entry Point
GameStartBehaviour (MonoBehaviour):
- Initializes DI context
- Registers all core managers
- Sets up game loop (Timer system)
- Starts state machine with GameTrackingTransparencyState
Key Design Decisions
- Modular Level System: Gameplay features loaded as modules per level, allowing clean initialization/disposal
- Hierarchical Contexts: Sub-contexts for entities (structures, paths) enable isolated dependency injection
- State-Driven Architecture: Both high-level game flow and entity behavior use state machines
- Separation of Concerns: Clear separation between data (Models), presentation (Views), and logic (Controllers/Managers)
- Platform Integration: Built-in support for ads, IAP, and iOS privacy compliance