# Architektura projektu ## 1. Cel i zasada zależności Architektura ma być prosta do rozwijania, modularna, testowalna i zorganizowana wokół feature'ów, a nie globalnych warstw technicznych. Kierunek zależności jest zawsze taki sam: ```text Godot -> Application -> Domain ``` ```text Domain -> Godot DOBRZE (domena nigdy nie zna silnika) Godot -> Domain ŹLE (silnik wywołuje domenę) ``` --- ## 2. Struktura katalogów ```text src/ ├── Features/ │ ├── Player/ │ │ ├── Contracts/ │ │ ├── Domain/ │ │ ├── Application/ │ │ └── Godot/ │ └── ... │ ├── Shared/ │ ├── Domain/ │ └── Events/ │ ├── Contracts/ │ └── Infrastructure/ │ ├── Bootstrap/ │ └── Scenes/ ``` Nie każdy feature potrzebuje wszystkich czterech folderów. Mały feature może mieć tylko np. `Domain/` + `Godot/`. Foldery dokłada się dopiero, gdy pojawia się realna potrzeba. --- ## 3. Warstwy architektury ### 3.1 `Domain` Właściwa logika i reguły gry. Zwykłe klasy C#, bez żadnej wiedzy o silniku. Wrzucamy tam encje, value obiekty, reguły, inwarianty, obliczenia (`Health`, `PlayerData`, `Norma`, `Zgloszenie`). Unikamy `Node`, sceny, input, UI, zapis do pliku - `Domain` o tym nie wie. Wyjątkiem są matematyczne value type'y Godota (`Vector2`, `Vector3`, `Mathf`) - one są dozwolone. ```csharp namespace Game.Features.Player.Domain; public sealed class Health { public int Current { get; private set; } public int Max { get; } public bool IsDead => Current <= 0; public Health(int max) { Max = max; Current = max; } public void TakeDamage(int amount) { Current = Math.Max(0, Current - amount); } } ``` Nazewnictwo feature to zazwyczaj encja w liczbie mnogiej (`Players`, `Enemies` itd.). Gdy liczba mnoga nie brzmi naturalnie, nadajemy klasie nazwę opisującą jej rolę zamiast powtarzać nazwę feature'a - np. feature `Terminal` trzyma klasę `Report`, (zamiast `Terminal.Terminal`). --- ### 3.2 `Application` Koordynacja przypadków użycia (use case'ów) - logika, która dotyka kilku obiektów domenowych, korzysta z portów albo publikuje eventy. Wrzucamy klasy typu `SpawnEnemy`, `FindNearestEnemy` - jedna klasa = jeden przypadek użycia. ```csharp namespace Game.Features.Combat.Application; public sealed class Attack { public void Execute(Character attacker, IDamageable target) { if (!attacker.CanAttack) return; target.TakeDamage(attacker.CalculateDamage()); } } ``` --- ### 3.3 `Contracts` Publiczne API feature'a. Jedyna część feature'a, którą wolno importować z zewnątrz. Wrzucamy identyfikatory, DTO/read-modele, eventy, małe interfejsy (porty publiczne). Unikamy logiki. Kontrakt to tylko kształt danych i sygnatury - nie implementacja. ```csharp namespace Game.Features.Enemies.Contracts; public readonly record struct EnemyId(Guid Value); public sealed record EnemyInfo(EnemyId Id, Vector2 Position, bool IsAlive); public interface IEnemyQuery { IReadOnlyCollection GetAliveEnemies(); } ``` --- ### 3.4 `Godot` Adapter między silnikiem a grą. Cienka warstwa łącząca `Node`y z logiką w `Domain`/`Application`. Wrzucamy wszystko co związane z silnikiem gry: `Node`, `CharacterBody3D`, `Area3D`, `AnimationPlayer`, obsługę inputu, sygnały, sceny. Unikamy logiki gry. `Node` tylko woła `Domain`/`Application` i mapuje wyniki z powrotem na silnik. ```csharp namespace Game.Features.Player.Godot; public partial class PlayerController : Node3D { [Export] public CharacterBody3D Body; private Health _health = null!; public void Initialize(Health health) { _health = health; } } ``` --- ### 3.5 `Shared` Wspólne, stabilne koncepty używane przez wiele feature'ów. Proste value obiekty lądują płasko w `Shared/Domain`: ```csharp namespace Game.Shared.Domain; public readonly record struct EntityId(Guid Value); ``` Jeśli współdzielony koncept ma też port + implementację (np. event bus, z którego korzysta wiele feature'ów), dostaje własny podfolder z tym samym podziałem `Contracts`/`Infrastructure`, co niżej: ```text Shared/ └── Events/ ├── Contracts/ │ ├── IEvent.cs │ └── IEventBus.cs └── Infrastructure/ └── InMemoryEventBus.cs ``` --- ### 3.6 `Infrastructure` Implementacja portu z `Contracts`. Wrzucamy konkretne, techniczne implementacje interfejsów zdefiniowanych w `Contracts`. Unikamy logiki domenowej i unikamy trzymania w tym folderze samego portu (interfejs zawsze zostaje w `Contracts`). ```csharp namespace Game.Shared.Events.Infrastructure; public sealed class InMemoryEventBus : IEventBus { // implementacja portu } ``` --- ### 3.7 `Bootstrap` Wiring implementacji portów w całość. Wrzucamy skrypt(y) spinające całą grę: tworzenie konkretnych implementacji portów, `GetNode()` po dzieci sceny głównej, wywołania `Initialize(...)`. Unikamy logiki feature'a (to należy do `Godot/` danego feature'a) i lokalnych, jedno-portowych implementacji (to należy do `Infrastructure/` danego modułu). ```csharp namespace Game.Bootstrap; public partial class GameBootstrap : Node { public override void _Ready() { var eventBus = new InMemoryEventBus(); var player = GetNode("Player"); player.Initialize(eventBus); } } ``` --- ### 3.8 `Scenes` Sceny i geometria świata, które same w sobie nie niosą logiki gry - poziomy, propy, statyczne elementy środowiska. Wrzucamy pliki `.tscn`, niepowiązane bezpośrednio z jednym feature'em. Unikamy logiki - jeśli scena zaczyna mieć skrypt z regułami gry, ten skrypt trafia do odpowiedniego feature'a (`Godot/`), nie tutaj. --- ## 4. Zależności między feature'ami Zasada: **feature A może importować tylko `FeatureB.Contracts`.** ```text Targeting.Application -> Enemies.Contracts DOBRZE Loot.Application -> Enemies.Contracts DOBRZE Targeting -> Enemies.Domain ŹLE Loot -> Enemies.Application ŹLE Combat -> Enemies.Godot ŹLE ``` ### Eventy między feature'ami Gdy jedno zdarzenie interesuje wiele feature'ów, feature źródłowy publikuje event i nie wie, kto go słucha: ```text Enemies -> EnemyKilled -> Loot -> Quests -> Score ``` `Enemies` nie zna `Loot`, `Quests` ani `Score`.