docs: add plans for Schritt 12 (Charakterbogen), 13 (Capsules), 14 (Art Pass)
This commit is contained in:
233
docs/plans/2026-04-16-schritt-12-charakterbogen.md
Normal file
233
docs/plans/2026-04-16-schritt-12-charakterbogen.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# Schritt 12 — Lite Charakterbogen in der Lobby
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Spieler füllen in der Lobby ihren Charakter aus (Name, Rasse, Klasse, 6 Ability-Modifier). Die Daten werden an alle Peers gesynct. Quick-Fill-Buttons für Dev-Archetypen.
|
||||
|
||||
**Architecture:** Neue Felder in `tavern_lobby.tscn` unter dem Join-Bereich. `tavern_lobby.gd` sendet bei Änderungen via neuem `request_update_character` RPC an Server; Server speichert unter `players[id]["char"]` und relayed an alle. NetworkManager bekommt Signal `character_updated(peer_id, data)`.
|
||||
|
||||
**Tech Stack:** Godot 4.x GDScript, MultiplayerAPI RPC, OptionButton, SpinBox, GridContainer
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Aktion |
|
||||
|------|--------|
|
||||
| `ruf-der-pilze/scenes/tavern_lobby.tscn` | Modify — Charakterbogen-UI unter Join-Bereich |
|
||||
| `ruf-der-pilze/scripts/tavern_lobby.gd` | Modify — Formular-Logik, Auto-Send bei Änderung |
|
||||
| `ruf-der-pilze/scripts/network_manager.gd` | Modify — `request_update_character` + `_relay_character_update` RPC, `character_updated` Signal |
|
||||
| `docs/STATUS.md` | Modify — Schritt 12 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Character-RPC in NetworkManager
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scripts/network_manager.gd`
|
||||
|
||||
- [ ] **Schritt 1: Signal + zwei RPCs am Ende von network_manager.gd hinzufügen**
|
||||
|
||||
```gdscript
|
||||
signal character_updated(peer_id: int, data: Dictionary)
|
||||
|
||||
# Client → Server
|
||||
@rpc("any_peer", "call_remote", "reliable")
|
||||
func request_update_character(data: Dictionary) -> void:
|
||||
if not multiplayer.is_server(): return
|
||||
var peer_id := multiplayer.get_remote_sender_id()
|
||||
if not players.has(peer_id): return
|
||||
players[peer_id]["char"] = data
|
||||
_relay_character_update.rpc(peer_id, data)
|
||||
|
||||
# Server → All
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _relay_character_update(peer_id: int, data: Dictionary) -> void:
|
||||
if players.has(peer_id):
|
||||
players[peer_id]["char"] = data
|
||||
character_updated.emit(peer_id, data)
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Godot öffnen, Parse-Fehler prüfen**
|
||||
|
||||
Godot-Editor öffnen und prüfen ob das Script fehlerfrei geladen wird (keine roten Fehler im Output-Panel).
|
||||
|
||||
- [ ] **Schritt 3: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scripts/network_manager.gd
|
||||
git commit -m "net: add request_update_character RPC + character_updated signal"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Charakterbogen-UI in tavern_lobby.tscn
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scenes/tavern_lobby.tscn`
|
||||
|
||||
Node-Struktur unter dem bestehenden Join-Bereich (nach NameInput/JoinButton, aber vor der StartButton-Zeile). `CharSheet` ist zunächst `visible = false` — wird nach erfolgreichem Join eingeblendet.
|
||||
|
||||
```
|
||||
VBoxContainer "CharSheet"
|
||||
visible = false
|
||||
Label "LblChar" — text="Charakter"
|
||||
GridContainer "CharGrid" — columns=2
|
||||
Label — text="Rasse"
|
||||
OptionButton "RaceOption"
|
||||
items: Mensch, Elf, Zwerg, Halbork, Halbelf, Tiefling
|
||||
Label — text="Klasse"
|
||||
OptionButton "ClassOption"
|
||||
items: Krieger, Magier, Kleriker, Schurke, Druide, Barbar
|
||||
HSeparator
|
||||
Label "LblModifiers" — text="Werte"
|
||||
GridContainer "ModGrid" — columns=6
|
||||
Label — text="STR"
|
||||
Label — text="DEX"
|
||||
Label — text="CON"
|
||||
Label — text="INT"
|
||||
Label — text="WIS"
|
||||
Label — text="CHA"
|
||||
SpinBox "SpinSTR" — min=-5, max=10, value=0
|
||||
SpinBox "SpinDEX" — min=-5, max=10, value=0
|
||||
SpinBox "SpinCON" — min=-5, max=10, value=0
|
||||
SpinBox "SpinINT" — min=-5, max=10, value=0
|
||||
SpinBox "SpinWIS" — min=-5, max=10, value=0
|
||||
SpinBox "SpinCHA" — min=-5, max=10, value=0
|
||||
HSeparator
|
||||
Label "LblArchetype" — text="Schnell-Auswahl (Dev)"
|
||||
HBoxContainer "ArchetypeRow"
|
||||
Button "BtnKrieger" — text="Krieger"
|
||||
Button "BtnMagier" — text="Magier"
|
||||
Button "BtnKleriker"— text="Kleriker"
|
||||
Button "BtnSchurke" — text="Schurke"
|
||||
Button "BtnDruide" — text="Druide"
|
||||
Button "BtnBarbar" — text="Barbar"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 1: Nodes in tavern_lobby.tscn via Godot-Editor anlegen**
|
||||
|
||||
`tavern_lobby.tscn` öffnen. Im Scene-Panel die bestehende UI-Hierarchie finden (CanvasLayer → Panel o.ä.). Den neuen `VBoxContainer "CharSheet"` nach den Join-Controls einfügen.
|
||||
|
||||
- [ ] **Schritt 2: Layout in Godot-Editor prüfen**
|
||||
|
||||
CharSheet sollte initial unsichtbar sein (`visible = false`). Kurz auf `visible = true` setzen, Layout im Viewport prüfen, dann wieder auf `false`.
|
||||
|
||||
- [ ] **Schritt 3: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scenes/tavern_lobby.tscn
|
||||
git commit -m "lobby: add character sheet UI nodes (CharSheet, ModGrid, ArchetypeRow)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Charakterbogen-Logik in tavern_lobby.gd
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scripts/tavern_lobby.gd`
|
||||
|
||||
Zuerst das Script lesen um die genauen Node-Pfade und bestehende Struktur zu verstehen. Die Pfade in den Code-Beispielen unten ggf. anpassen.
|
||||
|
||||
- [ ] **Schritt 1: Konstante Archetyp-Presets + Helper `_collect_char_data()` hinzufügen**
|
||||
|
||||
Am Anfang der Klasse einfügen (nach `extends` und bestehenden `var`-Deklarationen):
|
||||
|
||||
```gdscript
|
||||
const ARCHETYPES := {
|
||||
"Krieger": {"race": "Mensch", "class": "Krieger", "STR": 3, "DEX": 1, "CON": 2, "INT": 0, "WIS": 0, "CHA": 0},
|
||||
"Magier": {"race": "Elf", "class": "Magier", "STR":-1, "DEX": 2, "CON": 0, "INT": 4, "WIS": 2, "CHA": 1},
|
||||
"Kleriker": {"race": "Zwerg", "class": "Kleriker", "STR": 1, "DEX": 0, "CON": 2, "INT": 1, "WIS": 3, "CHA": 0},
|
||||
"Schurke": {"race": "Halbelf", "class": "Schurke", "STR": 0, "DEX": 4, "CON": 1, "INT": 1, "WIS": 1, "CHA": 2},
|
||||
"Druide": {"race": "Mensch", "class": "Druide", "STR": 0, "DEX": 1, "CON": 1, "INT": 1, "WIS": 3, "CHA": 0},
|
||||
"Barbar": {"race": "Halbork", "class": "Barbar", "STR": 4, "DEX": 2, "CON": 3, "INT":-1, "WIS": 0, "CHA":-1},
|
||||
}
|
||||
|
||||
func _collect_char_data() -> Dictionary:
|
||||
var race_opt := $<UI_PATH>/CharSheet/CharGrid/RaceOption as OptionButton
|
||||
var class_opt := $<UI_PATH>/CharSheet/CharGrid/ClassOption as OptionButton
|
||||
return {
|
||||
"race": race_opt.get_item_text(race_opt.selected),
|
||||
"class": class_opt.get_item_text(class_opt.selected),
|
||||
"STR": int($<UI_PATH>/CharSheet/ModGrid/SpinSTR.value),
|
||||
"DEX": int($<UI_PATH>/CharSheet/ModGrid/SpinDEX.value),
|
||||
"CON": int($<UI_PATH>/CharSheet/ModGrid/SpinCON.value),
|
||||
"INT": int($<UI_PATH>/CharSheet/ModGrid/SpinINT.value),
|
||||
"WIS": int($<UI_PATH>/CharSheet/ModGrid/SpinWIS.value),
|
||||
"CHA": int($<UI_PATH>/CharSheet/ModGrid/SpinCHA.value),
|
||||
}
|
||||
```
|
||||
|
||||
`<UI_PATH>` durch den tatsächlichen Pfad zur UI-Ebene ersetzen (aus dem Script lesen).
|
||||
|
||||
- [ ] **Schritt 2: `_setup_char_sheet()` hinzufügen und in `_ready()` aufrufen**
|
||||
|
||||
```gdscript
|
||||
func _setup_char_sheet() -> void:
|
||||
for archetype_name: String in ARCHETYPES:
|
||||
var btn := $<UI_PATH>/CharSheet/ArchetypeRow.get_node(
|
||||
NodePath("Btn" + archetype_name)) as Button
|
||||
var preset: Dictionary = ARCHETYPES[archetype_name]
|
||||
btn.pressed.connect(func():
|
||||
var race_opt := $<UI_PATH>/CharSheet/CharGrid/RaceOption as OptionButton
|
||||
var class_opt := $<UI_PATH>/CharSheet/CharGrid/ClassOption as OptionButton
|
||||
for i in race_opt.item_count:
|
||||
if race_opt.get_item_text(i) == preset["race"]:
|
||||
race_opt.selected = i
|
||||
for i in class_opt.item_count:
|
||||
if class_opt.get_item_text(i) == preset["class"]:
|
||||
class_opt.selected = i
|
||||
for stat: String in ["STR", "DEX", "CON", "INT", "WIS", "CHA"]:
|
||||
($<UI_PATH>/CharSheet/ModGrid.get_node(
|
||||
NodePath("Spin" + stat)) as SpinBox).value = preset[stat]
|
||||
_send_char_data()
|
||||
)
|
||||
for spin_name: String in ["SpinSTR", "SpinDEX", "SpinCON", "SpinINT", "SpinWIS", "SpinCHA"]:
|
||||
($<UI_PATH>/CharSheet/ModGrid.get_node(NodePath(spin_name)) as SpinBox
|
||||
).value_changed.connect(func(_v: float): _send_char_data())
|
||||
($<UI_PATH>/CharSheet/CharGrid/RaceOption as OptionButton
|
||||
).item_selected.connect(func(_i: int): _send_char_data())
|
||||
($<UI_PATH>/CharSheet/CharGrid/ClassOption as OptionButton
|
||||
).item_selected.connect(func(_i: int): _send_char_data())
|
||||
|
||||
func _send_char_data() -> void:
|
||||
NetworkManager.request_update_character.rpc_id(1, _collect_char_data())
|
||||
```
|
||||
|
||||
`_setup_char_sheet()` am Ende von `_ready()` aufrufen.
|
||||
|
||||
- [ ] **Schritt 3: CharSheet nach erfolgreichem Join einblenden**
|
||||
|
||||
In der bestehenden Funktion die nach erfolgreichem Join aufgerufen wird (per NetworkManager-Signal oder direkt in `_on_join_button_pressed`), am Ende hinzufügen:
|
||||
|
||||
```gdscript
|
||||
$<UI_PATH>/CharSheet.visible = true
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: Godot öffnen, Parse-Fehler prüfen**
|
||||
|
||||
- [ ] **Schritt 5: End-to-End-Test**
|
||||
|
||||
Server + DM-Client + Spieler-Client starten.
|
||||
- Spieler joined → CharSheet erscheint
|
||||
- Spieler klickt "Magier" → Race=Elf, Class=Magier, Modifier werden gesetzt
|
||||
- Spieler ändert STR manuell → sendet sofort
|
||||
- Im GDScript-Debugger: `NetworkManager.players[peer_id]["char"]` zeigt die Daten
|
||||
- Zweiter Client: erhält `character_updated` Signal
|
||||
|
||||
- [ ] **Schritt 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scripts/tavern_lobby.gd
|
||||
git commit -m "lobby: wire character sheet form — archetypes, modifier spinboxes, auto-sync RPC"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: STATUS.md updaten
|
||||
|
||||
- [ ] **Schritt 1: Schritt 12 als ✅ markieren in `docs/STATUS.md` und `ruf-der-pilze/CLAUDE.md`**
|
||||
|
||||
- [ ] **Schritt 2: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/STATUS.md ruf-der-pilze/CLAUDE.md
|
||||
git commit -m "docs: mark Schritt 12 complete — lite Charakterbogen"
|
||||
```
|
||||
217
docs/plans/2026-04-16-schritt-13-capsules.md
Normal file
217
docs/plans/2026-04-16-schritt-13-capsules.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Schritt 13 — Charakter-Platzhalter (Capsule-Avatare)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Jeder Spieler wird in der Lobby als farbige Kapsel mit Namens-Label dargestellt. Farbe kommt aus `players[id]["char"]["class"]` (Fallback: Grau wenn char-Daten noch nicht vorhanden). Capsules sind eine eigene Szene — einfach austauschbar gegen echte Modelle später.
|
||||
|
||||
**Architecture:** Neue Szene `character_placeholder.tscn` (Node3D + CapsuleMesh + Label3D). `tavern_lobby.gd` instanziiert und entfernt Capsules wenn `player_joined`/`player_left` feuert. Capsules stehen an vordefinierten `Marker3D`-Lobby-Standpunkten (5 Spots in tavern_lobby.tscn). Farbe wird per `CharacterPlaceholder.set_color(class_name)` gesetzt; bei `character_updated`-Signal wird Farbe aktualisiert.
|
||||
|
||||
**Tech Stack:** Godot 4.x GDScript, MeshInstance3D, CapsuleMesh, StandardMaterial3D, Label3D, Marker3D
|
||||
|
||||
**Parallel-Hinweis:** Dieser Plan liest `players[id].get("char", {}).get("class", "")`. Falls Schritt 12 noch nicht gemergt ist, ist "class" leer → Grau-Fallback. Kein harter Blocker.
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Aktion |
|
||||
|------|--------|
|
||||
| `ruf-der-pilze/scenes/character_placeholder.tscn` | Create — Capsule + Label3D + Script |
|
||||
| `ruf-der-pilze/scripts/character_placeholder.gd` | Create — `set_color(class_name)`, `set_label(name)` |
|
||||
| `ruf-der-pilze/scenes/tavern_lobby.tscn` | Modify — 5 LobbySpot Marker3D-Nodes hinzufügen |
|
||||
| `ruf-der-pilze/scripts/tavern_lobby.gd` | Modify — Capsule spawn/remove on join/leave |
|
||||
| `docs/STATUS.md` | Modify — Schritt 13 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: character_placeholder.gd + .tscn erstellen
|
||||
|
||||
**Files:**
|
||||
- Create: `ruf-der-pilze/scripts/character_placeholder.gd`
|
||||
- Create: `ruf-der-pilze/scenes/character_placeholder.tscn`
|
||||
|
||||
- [ ] **Schritt 1: character_placeholder.gd erstellen**
|
||||
|
||||
Datei anlegen unter `ruf-der-pilze/scripts/character_placeholder.gd`:
|
||||
|
||||
```gdscript
|
||||
extends Node3D
|
||||
|
||||
const CLASS_COLORS: Dictionary = {
|
||||
"Krieger": Color(0.80, 0.20, 0.20), # Rot
|
||||
"Magier": Color(0.20, 0.20, 0.90), # Blau
|
||||
"Kleriker": Color(0.90, 0.85, 0.20), # Gelb
|
||||
"Schurke": Color(0.20, 0.65, 0.20), # Grün
|
||||
"Druide": Color(0.40, 0.75, 0.30), # Hellgrün
|
||||
"Barbar": Color(0.75, 0.40, 0.10), # Orange
|
||||
}
|
||||
const DEFAULT_COLOR := Color(0.65, 0.65, 0.65) # Grau
|
||||
|
||||
func set_color(class_name: String) -> void:
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = CLASS_COLORS.get(class_name, DEFAULT_COLOR)
|
||||
($Capsule as MeshInstance3D).material_override = mat
|
||||
|
||||
func set_label(player_name: String) -> void:
|
||||
($NameLabel as Label3D).text = player_name
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: character_placeholder.tscn via Godot-Editor erstellen**
|
||||
|
||||
Neue Szene: `Datei → Neue Szene → Node3D als Root`.
|
||||
Root umbenennen in `CharacterPlaceholder`, Script `character_placeholder.gd` zuweisen.
|
||||
|
||||
Kinder-Nodes hinzufügen:
|
||||
|
||||
**MeshInstance3D** mit Name `Capsule`:
|
||||
- `mesh` = neues `CapsuleMesh`
|
||||
- CapsuleMesh-Properties: `height = 1.8`, `radius = 0.3`
|
||||
- `material_override` = neues `StandardMaterial3D`, `albedo_color = Color(0.65, 0.65, 0.65)`
|
||||
|
||||
**Label3D** mit Name `NameLabel`:
|
||||
- `text = ""`
|
||||
- `position = Vector3(0.0, 1.2, 0.0)`
|
||||
- `font_size = 48`
|
||||
- `billboard = BILLBOARD_ENABLED`
|
||||
- `no_depth_test = true`
|
||||
|
||||
Szene als `ruf-der-pilze/scenes/character_placeholder.tscn` speichern.
|
||||
|
||||
- [ ] **Schritt 3: Parse-Fehler prüfen**
|
||||
|
||||
Godot-Editor: Ausgabe-Panel prüfen, keine Fehler in `character_placeholder.gd`.
|
||||
|
||||
- [ ] **Schritt 4: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scenes/character_placeholder.tscn ruf-der-pilze/scripts/character_placeholder.gd
|
||||
git commit -m "feat: add CharacterPlaceholder scene — colored capsule + name label"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: LobbySpot-Marker in tavern_lobby.tscn
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scenes/tavern_lobby.tscn`
|
||||
|
||||
`tavern_lobby.tscn` instanziiert `taproom.tscn` als Sub-Scene und hat einen CanvasLayer für UI. Die Marker3D-Nodes kommen in die 3D-Welt (unter der Root-Node, nicht unter CanvasLayer).
|
||||
|
||||
- [ ] **Schritt 1: 5 Marker3D-Nodes in tavern_lobby.tscn hinzufügen**
|
||||
|
||||
Im Godot-Editor `tavern_lobby.tscn` öffnen. Im Scene-Panel: Root-Node (oder die Node3D direkt unterhalb der Root) → `Marker3D` hinzufügen. Fünfmal wiederholen.
|
||||
|
||||
Namen und Positionen:
|
||||
```
|
||||
Marker3D "LobbySpot0" — position = Vector3(-2.0, 0.0, 1.0)
|
||||
Marker3D "LobbySpot1" — position = Vector3(-1.0, 0.0, 1.5)
|
||||
Marker3D "LobbySpot2" — position = Vector3( 0.0, 0.0, 1.8)
|
||||
Marker3D "LobbySpot3" — position = Vector3( 1.0, 0.0, 1.5)
|
||||
Marker3D "LobbySpot4" — position = Vector3( 2.0, 0.0, 1.0)
|
||||
```
|
||||
|
||||
Koordinaten ggf. anpassen damit Kapseln im Gastraum stehen (nicht in Wänden). Orientierung der Marker: `rotation_degrees.y = 180` so dass Kapseln zur Kamera schauen.
|
||||
|
||||
- [ ] **Schritt 2: Screenshot via godot-mcp prüfen**
|
||||
|
||||
Godot-Editor: Szene kurz ausführen oder Viewport nutzen um zu prüfen ob Positionen im Gastraum liegen. Anpassen falls nötig.
|
||||
|
||||
- [ ] **Schritt 3: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scenes/tavern_lobby.tscn
|
||||
git commit -m "lobby: add 5 LobbySpot Marker3D for capsule placement"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Capsule-Spawn-Logik in tavern_lobby.gd
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scripts/tavern_lobby.gd`
|
||||
|
||||
Zuerst das Script lesen um bestehende Struktur zu verstehen. Neue Felder und Methoden hinzufügen — bestehenden Code nicht anfassen.
|
||||
|
||||
- [ ] **Schritt 1: Capsule-Tracking-Variablen + Preload hinzufügen**
|
||||
|
||||
Am Anfang der Klasse (nach bestehenden `var`-Deklarationen):
|
||||
|
||||
```gdscript
|
||||
const CharacterPlaceholder := preload("res://scenes/character_placeholder.tscn")
|
||||
var _capsules: Dictionary = {} # peer_id: int → Node3D
|
||||
var _lobby_spots: Array[Marker3D] = []
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Spot-Array + Signal-Verbindungen in `_ready()` ergänzen**
|
||||
|
||||
Am Ende von `_ready()` anhängen:
|
||||
|
||||
```gdscript
|
||||
_lobby_spots = [
|
||||
$LobbySpot0 as Marker3D,
|
||||
$LobbySpot1 as Marker3D,
|
||||
$LobbySpot2 as Marker3D,
|
||||
$LobbySpot3 as Marker3D,
|
||||
$LobbySpot4 as Marker3D,
|
||||
]
|
||||
NetworkManager.player_joined.connect(_on_player_joined_capsule)
|
||||
NetworkManager.player_left.connect(_on_player_left_capsule)
|
||||
NetworkManager.character_updated.connect(_on_character_updated_capsule)
|
||||
```
|
||||
|
||||
Pfade `$LobbySpot0` etc. anpassen wenn Marker3D-Nodes unter einer anderen Parent-Node liegen.
|
||||
|
||||
- [ ] **Schritt 3: Spawn/Remove/Update-Methoden hinzufügen**
|
||||
|
||||
```gdscript
|
||||
func _on_player_joined_capsule(peer_id: int, _data: Dictionary) -> void:
|
||||
if _capsules.has(peer_id):
|
||||
return
|
||||
var spot_index := _capsules.size() % _lobby_spots.size()
|
||||
var capsule: Node3D = CharacterPlaceholder.instantiate()
|
||||
add_child(capsule)
|
||||
capsule.global_position = _lobby_spots[spot_index].global_position
|
||||
var player_data: Dictionary = NetworkManager.players.get(peer_id, {})
|
||||
capsule.set_label(player_data.get("name", "???"))
|
||||
capsule.set_color(player_data.get("char", {}).get("class", ""))
|
||||
_capsules[peer_id] = capsule
|
||||
|
||||
func _on_player_left_capsule(peer_id: int) -> void:
|
||||
if not _capsules.has(peer_id):
|
||||
return
|
||||
(_capsules[peer_id] as Node3D).queue_free()
|
||||
_capsules.erase(peer_id)
|
||||
|
||||
func _on_character_updated_capsule(peer_id: int, data: Dictionary) -> void:
|
||||
if not _capsules.has(peer_id):
|
||||
return
|
||||
(_capsules[peer_id] as Node3D).set_color(data.get("class", ""))
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: Godot öffnen, Parse-Fehler prüfen**
|
||||
|
||||
- [ ] **Schritt 5: End-to-End-Test**
|
||||
|
||||
Server + 2 Spieler-Clients starten.
|
||||
- Spieler A joined → Kapsel A erscheint an LobbySpot0 (grau, kein char-Daten)
|
||||
- Spieler B joined → Kapsel B erscheint an LobbySpot1
|
||||
- Wenn Schritt 12 gemergt: Spieler A klickt "Magier" → Kapsel A wird blau
|
||||
- Spieler A verlässt → Kapsel A verschwindet
|
||||
|
||||
- [ ] **Schritt 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scripts/tavern_lobby.gd
|
||||
git commit -m "lobby: spawn colored capsule avatars for each player at LobbySpots"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: STATUS.md updaten
|
||||
|
||||
- [ ] **Schritt 1: Schritt 13 als ✅ markieren in `docs/STATUS.md` und `ruf-der-pilze/CLAUDE.md`**
|
||||
|
||||
- [ ] **Schritt 2: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/STATUS.md ruf-der-pilze/CLAUDE.md
|
||||
git commit -m "docs: mark Schritt 13 complete — Charakter-Capsules"
|
||||
```
|
||||
313
docs/plans/2026-04-16-schritt-14-tavern-art-pass.md
Normal file
313
docs/plans/2026-04-16-schritt-14-tavern-art-pass.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# Schritt 14 — Tavern Art Pass
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** `taproom.tscn` (Gastraum EG, geteilt von Lobby + In-Game-Taverne) und die Obergeschoss-Zimmer in `tavern.tscn` bekommen echte 3D-Props statt Placeholder-Geometrie. Quellen: Polyhaven (CC0, kostenlos) via `blender-mcp`, optional Hyper3D/Meshy für fehlende Assets.
|
||||
|
||||
**Architecture:** Assets via Blender-MCP downloaden/generieren → Skala normalisieren → als `.glb` nach `ruf-der-pilze/assets/props/` exportieren → via Godot-Editor in Szenen platzieren. `taproom.tscn` hat kein Script — Props als reine MeshInstance3D-Nodes einfügen. Keine Logic-Änderungen an Scripts.
|
||||
|
||||
**Tech Stack:** Godot 4.x, Blender (blender-mcp MCP-Server), Polyhaven (CC0 Models), godot-mcp Screenshot-Loop
|
||||
|
||||
**Asset-Liste:**
|
||||
|
||||
| Asset | Anzahl | Szene | Polyhaven-Suche |
|
||||
|-------|--------|-------|-----------------|
|
||||
| Holztisch | 4–5 | taproom | `"wooden table"` / `"pub table"` |
|
||||
| Holzstuhl/Hocker | 8–12 | taproom | `"wooden chair"` / `"stool"` |
|
||||
| Bar-Counter/Tresen | 1 | taproom | `"bar counter"` (evtl. Hyper3D) |
|
||||
| Fässer (Barrel) | 3–5 | taproom | `"barrel"` / `"wooden barrel"` |
|
||||
| Kerzenhalter | 4–6 | taproom | `"candle"` / `"candlestick"` |
|
||||
| Bett (einfach) | 1 Template | tavern OG | `"bed"` / `"medieval bed"` |
|
||||
| Kleiner Tisch | 1 Template | tavern OG | (Wiederverwendung Tisch) |
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Aktion |
|
||||
|------|--------|
|
||||
| `ruf-der-pilze/assets/props/` | Verzeichnis anlegen, .glb Dateien ablegen |
|
||||
| `ruf-der-pilze/scenes/taproom.tscn` | Modify — Props platzieren (Tische, Stühle, Tresen, Fässer, Kerzen) |
|
||||
| `ruf-der-pilze/scenes/tavern.tscn` | Modify — Zimmer-Props (Bett, Tisch, Stuhl, Kerze) |
|
||||
| `docs/STATUS.md` | Modify — Schritt 14 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Assets via Polyhaven-MCP suchen und downloaden
|
||||
|
||||
**Vorbedingung:** Blender muss offen sein und der blender-mcp-Server muss laufen.
|
||||
|
||||
- [ ] **Schritt 1: Polyhaven-Kategorien abrufen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__get_polyhaven_categories
|
||||
```
|
||||
|
||||
Kategorien für Models notieren (sollte "models" o.ä. enthalten).
|
||||
|
||||
- [ ] **Schritt 2: Tisch-Assets suchen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__search_polyhaven_assets
|
||||
Params: type="models", query="wooden table"
|
||||
```
|
||||
|
||||
Und:
|
||||
```
|
||||
Tool: mcp__blender__search_polyhaven_assets
|
||||
Params: type="models", query="pub table"
|
||||
```
|
||||
|
||||
Passenden Asset-ID notieren (z.B. `pub_table`, `round_wooden_table`).
|
||||
|
||||
- [ ] **Schritt 3: Stuhl/Hocker-Assets suchen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__search_polyhaven_assets
|
||||
Params: type="models", query="wooden stool"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: Fass-Assets suchen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__search_polyhaven_assets
|
||||
Params: type="models", query="barrel"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 5: Kerzen-Assets suchen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__search_polyhaven_assets
|
||||
Params: type="models", query="candle"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 6: Bett-Assets suchen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__search_polyhaven_assets
|
||||
Params: type="models", query="bed"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 7: Gefundene Assets herunterladen (nacheinander)**
|
||||
|
||||
Für jeden Asset:
|
||||
```
|
||||
Tool: mcp__blender__download_polyhaven_asset
|
||||
Params: asset_id="<id>", asset_type="models", resolution="1k"
|
||||
```
|
||||
|
||||
Status pollen:
|
||||
```
|
||||
Tool: mcp__blender__get_polyhaven_status
|
||||
```
|
||||
|
||||
Warten bis `status = "completed"`.
|
||||
|
||||
- [ ] **Schritt 8: Falls Bar-Counter nicht auf Polyhaven: Hyper3D nutzen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__generate_hyper3d_model_via_text
|
||||
Params: prompt="medieval tavern bar counter, dark wood, worn surface, low poly game asset, simple geometry"
|
||||
```
|
||||
|
||||
Status pollen:
|
||||
```
|
||||
Tool: mcp__blender__get_hyper3d_status
|
||||
```
|
||||
|
||||
Asset importieren:
|
||||
```
|
||||
Tool: mcp__blender__import_generated_asset
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Assets in Blender normalisieren und als .glb exportieren
|
||||
|
||||
- [ ] **Schritt 1: Blender-Szene prüfen**
|
||||
|
||||
```
|
||||
Tool: mcp__blender__get_scene_info
|
||||
Tool: mcp__blender__get_viewport_screenshot
|
||||
```
|
||||
|
||||
Alle heruntergeladenen Assets sollten in der Szene sichtbar sein.
|
||||
|
||||
- [ ] **Schritt 2: Assets-Verzeichnis anlegen**
|
||||
|
||||
```bash
|
||||
mkdir -p /home/mpuchstein/Dev/Godot/DnD_Anna_OneShot/ruf-der-pilze/assets/props
|
||||
```
|
||||
|
||||
- [ ] **Schritt 3: Skala prüfen und normalisieren**
|
||||
|
||||
Referenz-Maße für mittelalterliche Taverne:
|
||||
- Tisch: ca. 0.75m hoch, 1.2m × 0.8m Fläche
|
||||
- Stuhl/Hocker: ca. 0.45m hoch
|
||||
- Barrel: ca. 0.8m hoch
|
||||
- Kerzenhalter: ca. 0.3m hoch
|
||||
- Bett: ca. 0.5m hoch, 2.0m × 1.0m
|
||||
|
||||
Skala per Blender-Code anpassen (für jeden Asset einzeln ausführen):
|
||||
|
||||
```python
|
||||
# Beispiel: Tisch auf 0.75m Höhe skalieren
|
||||
import bpy
|
||||
obj = bpy.data.objects["<asset_object_name>"] # Namen aus get_scene_info
|
||||
dims = obj.dimensions
|
||||
if dims.z > 0:
|
||||
scale_factor = 0.75 / dims.z
|
||||
obj.scale = (scale_factor, scale_factor, scale_factor)
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.transform_apply(scale=True)
|
||||
```
|
||||
|
||||
```
|
||||
Tool: mcp__blender__execute_blender_code
|
||||
Params: code="<python_code>"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: Assets einzeln als .glb exportieren**
|
||||
|
||||
Für jeden Asset (Objekt selektieren, dann exportieren):
|
||||
|
||||
```python
|
||||
import bpy
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.data.objects["<object_name>"].select_set(True)
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath="/home/mpuchstein/Dev/Godot/DnD_Anna_OneShot/ruf-der-pilze/assets/props/table.glb",
|
||||
export_format='GLB',
|
||||
use_selection=True,
|
||||
export_materials='EXPORT'
|
||||
)
|
||||
```
|
||||
|
||||
Zieldateien:
|
||||
- `assets/props/table.glb`
|
||||
- `assets/props/chair.glb`
|
||||
- `assets/props/barrel.glb`
|
||||
- `assets/props/candle.glb`
|
||||
- `assets/props/bed.glb`
|
||||
- `assets/props/bar_counter.glb`
|
||||
|
||||
- [ ] **Schritt 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/assets/props/
|
||||
git commit -m "assets: import tavern props — table, chair, barrel, candle, bed, bar_counter"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: taproom.tscn mit Props bestücken
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scenes/taproom.tscn`
|
||||
|
||||
`taproom.tscn` ist die Gastraum-Geometrie ohne Script. Hier werden Props als Kinder-Nodes eingefügt. Die Props werden als instanziierte .glb-Szenen oder MeshInstance3D-Nodes platziert.
|
||||
|
||||
- [ ] **Schritt 1: Aktuellen Stand per Screenshot prüfen**
|
||||
|
||||
In Godot: `taproom.tscn` öffnen. Godot-MCP Screenshot machen um die Raumgröße und Geometry zu sehen:
|
||||
```
|
||||
Tool: mcp__godot-mcp__scene
|
||||
Params: action="get_node_properties", scene_path="res://scenes/taproom.tscn", node_path="."
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Bar-Counter an Wand platzieren**
|
||||
|
||||
Im Godot-Editor: `bar_counter.glb` (aus `assets/props/`) per Drag & Drop in `taproom.tscn` ziehen, oder Node3D hinzufügen und Mesh zuweisen. Position entlang einer Wand:
|
||||
- Ungefähre Position: `Vector3(-4.0, 0.0, 0.0)` — an der linken Wand
|
||||
- Rotation anpassen damit Counter zur Mitte zeigt
|
||||
|
||||
- [ ] **Schritt 3: 4 Tische im Raum verteilen**
|
||||
|
||||
Vier Tisch-Nodes platzieren, gleichmäßig verteilt:
|
||||
```
|
||||
Tisch 1: Vector3(-1.5, 0.0, -2.0)
|
||||
Tisch 2: Vector3( 1.5, 0.0, -2.0)
|
||||
Tisch 3: Vector3(-1.5, 0.0, 1.0)
|
||||
Tisch 4: Vector3( 1.5, 0.0, 1.0)
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: Stühle/Hocker um jeden Tisch**
|
||||
|
||||
Je 2–4 Stühle um jeden Tisch. Versatz vom Tisch-Zentrum: ca. 0.7m in jede Richtung.
|
||||
|
||||
- [ ] **Schritt 5: 3–4 Fässer nahe Tresen**
|
||||
|
||||
Fässer geclustert nahe dem Bar-Counter. Position: entlang der Tresen-Wand.
|
||||
|
||||
- [ ] **Schritt 6: Kerzenhalter auf Tischen + Wandbefestigung**
|
||||
|
||||
Je 1 Kerzenhalter auf jedem Tisch (Y-Position = Tischhöhe ~0.75). 2 Kerzenhalter an Wänden.
|
||||
|
||||
- [ ] **Schritt 7: Screenshot via godot-mcp, Iterationen bis es gut aussieht**
|
||||
|
||||
Mindestens 2–3 Korrekturrunden. Ziel: bewohnter, atmosphärischer Gastraum.
|
||||
|
||||
- [ ] **Schritt 8: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scenes/taproom.tscn
|
||||
git commit -m "art: taproom props — tables, chairs, bar counter, barrels, candles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Zimmer in tavern.tscn bestücken
|
||||
|
||||
**Files:** Modify `ruf-der-pilze/scenes/tavern.tscn`
|
||||
|
||||
Die Taverne hat 9 Zimmer im OG (SpawnPoint0–SpawnPoint8). Zuerst **Zimmer 1 (SpawnPoint0)** vollständig einrichten, dann das Muster für alle weiteren Zimmer wiederholen.
|
||||
|
||||
- [ ] **Schritt 1: tavern.tscn öffnen und SpawnPoint0-Zimmer lokalisieren**
|
||||
|
||||
Godot-Editor: `tavern.tscn` öffnen. Im Scene-Panel die SpawnPoint0-Node finden und deren Position notieren.
|
||||
|
||||
- [ ] **Schritt 2: Zimmer 1 mit Props bestücken**
|
||||
|
||||
Props relativ zu SpawnPoint0-Position (Offsets anpassen nach Zimmergeometrie):
|
||||
- **Bett:** SpawnPoint.position + Vector3(1.2, 0.0, 0.5), an Wand
|
||||
- **Kleiner Tisch:** SpawnPoint.position + Vector3(-1.0, 0.0, -0.8)
|
||||
- **Stuhl:** neben Tisch, Offset ~0.5m
|
||||
- **Kerzenhalter:** auf Tisch, Y = Tischhöhe
|
||||
|
||||
- [ ] **Schritt 3: Screenshot prüfen**
|
||||
|
||||
Godot-Editor Viewport oder godot-mcp Screenshot für Zimmer 1.
|
||||
|
||||
- [ ] **Schritt 4: Zimmer 2–9 analog bestücken**
|
||||
|
||||
Für SpawnPoint1–SpawnPoint8 dieselben Props einfügen. Positionen relativ zum jeweiligen SpawnPoint.
|
||||
|
||||
- [ ] **Schritt 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruf-der-pilze/scenes/tavern.tscn
|
||||
git commit -m "art: tavern OG rooms — bed, table, chair, candle per room (9 rooms)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Gesamt-Test + STATUS.md
|
||||
|
||||
- [ ] **Schritt 1: Spiel starten und Taverne visuell prüfen**
|
||||
|
||||
Server + 2 Clients starten (oder Godot-Editor ausführen):
|
||||
- `tavern_lobby.tscn`: Gastraum sieht bewohnt aus
|
||||
- `tavern.tscn` nach game_started: EG-Gastraum mit Props, OG-Zimmer mit Bett/Tisch/Kerze
|
||||
- Performance: FPS sollte bei >30 bleiben (Godot-Editor Statistiken)
|
||||
|
||||
- [ ] **Schritt 2: Schritt 14 als ✅ markieren**
|
||||
|
||||
In `docs/STATUS.md` und `ruf-der-pilze/CLAUDE.md`.
|
||||
|
||||
- [ ] **Schritt 3: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/STATUS.md ruf-der-pilze/CLAUDE.md
|
||||
git commit -m "docs: mark Schritt 14 complete — Tavern Art Pass"
|
||||
```
|
||||
Reference in New Issue
Block a user