feat: dm_view — restore full implementation (top-down, markers, player cams, overlay panel)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user