FindCompatibleRoom implementiert, DT_RoomPool angelegt, Plan aktualisiert

- BP_DungeonGenerator: FindCompatibleRoom mit Data Table Filterung, gewichteter Zufallswahl und Socket-Matching
- DT_RoomPool Data Table erstellt (General, Corridor, DeadEnd)
- Raum-Blueprints aktualisiert (alle Raumtypen)
- plan_v1.md: DT_RoomPool Anleitung ergänzt, UE5-spezifische Korrekturen (Get Data Table Row, Return Node Pins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andre 2026-04-12 01:18:43 +02:00
parent 40af732e21
commit 3642d53dd3
11 changed files with 87 additions and 18 deletions

Binary file not shown.

View file

@ -98,6 +98,54 @@ Speichert Informationen über jeden platzierten Raum zentral im Generator.
| MinLevel | Integer | Ab welchem Level verfügbar |
| MaxLevel | Integer | Bis welchem Level (0 = unbegrenzt) |
#### DT_RoomPool erstellen (Data Table im Editor)
Die Data Table `DT_RoomPool` ist die zentrale Tabelle, aus der der Generator zur Laufzeit Räume auswählt. Sie basiert auf dem Struct `S_RoomPoolEntry`.
**Voraussetzung:** Der Struct `S_RoomPoolEntry` muss bereits existieren (siehe oben).
**Schritt 1 Data Table Asset anlegen:**
1. Im Content Browser: Rechtsklick → **Miscellaneous** → **Data Table**
2. Im Popup **"Pick Row Structure"**: Wähle **S_RoomPoolEntry** als Row Structure
3. Bestätige mit **OK**
4. Benenne das neue Asset: **DT_RoomPool**
5. Speichere es z.B. unter `Content/Data/DT_RoomPool` (oder einem Ordner deiner Wahl)
**Schritt 2 Zeilen hinzufügen:**
Öffne DT_RoomPool per Doppelklick. Die Tabelle ist zunächst leer.
1. Klicke oben links auf **Add** (+ Symbol), um eine neue Zeile hinzuzufügen
2. Vergib einen eindeutigen **Row Name** (z.B. "General_01", "Corridor_01", "DeadEnd_01")
3. Befülle die Felder der Zeile:
| Feld | Was eintragen | Hinweis |
|---|---|---|
| RoomClass | Blueprint-Klasse auswählen (z.B. BP_DungeonRoom_General) | Klick auf das Dropdown → Blueprint-Class wählen |
| RoomType | Passenden Enum-Wert wählen (z.B. General) | Muss zum Blueprint passen |
| Weight | Gewichtung als Float (z.B. 1.0) | Höher = häufiger gewählt |
| MinLevel | Ab welchem Level verfügbar (z.B. 1) | 1 = ab dem ersten Level |
| MaxLevel | Bis welchem Level (z.B. 0) | 0 = unbegrenzt / auf allen Levels |
**Schritt 3 Empfohlene Einträge für den Prototyp:**
| Row Name | RoomClass | RoomType | Weight | MinLevel | MaxLevel |
|---|---|---|---|---|---|
| General_01 | BP_DungeonRoom_General | General | 1.0 | 1 | 0 |
| Corridor_01 | BP_DungeonRoom_Corridor | Corridor | 1.0 | 1 | 0 |
| DeadEnd_01 | BP_DungeonRoom_DeadEnd | DeadEnd | 1.0 | 1 | 0 |
> **Hinweis:** Entrance, PortalIn, PortalOut und Exit werden **nicht** in die Data Table eingetragen diese Räume spawnt der Generator direkt per Klasse (hartcodiert in BuildLevel / PlaceEndRoom). Nur Räume, die per Zufallswahl platziert werden (General, Corridor, DeadEnd), gehören hier rein.
> **Tipp:** Du kannst später weitere Zeilen hinzufügen (z.B. General_02, General_03 mit verschiedenen Gewichtungen oder Level-Bereichen), um mehr Vielfalt zu erzeugen.
**Schritt 4 Im Generator referenzieren:**
Im BP_DungeonGenerator: Die Variable `RoomPoolTable` (Typ: Data Table Reference) auf **DT_RoomPool** setzen. Das geschieht im Details Panel unter dem Default Value:
1. Wähle die Variable `RoomPoolTable` aus
2. Im Details Panel → Default Value → Dropdown → wähle **DT_RoomPool**
### 1.3 Was wird gelöscht/ersetzt
- **S_SocketData** bleibt in den Räumen, wird aber auf **S_SocketDefinition** reduziert (nur noch statische Daten)
@ -848,32 +896,50 @@ Rückwärts iterieren verhindert Index-Verschiebung beim Entfernen!
**Schritt 1 Gültige Kandidaten aus der Data Table filtern:**
1. → **Get Data Table Rows** (Table: RoomPoolTable) → AllRows (Array of S_RoomPoolEntry)
2. → Lokales Array **ValidCandidates** (Typ: Array of S_RoomPoolEntry)
3. → **ForEachLoop** über AllRows
4. → Array Element → **Break S_RoomPoolEntry** → RoomType, MinLevel, MaxLevel
**Lokale Variablen (in der Funktion anlegen):**
- **RowNames** (Array of Name) Zeilennamen aus der Data Table
- **ValidCandidates** (Array of S_RoomPoolEntry) gefilterte Kandidaten
- **TotalWeight** (Float) Summe aller Weights
- **RandomPick** (Float) zufälliger Wert zwischen 0 und TotalWeight
- **RunningWeight** (Float) laufende Summe beim Iterieren
- **ChosenEntry** (S_RoomPoolEntry) der gewählte Kandidat
- **TempCandidate** (Actor Object Ref) temporär gespawnter Raum für Socket-Matching
> **Wichtig:** In UE5 gibt es keinen "Get Data Table Rows"-Node (Plural). Stattdessen holst du zuerst alle Zeilennamen mit **Get Data Table Row Names**, dann iterierst du darüber und holst jede Zeile einzeln mit **Get Data Table Row**.
1. → **Get Data Table Row Names** (Table: RoomPoolTable) → Set **RowNames**
2. → **ForEachLoop** über RowNames
3. → **Get Data Table Row** (Table: RoomPoolTable, Row Name: Array Element) → hat zwei Exec-Pins und einen Data-Pin:
- **Row Found ▶** (Exec) Zeile existiert
- **Row Not Found ▶** (Exec) Zeile nicht gefunden (einfach ignorieren, weiter zur nächsten Iteration)
- **Out Row** (S_RoomPoolEntry) die Zeilendaten
4. → **Row Found ▶**: Out Row → **Break S_RoomPoolEntry** → RoomType, MinLevel, MaxLevel
5. → **Branch**: Drei Bedingungen müssen ALLE true sein (AND):
- RoomType → **Contains** in ExcludeTypes Array → **NOT** (= RoomType ist NICHT ausgeschlossen)
- MinLevel **<=** CurrentLevel
- MaxLevel **>=** CurrentLevel **OR** MaxLevel == 0 (0 = unbegrenzt)
6. → True ▶: **Add** Array Element zu ValidCandidates
6. → True ▶: Out Row → **Add** zu ValidCandidates
7. → ForEachLoop Completed ▶: **Branch** (ValidCandidates **Length > 0**)
8. → False ▶: **Return** (Success = false)
```
Get Data Table Rows → ForEachLoop
Get Data Table Row Names (RoomPoolTable) → RowNames
ForEachLoop (RowNames)
Loop Body ▶ → Get Data Table Row (RoomPoolTable, Array Element)
Row Found ▶ → Break S_RoomPoolEntry → RoomType, MinLevel, MaxLevel
→ Branch: NOT Contains(ExcludeTypes, RoomType)
AND MinLevel <= CurrentLevel
AND (MaxLevel >= CurrentLevel OR MaxLevel == 0)
True ▶ → Add zu ValidCandidates
Completed ▶ → Branch: Length > 0?
Row Not Found ▶ → (nichts, nächste Iteration)
Completed ▶ → Branch: ValidCandidates Length > 0?
False ▶ → Return (Success = false)
True ▶ → (weiter mit Schritt 9)
```
**Schritt 9 Gewichtete Zufallswahl:**
9. → **TotalWeight** = 0.0 (lokale Float-Variable)
9. → **Set TotalWeight** = 0.0
10. → **ForEachLoop** über ValidCandidates → TotalWeight += Array Element → Weight
11. → **Random Float in Range** (0.0, TotalWeight) → **Set** RandomPick
12. → **Set** RunningWeight = 0.0
@ -905,12 +971,15 @@ Wir müssen herausfinden, welcher Socket des neuen Raums als "Eingang" benutzt w
19. → **Branch**: SocketDefinitions **Length > 0**?
- False ▶: TempCandidate → **Destroy Actor****Return** (Success = false)
20. → True ▶: SocketDefinitions **Get (Index 0)** → Break S_SocketDefinition
21. → **Set MatchingSocketIndex** = 0
22. → **Set MatchingSocketId** = SocketId (aus dem Break)
23. → **Set FoundRoomType** = ChosenEntry → RoomType
24. → **Set FoundRoomClass** = ChosenEntry → RoomClass
25. → TempCandidate → **Destroy Actor**
26. → **Return** (FoundRoomClass, MatchingSocketIndex, MatchingSocketId, FoundRoomType, Success = true)
21. → TempCandidate → **Destroy Actor**
22. → **Return Node** die Output-Pins direkt am Return Node verdrahten:
- FoundRoomClass ← ChosenEntry → Break S_RoomPoolEntry → RoomClass
- MatchingSocketIndex ← 0 (Integer Literal)
- MatchingSocketId ← Break S_SocketDefinition → SocketId (aus Schritt 20)
- FoundRoomType ← ChosenEntry → Break S_RoomPoolEntry → RoomType
- Success ← true (Boolean Literal)
> **Hinweis:** In UE5 Funktionen haben die Outputs keine separaten Set-Nodes — sie sind direkt als Data-Pins am Return Node. Du ziehst die Daten-Verbindungen (grüne/blaue Drähte) direkt in die Pins des Return Nodes. Die Exec-Kette ist: SpawnActor ▶ → GetSocketDefinitions ▶ → Branch → True ▶ → Destroy Actor ▶ → Return.
**Spätere Erweiterung Richtungs-Matching:**
Statt pauschal Socket 0 zu nehmen, könnte man die Richtung des offenen Sockets prüfen und den Socket des Kandidaten wählen, der in die entgegengesetzte Richtung zeigt (Nord → Süd, Ost → West). Das ist aber für den Prototyp nicht nötig.