17d20440da
The per-peer spread offset used the raw ENet peer id directly ((peer_id - 1) * 1.5), assuming ids are small sequential numbers. Real peer ids are large effectively-random 32-bit values, so a joining client's XR rig was being shifted by hundreds of millions of units off the origin — the world had actually replicated correctly, the player was just teleported far away from all of it, with float precision bad enough at that range to jitter the view and destabilize physics. Bound the offset to a small deterministic slot instead; the host still keeps its original baked spot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
2.2 KiB
GDScript
59 lines
2.2 KiB
GDScript
extends Node3D
|
|
|
|
## Networked representation of one connected player. The owning peer's local
|
|
## XR rig (found via the "local_xr_origin" group) drives Head/LeftHand/
|
|
## RightHand each frame; the MultiplayerSynchronizer replicates those
|
|
## transforms so every other peer sees a matching avatar.
|
|
|
|
@onready var _head: Node3D = $Head
|
|
@onready var _left_hand: Node3D = $LeftHand
|
|
@onready var _right_hand: Node3D = $RightHand
|
|
|
|
var _local_camera: Node3D
|
|
var _local_left_hand: Node3D
|
|
var _local_right_hand: Node3D
|
|
|
|
|
|
func _enter_tree() -> void:
|
|
# MultiplayerSynchronizer resolves authority when this node enters the
|
|
# tree, so authority must be set here rather than in _ready(), or the
|
|
# first synced frames go the wrong direction.
|
|
set_multiplayer_authority(str(name).to_int())
|
|
|
|
|
|
func _ready() -> void:
|
|
if is_multiplayer_authority():
|
|
var origin := get_tree().get_first_node_in_group("local_xr_origin")
|
|
if origin:
|
|
_local_camera = origin.get_node_or_null("XRCamera3D")
|
|
_local_left_hand = origin.get_node_or_null("XRControllerLeftHand")
|
|
_local_right_hand = origin.get_node_or_null("XRControllerRightHand")
|
|
# Every peer's rig is baked at the same spot in the scene; spread
|
|
# joiners out along X so they don't start stacked on top of each
|
|
# other. Peer ids from ENet are large effectively-random 32-bit
|
|
# numbers (not small sequential ones), so the offset must be
|
|
# bounded — using the raw id directly once shifted a joining
|
|
# player ~460 million units from the origin, which reads as an
|
|
# empty world (everything was still there, just unreachably far
|
|
# away) and wrecks float precision badly enough to jitter the
|
|
# view and destabilize physics.
|
|
var peer_id := str(name).to_int()
|
|
if peer_id != 1:
|
|
var slot := absi(peer_id) % 8 + 1
|
|
origin.position += Vector3(slot * 1.5, 0, 0)
|
|
# Don't render your own floating head/hands from the inside.
|
|
_head.visible = false
|
|
_left_hand.visible = false
|
|
_right_hand.visible = false
|
|
else:
|
|
set_process(false)
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
if _local_camera:
|
|
_head.global_transform = _local_camera.global_transform
|
|
if _local_left_hand:
|
|
_left_hand.global_transform = _local_left_hand.global_transform
|
|
if _local_right_hand:
|
|
_right_hand.global_transform = _local_right_hand.global_transform
|