UnityTowerDefense/Project_Context.md

9.3 KiB
Raw Permalink Blame History

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.md for 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 remote origin). A legacy GitHub mirror exists as remote github.
  • Multiple contributors work in parallel on feature branches merged to main via PRs. Expect main to 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 static GetForClient(clientId) / Local registry. Current set: PlayerGoldManager, PlayerMatchState, PlayerBuffManager, PlayerTowerDeck, PlayerDraft.
  • Networked identifiers are catalog indices. TowerTypeId indexes TowerPlacementManager.towerDefinitions[]; DraftOptionId indexes DraftPool. 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: MainMenuLobby → 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 → PlayerSlot mapping.

Combat & towers

  • TowerCombat (server targeting loop; OnTargetAcquired/OnTargetLost/OnFire events for future animation), Projectile, hitscan + projectile paths, Single/Splash/Chain target types, damage types, range indicator. Air-targeting done: GroundedOnly towers skip flying enemies. Range checks are 3D.
  • TowerPlacementManager (server-validated placement queue), Builder build queue with staged construction/pause/cancel/refund, BuildSiteVisual ghosts, 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 by EnemyDefinition.FlightHeight.
  • Content: ~10 enemy definitions/prefabs (Crystal Golem / Cyclops / Ent variants, Undead Drake = the flying test enemy), 10 wave definitions.

Lobby, connection & races

  • MainMenu + Lobby scenes; Direct-IP via NetworkBootstrap (the single seam for the deferred Steam swap) + UnityTransport; LobbyService, SessionFlow; Quick Start dev shortcut.
  • Race data + selection UI: RaceDefinition, RaceRegistry, 16-slot RaceId, 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), WaveManager orchestration (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 unlocked TowerTypeIds. Starts at the base set (seeded by WaveManager from TowerPlacementManager.startingDeck); placement is gated server-side (TowerNotInDeck rejection); the HUD build grid reads the local deck and rebuilds live on change. Merged to main.
  • 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 branch feature/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.Levels authoring 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) and TowerRegistry.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.