Client/Server Sync tested for 1 player, lerp smoothing, player face to target
This commit is contained in:
parent
fbb880acd6
commit
5fbdc61bec
4 changed files with 65 additions and 13 deletions
|
|
@ -103,3 +103,32 @@ Dieser Abschnitt beschreibt die Verantwortlichkeiten der zentralen Skripte im Pr
|
|||
- **Rolle:** Hauptmenü-UI.
|
||||
- **Verantwortlichkeit:** Enthält die UI-Elemente wie "Host" und "Join" Buttons. Es wird angenommen, dass die `pressed()` Signale dieser Buttons direkt im Godot-Editor mit den entsprechenden Signalen im `GameEvents`-Singleton (`host_requested`, `join_requested`) verbunden sind.
|
||||
|
||||
## 8. Wichtige Erkenntnisse & Best Practices
|
||||
|
||||
Dieser Abschnitt dokumentiert wichtige technische Lektionen, die während der Entwicklung gelernt wurden.
|
||||
|
||||
### 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:
|
||||
|
||||
1. **Konfiguration nur per Code:** Die Konfiguration muss zur Laufzeit im Code erfolgen, nicht im Editor. Der Editor speichert absolute Pfade, die in einem dynamischen Szenen-Setup (mehrere Welten/Viewports) unbrauchbar sind.
|
||||
|
||||
2. **Ort der Konfiguration: `_ready()`:** Die Logik zur Erstellung der `SceneReplicationConfig` gehört in die `_ready()`-funktion des Skripts, dessen Eigenschaften synchronisiert werden sollen (z.B. `player_controller.gd`).
|
||||
|
||||
3. **Korrekte Eigenschafts-Syntax:** Der Pfad zu einer Eigenschaft, die sich direkt auf dem `root_path`-Node des Synchronizers befindet (standardmäßig sein Parent), **muss mit einem Doppelpunkt beginnen**.
|
||||
- **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)
|
||||
# ...
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -162,19 +162,22 @@ func _spawn_player_in_world(id, viewport_node):
|
|||
|
||||
#player.set_multiplayer_authority(id)
|
||||
player.set_multiplayer_authority(1)
|
||||
|
||||
viewport_node.add_child(player, true)
|
||||
|
||||
# Visibility Filtering: Nur Spieler im gleichen Viewport sehen sich
|
||||
var sync = player.get_node("MultiplayerSynchronizer")
|
||||
# SCHRITT 2: Explizit auf den nächsten Frame warten.
|
||||
# Dies gibt Godot die Zeit, die _ready() Funktion des Spielers auszuführen.
|
||||
await get_tree().process_frame
|
||||
|
||||
# SCHRITT 3: Jetzt ist der Spieler garantiert bereit und konfiguriert.
|
||||
# Alle folgenden Operationen sind sicher.
|
||||
var sync = player.get_node("MultiplayerSynchronizer")
|
||||
|
||||
# Visibility Filtering: Nur Spieler im gleichen Viewport sehen sich
|
||||
for other in viewport_node.get_children():
|
||||
if other.is_in_group("players"):
|
||||
var other_id = other.name.to_int()
|
||||
|
||||
var other_id = other.name.to_int()
|
||||
# Der neue Spieler sieht den alten
|
||||
other.get_node("MultiplayerSynchronizer").set_visibility_for(id, true)
|
||||
|
||||
# Der alte Spieler sieht den neuen
|
||||
sync.set_visibility_for(other_id, true)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@
|
|||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_fqfg4"]
|
||||
|
||||
[node name="CharacterBody3D" type="CharacterBody3D" unique_id=682006733]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.26067275)
|
||||
script = ExtResource("1_ktl23")
|
||||
interpolation_speed = 5.0
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=159496934]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0185384, 0)
|
||||
shape = SubResource("CylinderShape3D_ktl23")
|
||||
|
||||
[node name="Visuals" type="Node3D" parent="." unique_id=1840405751]
|
||||
transform = Transform3D(-0.99988204, 0, 0.015358234, 0, 1, 0, -0.015358234, 0, -0.99988204, 0, 0, 0)
|
||||
|
||||
[node name="Dude" parent="Visuals" unique_id=1185543431 instance=ExtResource("1_2anqx")]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ extends CharacterBody3D
|
|||
@export var sync_position: Vector3
|
||||
@export var sync_rotation: Vector3
|
||||
|
||||
@export var interpolation_speed = 15.0 # Geschwindigkeit für die Glättung
|
||||
|
||||
var path: PackedVector3Array = []
|
||||
var speed = 5.0
|
||||
|
||||
|
|
@ -17,13 +19,25 @@ func _enter_tree():
|
|||
#set_multiplayer_authority(name.to_int())
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
|
||||
func _ready():
|
||||
target_position = global_position
|
||||
# --- START: Dynamische Replikations-Konfiguration ---
|
||||
# Eine neue Konfigurations-Ressource im Code erstellen.
|
||||
var config = SceneReplicationConfig.new()
|
||||
# Die zu synchronisierenden Eigenschaften hinzufügen. Der Pfad '..:sync_position' bedeutet:
|
||||
# '..': Gehe zum Eltern-Node (dem CharacterBody3D).
|
||||
# ':': Greife auf eine Eigenschaft dieses Nodes zu.
|
||||
# 'sync_position': Der Name der Eigenschaft.
|
||||
config.add_property(NodePath(":sync_position"))
|
||||
config.add_property(NodePath(":sync_rotation"))
|
||||
# Die generierte Konfiguration dem Synchronizer zuweisen. Dies überschreibt alle Editor-Einstellungen.
|
||||
synchronizer.replication_config = config
|
||||
# --- ENDE: Dynamische Replikations-Konfiguration ---
|
||||
|
||||
target_position = global_position
|
||||
# Wir schalten die automatische Physik-Synchronisation aus
|
||||
set_physics_process(multiplayer.is_server())
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
# Dieser Teil läuft NUR auf dem Server
|
||||
if path.size() > 0:
|
||||
|
|
@ -32,6 +46,9 @@ func _physics_process(delta):
|
|||
var next_point = path[0]
|
||||
var direction = (next_point - global_position).normalized()
|
||||
|
||||
# Richte den Avatar in die Bewegungsrichtung aus.
|
||||
look_at(next_point, Vector3.UP)
|
||||
|
||||
# Wir nutzen move_and_slide, damit Kollisionen (Wände) funktionieren
|
||||
velocity = direction * speed
|
||||
move_and_slide()
|
||||
|
|
@ -115,8 +132,8 @@ func _get_click_floor_pos():
|
|||
func _process(_delta):
|
||||
# Dieser Teil läuft auf dem Client (besonders dem, der die Authority hat)
|
||||
if not multiplayer.is_server():
|
||||
if global_position != sync_position:
|
||||
print("Client: _process() => Setting client position")
|
||||
# Wir erzwingen die Position, die der Server berechnet hat
|
||||
global_position = sync_position
|
||||
rotation = sync_rotation
|
||||
# Bewege die lokale Position sanft in Richtung der vom Server vorgegebenen Position.
|
||||
global_position = global_position.lerp(sync_position, _delta * interpolation_speed)
|
||||
|
||||
# Rotiere sanft in Richtung der vom Server vorgegebenen Rotation.
|
||||
rotation = rotation.lerp(sync_rotation, _delta * interpolation_speed)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue