From e30c49d1d3d264b8d036defeeef87414acbe918a Mon Sep 17 00:00:00 2001 From: "s0wlz (Matthias Puchstein)" Date: Tue, 14 Apr 2026 03:20:11 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20dm=5Fview=20=E2=80=94=20restore=20full?= =?UTF-8?q?=20implementation=20(top-down,=20markers,=20player=20cams,=20ov?= =?UTF-8?q?erlay=20panel)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruf-der-pilze/scenes/dm_view.tscn | 66 ++++++--- ruf-der-pilze/scripts/dm_view.gd | 213 +++++++++++++++++++++++++++++- 2 files changed, 259 insertions(+), 20 deletions(-) diff --git a/ruf-der-pilze/scenes/dm_view.tscn b/ruf-der-pilze/scenes/dm_view.tscn index ff2337e..3b2ef79 100644 --- a/ruf-der-pilze/scenes/dm_view.tscn +++ b/ruf-der-pilze/scenes/dm_view.tscn @@ -5,27 +5,55 @@ [node name="DmView" type="Node"] script = ExtResource("1") -[node name="ViewportContainer" type="SubViewportContainer" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 +[node name="RootLayout" type="VBoxContainer" parent="."] -[node name="SubViewport" type="SubViewport" parent="ViewportContainer"] +[node name="TopSection" type="HSplitContainer" parent="RootLayout"] +size_flags_vertical = 3 -[node name="Camera3D" type="Camera3D" parent="ViewportContainer/SubViewport"] -transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 20, 0) +[node name="MapContainer" type="SubViewportContainer" parent="RootLayout/TopSection"] +custom_minimum_size = Vector2(700, 500) +size_flags_horizontal = 3 +stretch = true + +[node name="MapViewport" type="SubViewport" parent="RootLayout/TopSection/MapContainer"] +size = Vector2i(700, 500) + +[node name="MapRoot" type="Node3D" parent="RootLayout/TopSection/MapContainer/MapViewport"] + +[node name="PlayerMarkers" type="Node3D" parent="RootLayout/TopSection/MapContainer/MapViewport/MapRoot"] + +[node name="TopDownCam" type="Camera3D" parent="RootLayout/TopSection/MapContainer/MapViewport/MapRoot"] +transform = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, 2, 25, 0) current = true -[node name="UI" type="CanvasLayer" parent="."] +[node name="SidePanel" type="VBoxContainer" parent="RootLayout/TopSection"] +custom_minimum_size = Vector2(220, 0) -[node name="StubLabel" type="Label" parent="UI"] -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -150.0 -offset_top = -30.0 -offset_right = 150.0 -offset_bottom = 30.0 -text = "DM View — Stub -(wird in Schritt 7 ausgebaut)" -horizontal_alignment = 1 +[node name="LblFloor" type="Label" parent="RootLayout/TopSection/SidePanel"] +text = "Etage" + +[node name="FloorButtons" type="HBoxContainer" parent="RootLayout/TopSection/SidePanel"] + +[node name="BtnEG" type="Button" parent="RootLayout/TopSection/SidePanel/FloorButtons"] +text = "Erdgeschoss" +toggle_mode = true +button_pressed = true + +[node name="BtnOG" type="Button" parent="RootLayout/TopSection/SidePanel/FloorButtons"] +text = "Obergeschoss" +toggle_mode = true + +[node name="HSeparator" type="HSeparator" parent="RootLayout/TopSection/SidePanel"] + +[node name="LblOverlay" type="Label" parent="RootLayout/TopSection/SidePanel"] +text = "Overlays" + +[node name="OverlayScroll" type="ScrollContainer" parent="RootLayout/TopSection/SidePanel"] +size_flags_vertical = 3 + +[node name="PlayerList" type="VBoxContainer" parent="RootLayout/TopSection/SidePanel/OverlayScroll"] + +[node name="HSeparator2" type="HSeparator" parent="RootLayout"] + +[node name="PlayerCamsRow" type="HBoxContainer" parent="RootLayout"] +custom_minimum_size = Vector2(0, 200) diff --git a/ruf-der-pilze/scripts/dm_view.gd b/ruf-der-pilze/scripts/dm_view.gd index f753936..576adfe 100644 --- a/ruf-der-pilze/scripts/dm_view.gd +++ b/ruf-der-pilze/scripts/dm_view.gd @@ -1,5 +1,216 @@ extends Node +const TAVERN_SCENE := "res://scenes/tavern.tscn" +const CAM_EG_Y := 25.0 +const CAM_OG_Y := 30.0 +const OVERLAY_CYCLE := ["default", "spore_active"] + +var _top_down_cam: Camera3D +var _player_markers: Node3D +var _markers_by_id: Dictionary = {} # peer_id → MeshInstance3D +var _player_cam_cams: Dictionary = {} # peer_id → Camera3D (in SubViewport) +var _showing_upper_floor: bool = false + func _ready() -> void: - print("[DmView] DM-Szene geladen (Stub)") + $RootLayout.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + _load_tavern_into_viewport() + _setup_floor_buttons() + _setup_player_cams() + _setup_overlay_panel() + + +func _load_tavern_into_viewport() -> void: + var packed := ResourceLoader.load(TAVERN_SCENE, "PackedScene") as PackedScene + if packed == null: + push_error("[DmView] tavern.tscn nicht gefunden — wird nach Schritt 5 verfügbar sein") + return + var map_root := $RootLayout/TopSection/MapContainer/MapViewport/MapRoot as Node3D + _top_down_cam = map_root.get_node("TopDownCam") as Camera3D + _player_markers = map_root.get_node("PlayerMarkers") as Node3D + var tavern := packed.instantiate() + tavern.name = "Tavern" + map_root.add_child(tavern) + print("[DmView] Tavern in SubViewport geladen") + + +func _setup_floor_buttons() -> void: + var btn_eg := $RootLayout/TopSection/SidePanel/FloorButtons/BtnEG as Button + var btn_og := $RootLayout/TopSection/SidePanel/FloorButtons/BtnOG as Button + btn_eg.pressed.connect(func() -> void: _switch_floor(false)) + btn_og.pressed.connect(func() -> void: _switch_floor(true)) + + +func _switch_floor(upper: bool) -> void: + _showing_upper_floor = upper + if _top_down_cam == null: + return + _top_down_cam.global_position.y = CAM_OG_Y if upper else CAM_EG_Y + print("[DmView] Etage gewechselt → %s" % ("OG" if upper else "EG")) + + +# --- Player Cam Feeds (Task 7) --- + +func _setup_player_cams() -> void: + NetworkManager.player_joined.connect(_on_player_joined_cam) + NetworkManager.player_left.connect(_on_player_left_cam) + for peer_id in NetworkManager.players.keys(): + var info: Dictionary = NetworkManager.players[peer_id] + if info.get("role", "") == "player": + _create_player_cam(peer_id, info.get("name", "???")) + + +func _on_player_joined_cam(peer_id: int, player_name: String, role: String) -> void: + if role == "player": + _create_player_cam(peer_id, player_name) + + +func _on_player_left_cam(peer_id: int) -> void: + var row := $RootLayout/PlayerCamsRow + var panel := row.get_node_or_null("CamPanel_%d" % peer_id) + if panel != null: + panel.queue_free() + _player_cam_cams.erase(peer_id) + + +func _create_player_cam(peer_id: int, player_name: String) -> void: + var packed := ResourceLoader.load(TAVERN_SCENE, "PackedScene") as PackedScene + if packed == null: + push_error("[DmView] tavern.tscn nicht gefunden für Player-Cam") + return + + var viewport := SubViewport.new() + viewport.size = Vector2i(320, 180) + + var scene_root := Node3D.new() + var tavern := packed.instantiate() + scene_root.add_child(tavern) + + var cam := Camera3D.new() + cam.name = "PlayerCam" + cam.current = true + scene_root.add_child(cam) + viewport.add_child(scene_root) + + var container := SubViewportContainer.new() + container.name = "CamPanel_%d" % peer_id + container.custom_minimum_size = Vector2(320, 180) + container.stretch = true + container.add_child(viewport) + + var wrapper := VBoxContainer.new() + var label := Label.new() + label.text = player_name + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + wrapper.add_child(label) + wrapper.add_child(container) + + $RootLayout/PlayerCamsRow.add_child(wrapper) + _player_cam_cams[peer_id] = cam + print("[DmView] Player-Cam erstellt für %s (Peer %d)" % [player_name, peer_id]) + + +# --- Overlay Panel (Task 8) --- + +func _setup_overlay_panel() -> void: + NetworkManager.player_joined.connect(_on_player_joined) + NetworkManager.player_left.connect(_on_player_left) + for peer_id in NetworkManager.players.keys(): + var info: Dictionary = NetworkManager.players[peer_id] + if info.get("role", "") == "player": + _add_overlay_row(peer_id, info.get("name", "???")) + + +func _on_player_joined(peer_id: int, player_name: String, role: String) -> void: + if role == "player": + _add_overlay_row(peer_id, player_name) + + +func _on_player_left(peer_id: int) -> void: + var list := $RootLayout/TopSection/SidePanel/OverlayScroll/PlayerList as VBoxContainer + var row := list.get_node_or_null("Row_%d" % peer_id) + if row != null: + row.queue_free() + + +func _add_overlay_row(peer_id: int, player_name: String) -> void: + var list := $RootLayout/TopSection/SidePanel/OverlayScroll/PlayerList as VBoxContainer + var row := HBoxContainer.new() + row.name = "Row_%d" % peer_id + var label := Label.new() + label.text = player_name + label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + var btn := Button.new() + btn.text = "● default" + btn.pressed.connect(func() -> void: _toggle_overlay(peer_id, btn)) + row.add_child(label) + row.add_child(btn) + list.add_child(row) + + +func _toggle_overlay(peer_id: int, btn: Button) -> void: + var current := GameState.get_overlay(peer_id) + var idx := OVERLAY_CYCLE.find(current) + var next: String = OVERLAY_CYCLE[(idx + 1) % OVERLAY_CYCLE.size()] + NetworkManager.request_set_overlay.rpc_id(1, peer_id, next) + GameState.set_overlay_local(peer_id, next) + btn.text = "● %s" % next + print("[DmView] Overlay-Toggle → Spieler %d: %s" % [peer_id, next]) + + +# --- _process: Marker + Cam updates --- + +func _process(_delta: float) -> void: + _update_player_markers() + _update_player_cams() + + +func _update_player_markers() -> void: + if _player_markers == null: + return + for peer_id in GameState.player_positions.keys(): + var data: Dictionary = GameState.player_positions[peer_id] + var pos: Vector3 = data.get("position", Vector3.ZERO) + var rot: Vector3 = data.get("rotation", Vector3.ZERO) + var marker := _get_or_create_marker(peer_id) + marker.global_position = pos + Vector3(0, 0.2, 0) + marker.rotation.y = rot.y + var is_upper := pos.y > 3.5 + marker.visible = (is_upper == _showing_upper_floor) + + +func _get_or_create_marker(peer_id: int) -> MeshInstance3D: + if peer_id in _markers_by_id: + return _markers_by_id[peer_id] + var mesh := MeshInstance3D.new() + var sphere := SphereMesh.new() + sphere.radius = 0.25 + sphere.height = 0.5 + mesh.mesh = sphere + var mat := StandardMaterial3D.new() + mat.albedo_color = _peer_color(peer_id) + mat.emission_enabled = true + mat.emission = mat.albedo_color + mat.emission_energy_multiplier = 0.5 + mesh.material_override = mat + _player_markers.add_child(mesh) + _markers_by_id[peer_id] = mesh + print("[DmView] Marker erstellt für Peer %d" % peer_id) + return mesh + + +func _peer_color(peer_id: int) -> Color: + var colors := [Color.RED, Color.CYAN, Color.YELLOW, Color.MAGENTA, Color.GREEN] + return colors[peer_id % colors.size()] + + +func _update_player_cams() -> void: + for peer_id in _player_cam_cams.keys(): + if not GameState.player_positions.has(peer_id): + continue + var data: Dictionary = GameState.player_positions[peer_id] + var pos: Vector3 = data.get("position", Vector3.ZERO) + var rot: Vector3 = data.get("rotation", Vector3.ZERO) + var cam: Camera3D = _player_cam_cams[peer_id] + cam.global_position = pos + cam.global_rotation = rot