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:
parent
40af732e21
commit
3642d53dd3
11 changed files with 87 additions and 18 deletions
BIN
Content/Dungeon/DataTable/DT_RoomPool.uasset
Normal file
BIN
Content/Dungeon/DataTable/DT_RoomPool.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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
|
||||
→ Branch: NOT Contains(ExcludeTypes, RoomType)
|
||||
AND MinLevel <= CurrentLevel
|
||||
AND (MaxLevel >= CurrentLevel OR MaxLevel == 0)
|
||||
True ▶ → Add zu ValidCandidates
|
||||
Completed ▶ → Branch: Length > 0?
|
||||
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
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue