Collisions for Area Change Trigger

This commit is contained in:
torsten 2026-02-15 14:51:00 +01:00
parent 721f005ff3
commit 1eeca653f5
5 changed files with 74 additions and 70 deletions

View file

@ -1,7 +1,3 @@
# Projektübersicht: weltenauge
Dieses Dokument dient als zentrale Wissensbasis für das Godot-Projekt "weltenauge". Es soll dem KI-Assistenten und den Entwicklern helfen, den Überblick über die Architektur und die Ziele des Projekts zu behalten.
## 1. Projektziel & Vision ## 1. Projektziel & Vision
"weltenauge" ist ein von Neverwinter Nights (NWN) inspiriertes Multiplayer-Rollenspiel. Das Kernstück ist eine robuste Client/Server-Architektur, die in einer einzigen Godot-Codebase umgesetzt wird. "weltenauge" ist ein von Neverwinter Nights (NWN) inspiriertes Multiplayer-Rollenspiel. Das Kernstück ist eine robuste Client/Server-Architektur, die in einer einzigen Godot-Codebase umgesetzt wird.
@ -131,4 +127,3 @@ Die Konfiguration des `MultiplayerSynchronizer` für wiederverwendbare Szenen (w
sync.set_visibility_for(id, true) sync.set_visibility_for(id, true)
# ... # ...
``` ```

View file

@ -18,6 +18,9 @@ albedo_color = Color(9.3854965e-07, 0.4579985, 0.32070312, 1)
[sub_resource type="PlaneMesh" id="PlaneMesh_0iae2"] [sub_resource type="PlaneMesh" id="PlaneMesh_0iae2"]
material = SubResource("StandardMaterial3D_30klc") material = SubResource("StandardMaterial3D_30klc")
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_1yg6u"]
data = PackedVector3Array(1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0, -1, 1, 0, -1)
[node name="keuwin_grasland" type="Node3D" unique_id=1953380732] [node name="keuwin_grasland" type="Node3D" unique_id=1953380732]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1262732162] [node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1262732162]
@ -35,6 +38,11 @@ environment = SubResource("Environment_8r0t0")
transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0) transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0)
mesh = SubResource("PlaneMesh_0iae2") mesh = SubResource("PlaneMesh_0iae2")
[node name="StaticBody3D" type="StaticBody3D" parent="Architecture/MeshInstance3D" unique_id=209028061]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Architecture/MeshInstance3D/StaticBody3D" unique_id=1119853048]
shape = SubResource("ConcavePolygonShape3D_1yg6u")
[node name="Navigation" type="Node3D" parent="." unique_id=1472582667] [node name="Navigation" type="Node3D" parent="." unique_id=1472582667]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.01873529, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.01873529, 0)

View file

@ -21,6 +21,9 @@ albedo_color = Color(0.1800012, 0.004964735, 0.2305904, 1)
[sub_resource type="PlaneMesh" id="PlaneMesh_0iae2"] [sub_resource type="PlaneMesh" id="PlaneMesh_0iae2"]
material = SubResource("StandardMaterial3D_30klc") material = SubResource("StandardMaterial3D_30klc")
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_0hy1c"]
data = PackedVector3Array(1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0, -1, 1, 0, -1)
[node name="halle_der_ahnen" type="Node3D" unique_id=1953380732] [node name="halle_der_ahnen" type="Node3D" unique_id=1953380732]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1262732162] [node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1262732162]
@ -38,6 +41,11 @@ environment = SubResource("Environment_8r0t0")
transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0) transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0)
mesh = SubResource("PlaneMesh_0iae2") mesh = SubResource("PlaneMesh_0iae2")
[node name="StaticBody3D" type="StaticBody3D" parent="Architecture/MeshInstance3D" unique_id=1395982254]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Architecture/MeshInstance3D/StaticBody3D" unique_id=521000493]
shape = SubResource("ConcavePolygonShape3D_0hy1c")
[node name="Navigation" type="Node3D" parent="." unique_id=1472582667] [node name="Navigation" type="Node3D" parent="." unique_id=1472582667]
[node name="SpawnPoints" type="Node3D" parent="." unique_id=438053421] [node name="SpawnPoints" type="Node3D" parent="." unique_id=438053421]

View file

@ -54,7 +54,7 @@ func _on_player_disconnected(id):
func change_player_area(id: int, area_key: String): func change_player_area(id: int, area_key: String):
if not multiplayer.is_server(): return if not multiplayer.is_server(): return
print("Server is loading ", area_key) print("Server is loading ", area_key)
var path = area_paths[area_key var path = area_paths[area_key]
var container_name = area_key + "_container" var container_name = area_key + "_container"
var viewport_name = area_key + "_viewport" var viewport_name = area_key + "_viewport"
@ -111,7 +111,7 @@ func client_ready_to_spawn():
if not multiplayer.is_server(): return if not multiplayer.is_server(): return
if player_worlds.has(id): if player_worlds.has(id):
var viewport_node = player_worlds[id var viewport_node = player_worlds[id]
print("Client ", id, " ist bereit. Spawne jetzt.") print("Client ", id, " ist bereit. Spawne jetzt.")
_spawn_player_in_world(id, viewport_node) _spawn_player_in_world(id, viewport_node)
@ -152,7 +152,8 @@ func client_load_area(area_key):
# Kamera-Setup nur beim ersten Laden oder falls sie gelöscht wurde # Kamera-Setup nur beim ersten Laden oder falls sie gelöscht wurde
if not multiplayer.is_server() and local_camera == null: if not multiplayer.is_server() and not is_instance_valid(local_camera):
print("Setting up camera")
setup_client_camera(viewport_node) setup_client_camera(viewport_node)
# Jetzt können wir dem Server ok fürs Spawnen geben # Jetzt können wir dem Server ok fürs Spawnen geben
@ -225,11 +226,3 @@ func handle_area_change_request(area_key: String):
# 2. Bestehende Funktion für den Wechsel aufrufen # 2. Bestehende Funktion für den Wechsel aufrufen
change_player_area(id, area_key) change_player_area(id, area_key)
## The following script is for a Portal node.
## File: portal.gd
extends Area3D
## Definiert, wohin dieses Portal führt.
## Dieser Wert muss einem Schlüssel in NetworkManager.area_paths entsprechen.
@export var target_area_key: String = ""

View file

@ -76,53 +76,53 @@ func request_move(target: Vector3):
path = [target] path = [target]
target_position = target target_position = target
# ERSETZT die alte _input(event)-Funktion
func _input(event): func _input(event):
# Ist dieser Avatar nicht der des lokalen Spielers? Wenn ja, sofort abbrechen. # Ist dieser Avatar nicht der des lokalen Spielers? Wenn ja, sofort abbrechen.
if name.to_int() != multiplayer.get_unique_id(): return if name.to_int() != multiplayer.get_unique_id(): return
# NUR der Client, der diesen Spieler kontrolliert, darf klicken
#if not is_multiplayer_authority(): return
if DisplayServer.get_name() == "headless": return # Server ignoriert Klicks if DisplayServer.get_name() == "headless": return # Server ignoriert Klicks
if multiplayer.is_server(): return # Kein Input vom Server if multiplayer.is_server(): return # Kein Input vom Server
if event is InputEventMouseButton and event.pressed: # Wir reagieren nur auf den Klick der linken Maustaste
print("Click") if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
var target = _get_click_floor_pos() var click_result = _get_click_result()
if target != Vector3.INF:
print("Request move to target {0} by user {1}".format([target, name])) if click_result: # Prüfen, ob der Strahl überhaupt etwas getroffen hat
# RPC an den Server (ID 1) var collider = click_result.collider
request_move.rpc_id(1, target)
# PRIORITÄT 1: Haben wir ein Portal angeklickt?
# Wir prüfen, ob das getroffene Objekt gültig ist und zur Gruppe "portals" gehört.
if is_instance_valid(collider) and collider.is_in_group("portals"):
print("Portal angeklickt. Fordere Area-Wechsel an zu: ", collider.target_area_key)
# Sende RPC direkt an den NetworkManager mit dem Ziel des Portals
NetworkManager.handle_area_change_request.rpc_id(1, collider.target_area_key)
return # Wichtig: Aktion ist beendet, nichts weiter tun.
# PRIORITÄT 2 (Fallback): Wir haben etwas anderes getroffen (vermutlich den Boden), also bewegen.
var target_position = click_result.position
request_move.rpc_id(1, target_position)
func _get_click_floor_pos():
# Wir brauchen die Kamera, die tatsächlich für diesen Viewport zuständig ist # ERSETZT die alte _get_click_floor_pos()-Funktion
func _get_click_result():
var camera = get_viewport().get_camera_3d() var camera = get_viewport().get_camera_3d()
if not camera: return null
# DEBUG: Falls keine Kamera gefunden wird, wissen wir woran es liegt
if not camera:
print("Keine Kamera im Viewport von ", name, " gefunden!")
return Vector3.INF
var mouse_pos = get_viewport().get_mouse_position() var mouse_pos = get_viewport().get_mouse_position()
var space_state = get_world_3d().direct_space_state
var ray_origin = camera.project_ray_origin(mouse_pos) var ray_origin = camera.project_ray_origin(mouse_pos)
var ray_dir = camera.project_ray_normal(mouse_pos) var ray_dir = camera.project_ray_normal(mouse_pos)
# Da wir in einer isolierten World3D sind, müssen wir den Physics-State
# des Viewports direkt abfragen, statt nur gegen eine Plane zu prüfen:
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_origin + ray_dir * 1000) var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_origin + ray_dir * 1000)
# WICHTIG: Schließe den eigenen Körper von der Abfrage aus, um nicht auf sich selbst zu klicken.
query.exclude = [self]
# NEU: Sage dem Raycast, dass er auch mit Area3D-Nodes kollidieren soll.
query.collide_with_areas = true
# Falls du Layer nutzt: query.collision_mask = 1
var result = space_state.intersect_ray(query) var result = space_state.intersect_ray(query)
return result # Gibt das komplette Ergebnis-Dictionary zurück
if result:
return result.position
# Fallback auf Plane, falls kein Mesh getroffen wurde
var world_plane = Plane(Vector3.UP, 0)
var intersection = world_plane.intersects_ray(ray_origin, ray_dir)
return intersection if intersection else Vector3.INF
func _process(_delta): func _process(_delta):