# Race Fields Architecture Этот документ фиксирует текущую архитектуру проекта по фактическим сценам, prefab'ам и скриптам. Главная цель: видеть не просто граф зависимостей, а слои проекта, ответственность каждого блока и места, где архитектура сейчас хрупкая. ## Как Читать Диаграммы Главная диаграмма читается сверху вниз: 1. `Entry / Scene Layer` - откуда стартует игра. 2. `Scene Bootstrap Layer` - кто собирает игровую сцену. 3. `Runtime Entity Layer` - реальные игровые объекты. 4. `Subsystem Layer` - системы, которые обслуживают player/enemies. 5. `Asset / Data Layer` - prefab'ы, sprites, render textures, static state. Связи на главной диаграмме показывают только важные runtime-зависимости. Детальные связи вынесены ниже в отдельные диаграммы. ## Current Architecture Overview ```mermaid flowchart TB subgraph L1["ENTRY / SCENE LAYER"] SelectionScene["Selection Scene"] DemoScene["Demo Scene"] end subgraph L2["SCENE BOOTSTRAP LAYER"] SelectionMenu["SelectionMenuController\nвыбор машины / режима / карты"] SelectedVehicleSpawner["SelectedVehicleSpawner\nсоздает player и биндует сцену"] end subgraph L3["RUNTIME ENTITY LAYER"] Player["Player Vehicle Instance\nBuggy prefab clone"] Harvester["Harvester Enemy\nHarvester prefab instance"] Terrain["Terrain\nTerrainCollider"] end subgraph L4["PLAYER SUBSYSTEMS"] VehicleControl["ArcadeBuggyController\ninput + physics + wheels + sound + effects"] WheelRig["WheelColliders / WheelVisuals"] DamageCore["VehicleDamageController\nмодули здоровья"] ImpactDamage["VehicleImpactDamageReceiver\nterrain / landing / collision damage"] DamageHitboxes["DamageHitbox\nзоны урона"] end subgraph L5["SCENE SUBSYSTEMS"] FollowCamera["BuggyFollowCamera"] RearViewCamera["RearViewMirrorCamera"] DamageHUD["VehicleDamageHud"] HarvesterAI["HarvesterChaseController"] BladeDamage["BladeDamageSource"] BladeVisual["HarvesterBladeSpinner"] end subgraph L6["ASSET / DATA LAYER"] GameSelectionState["GameSelectionState static\nвыбор между сценами"] VehiclePrefabs["Player Vehicle Prefabs\nBuggy / Buggy_1 / Buggy_Player"] HarvesterPrefab["HarvesterEnemy Prefab"] DamageUISprites["MDP_Buggy_1.psb\nHUD base + overlay sprites"] RearViewRT["RearViewMirror RenderTexture"] end SelectionScene --> SelectionMenu SelectionMenu --> GameSelectionState SelectionMenu --> DemoScene DemoScene --> SelectedVehicleSpawner DemoScene --> Terrain DemoScene --> Harvester SelectedVehicleSpawner --> GameSelectionState SelectedVehicleSpawner --> VehiclePrefabs SelectedVehicleSpawner --> Player SelectedVehicleSpawner --> FollowCamera SelectedVehicleSpawner --> RearViewCamera SelectedVehicleSpawner --> DamageHUD VehiclePrefabs --> Player Player --> VehicleControl Player --> WheelRig Player --> DamageCore Player --> ImpactDamage Player --> DamageHitboxes VehicleControl --> WheelRig ImpactDamage --> DamageCore ImpactDamage --> WheelRig DamageHitboxes --> DamageCore DamageCore --> DamageHUD DamageUISprites --> DamageHUD HarvesterPrefab --> Harvester Harvester --> HarvesterAI Harvester --> BladeVisual Harvester --> BladeDamage HarvesterAI --> Player BladeDamage --> DamageHitboxes FollowCamera --> Player RearViewCamera --> Player RearViewCamera --> RearViewRT ``` ## Main Runtime Flow Это основной путь от выбора машины до gameplay. ```mermaid sequenceDiagram participant PlayerInput as Player Input participant SelectionMenu as SelectionMenuController participant SelectionState as GameSelectionState participant UnityScene as Unity SceneManager participant DemoScene as Demo Scene participant Spawner as SelectedVehicleSpawner participant Vehicle as Player Vehicle participant Camera as Cameras participant HUD as VehicleDamageHud participant Enemy as Harvester PlayerInput->>SelectionMenu: left/right/space SelectionMenu->>SelectionMenu: choose vehicle, mode, map SelectionMenu->>SelectionState: SetSelection(vehiclePrefab, mode, sceneName) SelectionMenu->>UnityScene: LoadScene(sceneName) UnityScene->>DemoScene: load gameplay scene DemoScene->>Spawner: Start() Spawner->>SelectionState: read selected prefab Spawner->>Vehicle: Instantiate(prefab at spawn point) Spawner->>Camera: SetTarget(vehicle) Spawner->>HUD: Bind(vehicle.VehicleDamageController) Enemy->>Vehicle: auto find / chase target ``` Проблема этого flow: - `GameSelectionState` - static. - `SelectedVehicleSpawner` уже не просто spawner, а bootstrap сцены. - Камеры, HUD и враг частично умеют искать player сами, частично получают его от spawner. ## Scene Composition ### Selection Scene ```mermaid flowchart LR subgraph SelectionScene["Selection Scene"] MainCamera["Main Camera"] Light["Directional Light"] PreviewRoot["VehiclePreviewRoot"] PreviewFloor["Preview Floor"] Canvas["Selection Canvas"] EventSystem["EventSystem\nInputSystemUIInputModule"] Menu["SelectionMenuController"] end Menu --> PreviewRoot Menu --> Canvas Menu -->|"spawns preview prefab"| PreviewVehicle["Runtime Preview Vehicle"] Menu -->|"writes selected data"| GameSelectionState["GameSelectionState"] Menu -->|"loads selected map"| SceneManager["SceneManager.LoadScene"] ``` Что важно: - Preview использует настоящий gameplay prefab. - После spawn preview отключаются gameplay-компоненты. - Это быстро, но preview и gameplay связаны слишком плотно. ### Demo Scene ```mermaid flowchart LR subgraph DemoScene["Demo Scene"] Terrain["Terrain + TerrainCollider"] ExistingBuggy["Buggy_1 scene instance\nтестовый объект"] SpawnPoint["Selected Vehicle Spawn Point"] Spawner["SelectedVehicleSpawner"] FollowCamera["Buggy_ThirdPersonCamera"] RearCamera["RearViewMirrorCamera"] HudCanvas["Gameplay HUD Canvas"] Harvester["Harvester"] MachineGun["MachineGun"] end Spawner --> SpawnPoint Spawner -->|"destroys existing ArcadeBuggyController if enabled"| ExistingBuggy Spawner -->|"spawns selected prefab"| SpawnedPlayer["Buggy(Clone)"] Spawner --> FollowCamera Spawner --> RearCamera Spawner --> HudCanvas Harvester -->|"chases"| SpawnedPlayer ``` Что важно: - В сцене есть и тестовая машина `Buggy_1`, и spawner. - Spawner может удалить старую машину и создать выбранную. - Для финальной архитектуры лучше оставить один путь создания player. ## Vehicle Prefab Composition Все три player-prefab'а сейчас должны иметь одну и ту же структуру: - `Buggy.prefab` - `Buggy_1.prefab` - `Buggy_Player.prefab` ```mermaid flowchart TB subgraph VehicleRoot["PLAYER VEHICLE PREFAB ROOT"] Transform["Transform"] Rigidbody["Rigidbody"] BoxCollider["BoxCollider"] end subgraph Control["CONTROL / PHYSICS"] ArcadeBuggyController["ArcadeBuggyController"] BuggyWheelSetup["BuggyWheelSetup"] WheelColliders["WheelCollider_FL / FR / RL / RR"] WheelVisuals["Wheel visuals"] VehicleAudio["EngineSound / TireScreechSound"] VehicleVFX["Smoke / Skid Trails"] end subgraph Damage["DAMAGE"] VehicleDamageController["VehicleDamageController"] VehicleImpactDamageReceiver["VehicleImpactDamageReceiver"] DamageModules["DamageModules"] DamageHitboxes["DamageHitboxes"] end subgraph Modules["MODULES"] Body["Body Module"] Engine["Engine Module"] FL["FrontLeftWheel Module"] FR["FrontRightWheel Module"] RL["RearLeftWheel Module"] RR["RearRightWheel Module"] end subgraph Hitboxes["HITBOXES"] BodyBox["Body Hitbox"] EngineBox["Engine Hitbox"] FLBox["FrontLeftWheel Hitbox"] FRBox["FrontRightWheel Hitbox"] RLBox["RearLeftWheel Hitbox"] RRBox["RearRightWheel Hitbox"] end VehicleRoot --> Control VehicleRoot --> Damage ArcadeBuggyController --> WheelColliders ArcadeBuggyController --> WheelVisuals ArcadeBuggyController --> VehicleAudio ArcadeBuggyController --> VehicleVFX BuggyWheelSetup --> WheelColliders VehicleDamageController --> DamageModules DamageModules --> Modules DamageHitboxes --> Hitboxes DamageHitboxes --> VehicleDamageController VehicleImpactDamageReceiver --> VehicleDamageController VehicleImpactDamageReceiver --> ArcadeBuggyController FL -->|"destroyed"| ArcadeBuggyController FR -->|"destroyed"| ArcadeBuggyController RL -->|"destroyed"| ArcadeBuggyController RR -->|"destroyed"| ArcadeBuggyController ``` Ключевая проблема: `Damage` зависит от `ArcadeBuggyController`. Это работает только пока все машины устроены как багги. Для нормальной архитектуры нужен wheel adapter/interface. ## Damage Architecture ```mermaid flowchart TB subgraph DamageInput["DAMAGE INPUT SOURCES"] BladeDamageSource["BladeDamageSource\ntrigger damage"] ImpactReceiver["VehicleImpactDamageReceiver\nterrain / landing / collision"] FutureWeapons["Future Weapons\nbullets / mines / rockets"] end subgraph Routing["DAMAGE ROUTING"] DamageHitbox["DamageHitbox\nexternal hit -> module type"] DamageHit["DamageHit\namount / type / point / direction / source"] end subgraph State["DAMAGE STATE"] DamageController["VehicleDamageController"] ModuleBase["DamageableVehicleModule"] WheelModule["VehicleWheelDamageModule"] end subgraph Output["DAMAGE OUTPUT"] Hud["VehicleDamageHud"] WheelDetach["Wheel detach\nvisual + collider + rigidbody"] ControllerEffect["ArcadeBuggyController.SetWheelEnabled(false)"] end BladeDamageSource --> DamageHitbox FutureWeapons --> DamageHitbox DamageHitbox --> DamageHit ImpactReceiver --> DamageHit DamageHit --> DamageController DamageController --> ModuleBase ModuleBase --> WheelModule DamageController --> Hud WheelModule --> WheelDetach WheelModule --> ControllerEffect ``` ### Damage Rules Сейчас External damage: - `BladeDamageSource` ищет `DamageHitbox`. - `DamageHitbox` знает `VehicleModuleType`. - `VehicleDamageController` передает damage в конкретный module. Terrain / impact damage: - `VehicleImpactDamageReceiver` висит на root машины. - Считает collision damage через `OnCollisionEnter`. - Считает landing damage по каждому `WheelCollider`. - Отправляет damage напрямую в `VehicleDamageController`. HUD: - `VehicleDamageHud` слушает `VehicleDamageController.ModuleStateChanged`. - Красит overlay: - healthy - transparent; - damaged - red; - destroyed/detached - black. ## Enemy Architecture ```mermaid flowchart TB subgraph HarvesterPrefab["HARVESTER PREFAB"] HarvesterRoot["Root\nRigidbody + BoxCollider"] Chase["HarvesterChaseController"] HarvesterWheels["WheelColliders + WheelVisuals"] Blades["Blades"] Spinner["HarvesterBladeSpinner"] BladeDamage["BladeDamageSource"] end subgraph Target["TARGET"] PlayerVehicle["ArcadeBuggyController player"] PlayerHitboxes["Player DamageHitboxes"] end Chase --> HarvesterWheels Chase -->|"FindFirstObjectByType / target"| PlayerVehicle Blades --> Spinner Blades --> BladeDamage BladeDamage --> PlayerHitboxes ``` Проблема: - Harvester сам ищет player. - Нет `EnemyDirector`. - Нет единого `TargetProvider`. - При нескольких машинах враг может выбрать не ту. ## Camera And HUD Binding ```mermaid flowchart TB Spawner["SelectedVehicleSpawner"] Player["Spawned Player Vehicle"] subgraph CameraSystem["CAMERA SYSTEM"] FollowCamera["BuggyFollowCamera"] RearViewCamera["RearViewMirrorCamera"] RearViewRT["RearViewMirror RenderTexture"] RearViewImage["Rear View UI Image"] end subgraph HUDSystem["HUD SYSTEM"] DamageHud["VehicleDamageHud"] HudBase["DamageHUD_BaseLayers"] HudOverlays["DamageHUD_DamageOverlays"] end Spawner --> Player Spawner -->|"SetTarget"| FollowCamera Spawner -->|"SetTarget"| RearViewCamera RearViewCamera --> RearViewRT RearViewRT --> RearViewImage Player -->|"VehicleDamageController"| DamageHud DamageHud --> HudBase DamageHud --> HudOverlays ``` Проблема: - Камеры и HUD умеют искать player сами. - Spawner тоже задает им player. - Нужно выбрать один путь. Лучше: bind только через bootstrap. ## Current Dependency Table | System | Owns | Depends On | Problem | |---|---|---|---| | `SelectionMenuController` | UI выбора, preview, load scene | `GameSelectionState`, real vehicle prefab | Preview завязан на gameplay prefab | | `GameSelectionState` | выбранная машина/режим/карта | static global state | нет lifecycle, нет reset/session | | `SelectedVehicleSpawner` | spawn player, bind camera/HUD | `GameSelectionState`, cameras, HUD, damage | делает больше, чем spawner | | `ArcadeBuggyController` | input, drive, wheels, audio, VFX | wheel objects, audio, particles | слишком широкий класс | | `VehicleDamageController` | health modules | modules array | ок, но нужен profile/config | | `VehicleImpactDamageReceiver` | terrain/collision damage | `ArcadeBuggyController`, `VehicleDamageController` | damage зависит от конкретной машины | | `VehicleWheelDamageModule` | wheel destruction | `ArcadeBuggyController` | damage знает controller | | `VehicleDamageHud` | module damage UI | `VehicleDamageController`, scene UI sprites | mapping зашит в scene | | `HarvesterChaseController` | enemy movement | player search, wheel colliders | нет EnemyDirector | | `BladeDamageSource` | blade damage | `DamageHitbox` | нормальная связь для source damage | | `BuggyFollowCamera` | follow camera | player search or external target | два способа binding | | `RearViewMirrorCamera` | rear view | player search or external target | два способа binding | ## Architecture Problems To Fix Before Scaling ### 1. Нужен GameBootstrap Сейчас gameplay-сцена собирается через `SelectedVehicleSpawner`, но по факту он уже делает bootstrap. Нужно: ```text GameBootstrap -> read session request -> create player -> bind cameras -> bind HUD -> start enemies -> start game mode ``` ### 2. Нужен GameSession Сейчас состояние между сценами хранится в static `GameSelectionState`. Нужно: ```text GameSession selected vehicle selected map selected game mode player instance active enemies session state ``` ### 3. Нужен VehicleDefinition Сейчас selection хранит `GameObject prefab`. Лучше: ```text VehicleDefinition display name gameplay prefab preview settings damage profile HUD damage layout audio profile ``` ### 4. Нужен Wheel Rig Adapter Damage не должен знать `ArcadeBuggyController`. Нужно: ```csharp public interface IVehicleWheelRig { WheelCollider GetWheelCollider(WheelSlot slot); Transform GetWheelVisual(WheelSlot slot); void SetWheelEnabled(WheelSlot slot, bool enabled); bool IsWheelGrounded(WheelSlot slot); } ``` Тогда: - `VehicleImpactDamageReceiver` зависит от `IVehicleWheelRig`; - `VehicleWheelDamageModule` зависит от `IVehicleWheelRig`; - конкретная багги просто предоставляет реализацию. ### 5. Нужен Input Layer Сейчас `ArcadeBuggyController` сам читает keyboard. Нужно: ```text IPlayerInputSource Throttle Steering Handbrake KeyboardInputSource GamepadInputSource ArcadeCabinetInputSource AIInputSource ``` ### 6. Нужны Game Modes Сейчас есть enum: - `Arena` - `SurvivalRace` Но нет runtime-режимов. Нужно: ```text IGameMode ArenaMode SurvivalRaceMode ``` Game mode должен отвечать за: - правила старта; - enemy spawn rules; - win/lose; - scoring; - timer; - map-specific logic. ## Target Architecture ```mermaid flowchart TB subgraph Entry["ENTRY"] Selection["Selection Scene"] SessionRequest["GameSessionRequest"] end subgraph Data["DATA / DEFINITIONS"] VehicleDefinition["VehicleDefinition"] MapDefinition["MapDefinition"] GameModeDefinition["GameModeDefinition"] DamageProfile["VehicleDamageProfile"] HudDefinition["VehicleHudDefinition"] end subgraph Bootstrap["GAMEPLAY BOOTSTRAP"] GameBootstrap["GameBootstrap"] GameSession["GameSession"] VehicleFactory["VehicleFactory"] CameraBinder["CameraRigBinder"] HudBinder["HudBinder"] EnemyDirector["EnemyDirector"] GameModeRunner["GameModeRunner"] end subgraph Runtime["RUNTIME OBJECTS"] Player["Player Vehicle"] Enemies["Enemies"] World["Map / Terrain / Hazards"] end subgraph VehicleSystems["VEHICLE SYSTEMS"] InputSource["IPlayerInputSource"] VehicleController["IVehicleController"] WheelRig["IVehicleWheelRig"] DamageController["VehicleDamageController"] ImpactReceiver["VehicleImpactDamageReceiver"] VehicleAudio["VehicleAudioController"] VehicleVFX["VehicleEffectsController"] end Selection --> SessionRequest SessionRequest --> GameBootstrap GameBootstrap --> GameSession GameBootstrap --> VehicleFactory GameBootstrap --> CameraBinder GameBootstrap --> HudBinder GameBootstrap --> EnemyDirector GameBootstrap --> GameModeRunner GameSession --> VehicleDefinition GameSession --> MapDefinition GameSession --> GameModeDefinition VehicleDefinition --> DamageProfile VehicleDefinition --> HudDefinition VehicleFactory --> Player Player --> InputSource Player --> VehicleController Player --> WheelRig Player --> DamageController Player --> ImpactReceiver Player --> VehicleAudio Player --> VehicleVFX EnemyDirector --> Enemies GameModeRunner --> Enemies GameModeRunner --> World CameraBinder --> Player HudBinder --> Player HudBinder --> HudDefinition ``` ## Recommended Refactor Plan ### Step 1 - Rename / Split Spawner Current: ```text SelectedVehicleSpawner ``` Target: ```text GameBootstrap VehicleFactory CameraRigBinder HudBinder ``` Minimum first change: - оставить spawn в `SelectedVehicleSpawner`; - вынести camera/HUD binding в отдельные компоненты; - или переименовать `SelectedVehicleSpawner` в `GameBootstrap` и явно признать его ответственность. ### Step 2 - Replace Static Selection With Session Request Current: ```text GameSelectionState static ``` Target: ```text GameSessionRequest GameSession ``` ### Step 3 - Introduce VehicleDefinition Current: ```text SelectionMenuController.VehicleOption -> GameObject prefab ``` Target: ```text SelectionMenuController.VehicleOption -> VehicleDefinition VehicleDefinition -> gameplay prefab / preview / damage / HUD / audio ``` ### Step 4 - Decouple Damage From ArcadeBuggyController Current: ```text VehicleImpactDamageReceiver -> ArcadeBuggyController VehicleWheelDamageModule -> ArcadeBuggyController ``` Target: ```text VehicleImpactDamageReceiver -> IVehicleWheelRig VehicleWheelDamageModule -> IVehicleWheelRig ArcadeBuggyController or BuggyWheelRig -> IVehicleWheelRig ``` ### Step 5 - Split ArcadeBuggyController Current: ```text ArcadeBuggyController input physics wheel visuals audio VFX wheel API for damage ``` Target: ```text ArcadeBuggyController / VehicleMotor VehicleInputSource VehicleWheelVisuals VehicleAudioController VehicleEffectsController BuggyWheelRig ``` ## What Is Safe To Add Now Можно добавлять без сильного риска: - tuning параметров машины; - tuning damage thresholds; - улучшение HUD; - настройка harvester movement; - новые визуальные эффекты; - простые hazards, если они наносят урон через `DamageHitbox`. ## What Should Wait Лучше не добавлять до bootstrap/session/refactor: - оружие; - несколько видов enemies; - power-ups; - полноценные game modes; - scoring/results; - несколько player vehicles; - сохранение прогресса; - arcade cabinet input; - VR-specific gameplay. Причина: все это сейчас будет цепляться к static state, scene object search и прямым ссылкам. ## Summary Текущая архитектура рабочая для prototype, но главный риск уже виден: ```text Нет единого runtime-контекста. ``` Сейчас обязанности размазаны между: - `GameSelectionState`; - `SelectedVehicleSpawner`; - камерами, которые сами ищут player; - harvester, который сам ищет player; - HUD, который сам ищет damage controller. Нормальная точка сборки должна выглядеть так: ```text Selection Scene -> GameSessionRequest -> Demo Scene -> GameBootstrap -> GameSession -> Player / Cameras / HUD / Enemies / GameMode ``` После этого новые системы будут подключаться через session/bootstrap, а не случайно искать друг друга по сцене.