Race_fields/Assets/_Project/Docs/Architecture.md
2026-08-12 12:11:13 +03:00

770 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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, а не случайно искать друг друга по сцене.