9.3 KiB
9.3 KiB
Unity Tower Defense — Project Context
Purpose
A snapshot of where the project is and how it works — the authoritative reference for current architecture, implemented systems, conventions, and known debt. It pairs with Project_Roadmap.md, which is the forward-looking plan. When the two disagree, this document describes what exists today; the roadmap describes what's planned next.
Last substantial update: 2026-06-23.
Game overview
A co-op tower-defense / roguelike hybrid for up to 9 players.
- Maze defense (Wintermaul-style): players build towers to force enemies along a longer path through their own zone. Lives are a shared pool; gold is per-player.
- Roguelike layer (now core to the design): every player starts with the same three towers and builds out a personal "deck"/"build" through a 3-option draft presented at match start and after every wave. Choices span new towers, systemic upgrades, builder abilities, enemy debuffs, and relic quests. See
Project_Roadmap.mdfor the full design. - Target platform: Steam (Windows / Linux / Steam Deck).
- Visual direction (aspirational): "painted tabletop miniature" look with Spider-Verse-style stepped (on-2s) enemy animation. Current visuals are placeholder (primitive meshes / cones, sourced creature models).
Engine & tech
Unity 6.4 (6000.4.4f1), URP, IL2CPP, .NET Standard 2.1, Linear color space, new Input System, Force Text serialization. Netcode: Netcode for GameObjects (NGO) 2.x.
Repository & collaboration
- Self-hosted Forgejo:
https://git.marlboro-bc.duckdns.org/yeahweregames/UnityTowerDefense(git remoteorigin). A legacy GitHub mirror exists as remotegithub. - Multiple contributors work in parallel on feature branches merged to
mainvia PRs. Expectmainto move between sessions; rebase/branch off the latest. - IP guardrail: the prototype uses Games-Workshop-adjacent placeholder content (race names, sourced models). The repo stays private, no public builds/demos, until that content is replaced. Maintain a plain-text asset manifest of IP-derived assets to swap before any public release.
Architecture & conventions
- Server-authoritative gameplay; local-only UI/visual state. Only gameplay-meaningful state is networked. Selection, placement ghosts, animation, and the paint cursor are client-local.
- Data-driven via ScriptableObjects:
TowerDefinition,EnemyDefinition,WaveDefinition,RaceDefinition,GoldConfig,BuffDefinition/BuffCategory,DraftOption(+NewTowerDraftOption). Designers tune stats in assets, not code. - Per-player state pattern:
NetworkBehaviours on the Player prefab with a staticGetForClient(clientId)/Localregistry. Current set:PlayerGoldManager,PlayerMatchState,PlayerBuffManager,PlayerTowerDeck,PlayerDraft. - Networked identifiers are catalog indices.
TowerTypeIdindexesTowerPlacementManager.towerDefinitions[];DraftOptionIdindexesDraftPool. Stable within a match, not across sessions — cross-match persistence will need stable IDs (asset GUID or a serialized StableId). - Namespaces:
TD.Core(enums, palettes, grid math),TD.Gameplay(builder, placement, match/wave/pathfinding state, economy, enemies, deck),TD.Gameplay.Draft(draft system),TD.Combat(TowerCombat/Projectile),TD.Levels(in-engine authoring + bake),TD.UI,TD.Net. - Scenes:
MainMenu→Lobby→ a Match level (9Player,Main).
Engineering principles (carried across sessions)
- Debug to root cause. No defensive workarounds or per-frame state-correcting hacks — find and fix the actual cause.
- Restate design intent before coding. Confirmed valuable repeatedly; prevents rework.
- Server owns gameplay; clients render. Authoritative state on the server, visualization derived locally on each peer.
Implemented systems
Match foundation
MatchState(NetworkBehaviour scene singleton): phase (Lobby/CountDown/Playing/Victory/Defeat), current wave, shared lives.PlayerMatchState(per-player): slot allocation, race selection, ready state. Authoritative client-id →PlayerSlotmapping.
Combat & towers
TowerCombat(server targeting loop;OnTargetAcquired/OnTargetLost/OnFireevents for future animation),Projectile, hitscan + projectile paths, Single/Splash/Chain target types, damage types, range indicator. Air-targeting done:GroundedOnlytowers skip flying enemies. Range checks are 3D.TowerPlacementManager(server-validated placement queue),Builderbuild queue with staged construction/pause/cancel/refund,BuildSiteVisualghosts, owner tinting, RTS selection.
Enemies & pathfinding
EnemyHealth(replicated HP, damage types,IsFlying, held state),EnemyStatus(lingering effects: slow/DoT),EnemyMovement(A* path following, zone-leak attribution, death/sink sequence).PathfindingService(A* on the runtime walkability grid, octile heuristic, corner-cut prevention, line-of-sight path smoothing; re-paths on tower placement/removal).- Flying enemies: path on the baked terrain grid (
LevelLoader.IsBaseWalkable) so they soar over towers; compute once, never re-path; spawn elevated byEnemyDefinition.FlightHeight. - Content: ~10 enemy definitions/prefabs (Crystal Golem / Cyclops / Ent variants, Undead Drake = the flying test enemy), 10 wave definitions.
Lobby, connection & races
MainMenu+Lobbyscenes; Direct-IP viaNetworkBootstrap(the single seam for the deferred Steam swap) +UnityTransport;LobbyService,SessionFlow; Quick Start dev shortcut.- Race data + selection UI:
RaceDefinition,RaceRegistry, 16-slotRaceId,RaceSelectionOverlay. Two placeholder races (both use the default builder). Note: "Race" is the code term; the roguelike design calls this concept "Builder" — naming reconciliation is an open decision.
Economy
PlayerGoldManager(per-player, server-validated spend/award),GoldConfig(starting gold, per-wave kill rewards, completion + no-leak bonuses),WaveManagerorchestration (prep countdown, per-zone spawning, kill-gold attribution, lives pool, Victory/Defeat).
Roguelike — tower deck & draft (the current focus)
PlayerTowerDeck(per-player): the growable set of unlockedTowerTypeIds. Starts at the base set (seeded byWaveManagerfromTowerPlacementManager.startingDeck); placement is gated server-side (TowerNotInDeckrejection); the HUD build grid reads the local deck and rebuilds live on change. Merged tomain.- Draft system (Slice 1 — spine + "new tower"):
DraftOption(abstract SO) /NewTowerDraftOption,DraftPool(scene singleton catalog),PlayerDraft(per-player offered set + pick/buy-roll RPCs),DraftService(server weighted-random generation + offer/auto-resolve). The prep phase is the draft window (wave 1's prep = the match-start draft); unpicked drafts auto-resolve at prep end. Gold "Buy Roll" purchases an extra roll any time. Non-modal HUD overlay. On branchfeature/draft-system, verified working in-engine; pending commit/merge as of session end. - Three base towers exist as data: Basic Arrow (ground+air single-target), Siege Cannon (ground-only splash), Wall (2×2, no damage, maze-shaping). (Coworkers have begun replacing the placeholder cube visuals with cones.)
Paint system (PAUSED)
- In-match paint (R/G/B + Reset) recolors owned towers and drives effects (Red=Splash, Green=Poison, Blue=Cold), server-authoritative. Frozen pending the roguelike reconciliation decision (it overlaps with systemic damage-type upgrades).
Level authoring
- In-engine
TD.Levelsauthoring volumes (player zones, spawners, leak exits, goals) +LevelData+ a bake pipeline producing walkability/placement/owner grids.
HUD & dev tools
HUDController(UI Toolkit): gold/wave/lives/leaks, scoreboard, minimap, selection portrait + context panel, build/paint command grid, build progress, match-end overlay, chat, buff menu, and the new draft overlay.DevWaveControls: F9 / "Force Next Wave", F8 / "Grant Next Tower" (dev stand-in for deck growth).
Known placeholders & technical debt
- Visuals are placeholder throughout (primitive/cone towers, sourced enemy models). See roadmap art TODOs.
- Enemies lack idle animation support — needs adding across all enemy prefabs.
- Tower footprint visuals don't fill their 2×2 space — the cone/mesh sits centered and reads smaller than the tile area it occupies; should visually reflect the full footprint.
- Two redundant tower lists:
TowerPlacementManager.towerDefinitions[](by-index catalog) andTowerRegistry.definitions[](by-name, for instance resolution). Candidate for consolidation. - Catalog index 0 is a reserved sentinel (valid
TowerTypeIds start at 1) — easy to forget when wiring the catalog in the inspector. - Catalog-index identifiers aren't session-stable — blocks cross-match persistence until a stable ID is added.
- Paint system frozen; Race vs Builder naming unresolved; the gold "Buy Roll available any time" rule is provisional and may change.
- Stubbed/unbuilt: tower Upgrade/Sell actions (HUD buttons disabled), enemy resistances/weaknesses, in-match race-pick countdown.