From be04f922606db61beaa55d6179d9aad228c967b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20St=C4=99pie=C5=84?= Date: Thu, 20 Aug 2026 20:48:10 +0200 Subject: [PATCH] chore: define dir structure --- docs/02_architecture.md | 258 ++++++++++++++++++ src/Bootstrap/GameBootstrap.cs | 8 + .../GameBootstrap.cs.uid} | 0 src/Infrastructure/GameWorld/GameWorld.cs | 8 - .../Floor => Scenes}/FloorCeilingHr42.tscn | 0 .../GameWorld => Scenes}/GameWorld.tscn | 2 +- src/Shared/Domain/EntityId.cs | 5 + src/Shared/Events/Contracts/IEvent.cs | 6 + .../Events/{ => Contracts}/IEvent.cs.uid | 0 .../Events/{ => Contracts}/IEventBus.cs | 2 +- .../Events/{ => Contracts}/IEventBus.cs.uid | 0 src/Shared/Events/IEvent.cs | 6 - .../{ => Infrastructure}/InMemoryEventBus.cs | 3 +- .../InMemoryEventBus.cs.uid | 0 14 files changed, 281 insertions(+), 17 deletions(-) create mode 100644 docs/02_architecture.md create mode 100644 src/Bootstrap/GameBootstrap.cs rename src/{Infrastructure/GameWorld/GameWorld.cs.uid => Bootstrap/GameBootstrap.cs.uid} (100%) delete mode 100644 src/Infrastructure/GameWorld/GameWorld.cs rename src/{Infrastructure/Floor => Scenes}/FloorCeilingHr42.tscn (100%) rename src/{Infrastructure/GameWorld => Scenes}/GameWorld.tscn (97%) create mode 100644 src/Shared/Domain/EntityId.cs create mode 100644 src/Shared/Events/Contracts/IEvent.cs rename src/Shared/Events/{ => Contracts}/IEvent.cs.uid (100%) rename src/Shared/Events/{ => Contracts}/IEventBus.cs (84%) rename src/Shared/Events/{ => Contracts}/IEventBus.cs.uid (100%) delete mode 100644 src/Shared/Events/IEvent.cs rename src/Shared/Events/{ => Infrastructure}/InMemoryEventBus.cs (96%) rename src/Shared/Events/{ => Infrastructure}/InMemoryEventBus.cs.uid (100%) diff --git a/docs/02_architecture.md b/docs/02_architecture.md new file mode 100644 index 0000000..23f16bf --- /dev/null +++ b/docs/02_architecture.md @@ -0,0 +1,258 @@ +# 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); + } +} +``` + +--- + +### 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`. \ No newline at end of file diff --git a/src/Bootstrap/GameBootstrap.cs b/src/Bootstrap/GameBootstrap.cs new file mode 100644 index 0000000..d4306e2 --- /dev/null +++ b/src/Bootstrap/GameBootstrap.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Game.Bootstrap; + +public partial class GameBootstrap : Node +{ + +} diff --git a/src/Infrastructure/GameWorld/GameWorld.cs.uid b/src/Bootstrap/GameBootstrap.cs.uid similarity index 100% rename from src/Infrastructure/GameWorld/GameWorld.cs.uid rename to src/Bootstrap/GameBootstrap.cs.uid diff --git a/src/Infrastructure/GameWorld/GameWorld.cs b/src/Infrastructure/GameWorld/GameWorld.cs deleted file mode 100644 index 222661a..0000000 --- a/src/Infrastructure/GameWorld/GameWorld.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Game.Features.Player.Godot; -using Godot; -using System; - -public partial class GameWorld : Node -{ - -} diff --git a/src/Infrastructure/Floor/FloorCeilingHr42.tscn b/src/Scenes/FloorCeilingHr42.tscn similarity index 100% rename from src/Infrastructure/Floor/FloorCeilingHr42.tscn rename to src/Scenes/FloorCeilingHr42.tscn diff --git a/src/Infrastructure/GameWorld/GameWorld.tscn b/src/Scenes/GameWorld.tscn similarity index 97% rename from src/Infrastructure/GameWorld/GameWorld.tscn rename to src/Scenes/GameWorld.tscn index c526c85..eeab236 100644 --- a/src/Infrastructure/GameWorld/GameWorld.tscn +++ b/src/Scenes/GameWorld.tscn @@ -1,7 +1,7 @@ [gd_scene format=3 uid="uid://c44pljehe0qun"] [ext_resource type="Texture2D" uid="uid://b5i827vmueyei" path="res://assets/textures/skyboxes/skybox_emx_19.png" id="1_g4n7o"] -[ext_resource type="PackedScene" uid="uid://curuqmbndymi6" path="res://src/Infrastructure/Floor/FloorCeilingHr42.tscn" id="2_1kpcd"] +[ext_resource type="PackedScene" uid="uid://curuqmbndymi6" path="res://src/Scenes/FloorCeilingHr42.tscn" id="2_1kpcd"] [ext_resource type="PackedScene" uid="uid://bka1dmgdi5btd" path="res://src/Features/Player/Godot/PlayerNode.tscn" id="2_mvtit"] [sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"] diff --git a/src/Shared/Domain/EntityId.cs b/src/Shared/Domain/EntityId.cs new file mode 100644 index 0000000..84752d1 --- /dev/null +++ b/src/Shared/Domain/EntityId.cs @@ -0,0 +1,5 @@ +using System; + +namespace Game.Shared.Domain; + +public readonly record struct EntityId(Guid Value); diff --git a/src/Shared/Events/Contracts/IEvent.cs b/src/Shared/Events/Contracts/IEvent.cs new file mode 100644 index 0000000..74c4708 --- /dev/null +++ b/src/Shared/Events/Contracts/IEvent.cs @@ -0,0 +1,6 @@ +namespace Game.Shared.Events.Contracts; + +public interface IEvent +{ + +} \ No newline at end of file diff --git a/src/Shared/Events/IEvent.cs.uid b/src/Shared/Events/Contracts/IEvent.cs.uid similarity index 100% rename from src/Shared/Events/IEvent.cs.uid rename to src/Shared/Events/Contracts/IEvent.cs.uid diff --git a/src/Shared/Events/IEventBus.cs b/src/Shared/Events/Contracts/IEventBus.cs similarity index 84% rename from src/Shared/Events/IEventBus.cs rename to src/Shared/Events/Contracts/IEventBus.cs index 543736a..a2047fe 100644 --- a/src/Shared/Events/IEventBus.cs +++ b/src/Shared/Events/Contracts/IEventBus.cs @@ -1,6 +1,6 @@ using System; -namespace Game.Shared.Events; +namespace Game.Shared.Events.Contracts; public interface IEventBus { diff --git a/src/Shared/Events/IEventBus.cs.uid b/src/Shared/Events/Contracts/IEventBus.cs.uid similarity index 100% rename from src/Shared/Events/IEventBus.cs.uid rename to src/Shared/Events/Contracts/IEventBus.cs.uid diff --git a/src/Shared/Events/IEvent.cs b/src/Shared/Events/IEvent.cs deleted file mode 100644 index f12c257..0000000 --- a/src/Shared/Events/IEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Game.Shared.Events; - -public interface IEvent -{ - -} \ No newline at end of file diff --git a/src/Shared/Events/InMemoryEventBus.cs b/src/Shared/Events/Infrastructure/InMemoryEventBus.cs similarity index 96% rename from src/Shared/Events/InMemoryEventBus.cs rename to src/Shared/Events/Infrastructure/InMemoryEventBus.cs index a12c659..4aa9460 100644 --- a/src/Shared/Events/InMemoryEventBus.cs +++ b/src/Shared/Events/Infrastructure/InMemoryEventBus.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using Game.Shared.Events.Contracts; -namespace Game.Shared.Events; +namespace Game.Shared.Events.Infrastructure; public sealed class InMemoryEventBus : IEventBus { diff --git a/src/Shared/Events/InMemoryEventBus.cs.uid b/src/Shared/Events/Infrastructure/InMemoryEventBus.cs.uid similarity index 100% rename from src/Shared/Events/InMemoryEventBus.cs.uid rename to src/Shared/Events/Infrastructure/InMemoryEventBus.cs.uid