First working area switch
This commit is contained in:
parent
1eeca653f5
commit
e7ebb20428
4 changed files with 53 additions and 46 deletions
|
|
@ -55,6 +55,11 @@ Alle Skripte und die dazugehörige Logik sollen thematisch in Unterordnern unter
|
|||
## 5. Implementierungsdetails & Konventionen
|
||||
|
||||
- **Click-to-Move:** Die Spielerbewegung soll aus Performancegründen über eine AStar-Implementierung (`systems/navigation/`) gesteuert werden.
|
||||
- **GDScript-Konvention: `async`-Schlüsselwort vermeiden:** In GDScript 4 wird jede Funktion, die `await` enthält, automatisch von der Engine als asynchron behandelt. Das `async`-Schlüsselwort in der Funktionssignatur (`async func...`) ist optional und hat in diesem Projekt wiederholt zu schwer diagnostizierbaren Syntaxfehlern geführt.
|
||||
- **Regel:** Das `async`-Schlüsselwort **darf nicht** verwendet werden.
|
||||
- **Korrekt:** `func my_function(): await get_tree().process_frame`
|
||||
- **Falsch:** `async func my_function(): await get_tree().process_frame`
|
||||
|
||||
|
||||
## 6. Globale Skripte (Autoloads)
|
||||
|
||||
|
|
@ -103,6 +108,10 @@ Dieser Abschnitt beschreibt die Verantwortlichkeiten der zentralen Skripte im Pr
|
|||
|
||||
Dieser Abschnitt dokumentiert wichtige technische Lektionen, die während der Entwicklung gelernt wurden.
|
||||
|
||||
### Technische Detail
|
||||
|
||||
Es gibt kein async Keyword, aber await funktioniert trotzdem. => await get_tree().process_frame
|
||||
|
||||
### Dynamische Konfiguration des MultiplayerSynchronizer
|
||||
|
||||
Die Konfiguration des `MultiplayerSynchronizer` für wiederverwendbare Szenen (wie den Spieler) ist kritisch und muss korrekt gehandhabt werden, um Timing- und Pfad-Probleme zu vermeiden. Die folgenden vier Regeln sind entscheidend:
|
||||
|
|
@ -115,15 +124,7 @@ Die Konfiguration des `MultiplayerSynchronizer` für wiederverwendbare Szenen (w
|
|||
- **Korrekt:** `config.add_property(":sync_position")`
|
||||
- **Falsch:** `config.add_property("sync_position")` oder `config.add_property("..:sync_position")`
|
||||
|
||||
4. **Timing im Spawner sicherstellen (`async`/`await`):** Der Code, der den Node instanziiert und dem Baum hinzufügt (z.B. `_spawn_player_in_world` im `NetworkManager`), muss nach dem `add_child()`-Aufruf explizit auf den nächsten Frame warten, bevor er den Synchronizer des neuen Nodes benutzt. Dies stellt sicher, dass dessen `_ready()`-Funktion bereits ausgeführt wurde.
|
||||
```gdscript
|
||||
# Im NetworkManager
|
||||
async func _spawn_player_in_world(id, viewport_node):
|
||||
# ...
|
||||
viewport_node.add_child(player)
|
||||
await get_tree().process_frame # Garantiert, dass _ready() auf player läuft
|
||||
# Jetzt ist die Benutzung des Synchronizers sicher
|
||||
var sync = player.get_node("MultiplayerSynchronizer")
|
||||
sync.set_visibility_for(id, true)
|
||||
# ...
|
||||
```
|
||||
### Dynamische Konfiguration des MultiplayerSpawner
|
||||
|
||||
- Handhabt nicht nur originäre Objekterzeugung in den Levels, sondern auch Objektaktualisierungen für "Late Joiner"
|
||||
- Generischer MultiplayerSpawner in Basis Level, von dem alle Level erben, so dass die Spawnliste nur 1x bearbeitet werden muss
|
||||
|
|
@ -55,3 +55,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.01873529, 0)
|
|||
[node name="Portal" parent="." unique_id=988801188 instance=ExtResource("1_1yg6u")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -6)
|
||||
target_area_key = "ahnenhalle"
|
||||
|
||||
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="." unique_id=58641401]
|
||||
_spawnable_scenes = PackedStringArray("uid://bjurf8vfd1fg8")
|
||||
spawn_path = NodePath("../SpawnPoints")
|
||||
|
|
|
|||
|
|
@ -53,3 +53,7 @@ shape = SubResource("ConcavePolygonShape3D_0hy1c")
|
|||
[node name="Interaction" type="Node3D" parent="." unique_id=1592705952]
|
||||
|
||||
[node name="Triggers" type="Node3D" parent="." unique_id=1507691477]
|
||||
|
||||
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="." unique_id=1610787579]
|
||||
_spawnable_scenes = PackedStringArray("uid://bjurf8vfd1fg8")
|
||||
spawn_path = NodePath("../SpawnPoints")
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ var area_paths = {
|
|||
"ahnenhalle": "res://maps/planes/fugue/halle_der_ahnen.tscn"
|
||||
}
|
||||
|
||||
# Server-seitiges Tracking: peer_id -> SubViewport (World)
|
||||
# Server-seitiges Tracking: peer_id -> SubViewportContainer
|
||||
var player_worlds = {}
|
||||
|
||||
func _ready():
|
||||
|
|
@ -77,17 +77,6 @@ func change_player_area(id: int, area_key: String):
|
|||
else:
|
||||
map_instance = viewport_node.get_node_or_null(area_key)
|
||||
|
||||
# Spawner muss vor laden der map hinzugefügt werden
|
||||
var spawner = viewport_node.get_node_or_null("AreaSpawner")
|
||||
if not spawner:
|
||||
spawner = MultiplayerSpawner.new()
|
||||
spawner.name = "AreaSpawner"
|
||||
print("Viewport Name: " + str(viewport_node.name))
|
||||
# "." bedeutet: Er spawnt Objekte als direkte Kinder des Viewports
|
||||
spawner.add_spawnable_scene("res://systems/player/player.tscn")
|
||||
viewport_node.add_child(spawner)
|
||||
spawner.spawn_path = viewport_node.get_path()
|
||||
|
||||
map_instance = load(path).instantiate()
|
||||
map_instance.name = area_key
|
||||
viewport_node.add_child(map_instance)
|
||||
|
|
@ -97,8 +86,7 @@ func change_player_area(id: int, area_key: String):
|
|||
player_worlds.erase(id)
|
||||
|
||||
# --- NEUEN ZUSTAND REGISTRIEREN ---
|
||||
player_worlds[id] = viewport_node
|
||||
|
||||
player_worlds[id] = container_node
|
||||
|
||||
# Client anweisen, Szene zu laden
|
||||
rpc_id(id, "client_load_area", area_key)
|
||||
|
|
@ -106,14 +94,21 @@ func change_player_area(id: int, area_key: String):
|
|||
# Avatar auf Server erstellen erfolgt erst wenn Client Vollzug meldet (client_ready_to_spawn)
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func client_ready_to_spawn():
|
||||
func client_ready_to_spawn(area_key:String):
|
||||
var id = multiplayer.get_remote_sender_id()
|
||||
if not multiplayer.is_server(): return
|
||||
|
||||
if player_worlds.has(id):
|
||||
var viewport_node = player_worlds[id]
|
||||
print("Client ", id, " ist bereit. Spawne jetzt.")
|
||||
_spawn_player_in_world(id, viewport_node)
|
||||
var container_node = player_worlds[id]
|
||||
var viewport_node = container_node.get_node(area_key+"_viewport")
|
||||
var spawn_target_node = viewport_node.get_node(area_key).get_node("SpawnPoints")
|
||||
|
||||
print("Container Node:", container_node)
|
||||
print("Viewport Node:", viewport_node)
|
||||
print("spawn target Node:", spawn_target_node)
|
||||
|
||||
print("Client ", id, " bereits für Spawning.")
|
||||
_spawn_player_in_world(id, spawn_target_node)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
|
|
@ -139,28 +134,22 @@ func client_load_area(area_key):
|
|||
viewport_node.size = Vector2i(1280,720)
|
||||
container_node.add_child(viewport_node)
|
||||
|
||||
# Spawner muss existieren bevor die map zum Viewport hinzugefügt wird
|
||||
var spawner = MultiplayerSpawner.new()
|
||||
spawner.name = "AreaSpawner"
|
||||
spawner.add_spawnable_scene("res://systems/player/player.tscn")
|
||||
viewport_node.add_child(spawner)
|
||||
spawner.spawn_path = viewport_node.get_path()
|
||||
|
||||
var map = load(area_paths[area_key]).instantiate()
|
||||
map.name = area_key
|
||||
viewport_node.add_child(map)
|
||||
|
||||
|
||||
# Kamera-Setup nur beim ersten Laden oder falls sie gelöscht wurde
|
||||
if not multiplayer.is_server() and not is_instance_valid(local_camera):
|
||||
|
||||
if not multiplayer.is_server(): #and not is_instance_valid(local_camera):
|
||||
print("Setting up camera")
|
||||
setup_client_camera(viewport_node)
|
||||
|
||||
# Jetzt können wir dem Server ok fürs Spawnen geben
|
||||
client_ready_to_spawn.rpc_id(1) # Server ist immer id=1
|
||||
client_ready_to_spawn.rpc_id(1, area_key) # Server ist immer id=1
|
||||
|
||||
func setup_client_camera(node):
|
||||
local_camera = Camera3D.new()
|
||||
local_camera.name = "Camera3D"
|
||||
local_camera.set_script(load("res://systems/player/camera_logic.gd"))
|
||||
local_camera.current = true
|
||||
node.add_child(local_camera)
|
||||
|
|
@ -168,14 +157,14 @@ func setup_client_camera(node):
|
|||
local_camera.add_child(AudioListener3D.new())
|
||||
|
||||
|
||||
func _spawn_player_in_world(id, viewport_node):
|
||||
func _spawn_player_in_world(id, spawn_target_node):
|
||||
var player_scene = load("res://systems/player/player.tscn")
|
||||
var player = player_scene.instantiate()
|
||||
player.name = str(id)
|
||||
|
||||
#player.set_multiplayer_authority(id)
|
||||
player.set_multiplayer_authority(1)
|
||||
viewport_node.add_child(player, true)
|
||||
spawn_target_node.add_child(player, true)
|
||||
|
||||
# SCHRITT 2: Explizit auf den nächsten Frame warten.
|
||||
# Dies gibt Godot die Zeit, die _ready() Funktion des Spielers auszuführen.
|
||||
|
|
@ -186,7 +175,7 @@ func _spawn_player_in_world(id, viewport_node):
|
|||
var sync = player.get_node("MultiplayerSynchronizer")
|
||||
|
||||
# Visibility Filtering: Nur Spieler im gleichen Viewport sehen sich
|
||||
for other in viewport_node.get_children():
|
||||
for other in spawn_target_node.get_children():
|
||||
if other.is_in_group("players"):
|
||||
var other_id = other.name.to_int()
|
||||
# Der neue Spieler sieht den alten
|
||||
|
|
@ -218,11 +207,20 @@ func handle_area_change_request(area_key: String):
|
|||
|
||||
print("Server: Player ", id, " requests area change to ", area_key)
|
||||
|
||||
# 1. Alten Avatar finden und entfernen
|
||||
var old_viewport = player_worlds[id]
|
||||
var player_node = old_viewport.find_child(str(id), true)
|
||||
# 1. Alten Spieler finden und entfernen
|
||||
var old_container = player_worlds[id]
|
||||
print("container name=", old_container)
|
||||
var old_viewport = old_container.get_child(0)
|
||||
print("Viewport name=", old_viewport)
|
||||
var player_node = old_viewport.get_child(0).get_node("SpawnPoints").get_node(str(id))
|
||||
|
||||
if player_node:
|
||||
player_node.queue_free()
|
||||
|
||||
else:
|
||||
print("Old viewport:", old_viewport)
|
||||
|
||||
print("Player for erasing not found:", id)
|
||||
|
||||
# 2. Bestehende Funktion für den Wechsel aufrufen
|
||||
change_player_area(id, area_key)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue