Fix: rejoining stacked players on top of each other (one fell through)
checkpoint / checkpoint (push) Successful in 1m40s
checkpoint / checkpoint (push) Successful in 1m40s
All players spawned at the exact same PlayerSpawn point and were on the same collision layer, so two overlapping CharacterBody3Ds triggered a physics depenetration shove that could launch one through the 1-unit-thick floor. - server.gd: golden-angle spawn spread -- each join lands ~1m+ from the others, growing outward, never exactly repeating. - server_player.tscn + player.tscn: players on collision layer 2, mask 1 (world only) -- players no longer collide with each other at all (fine for a social game; also removes the shove). - player_spawner.gd + player.gd: hard safety net -- if y < -10 (any tunnel-through), snap back to the spawn instead of falling forever. Verified: 3 server players spawn 1.15m+ apart, none fall, all rest on the floor. 2p multiplayer + network_client smokes green. Client-side -- rebuild the exe. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -11,6 +11,8 @@ radius = 0.35
|
|||||||
height = 1.6
|
height = 1.6
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody3D"]
|
[node name="Player" type="CharacterBody3D"]
|
||||||
|
collision_layer = 2
|
||||||
|
collision_mask = 1
|
||||||
script = ExtResource("1_player")
|
script = ExtResource("1_player")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ radius = 0.35
|
|||||||
height = 1.6
|
height = 1.6
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody3D"]
|
[node name="Player" type="CharacterBody3D"]
|
||||||
|
collision_layer = 2
|
||||||
|
collision_mask = 1
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const MOUSE_SENSITIVITY: float = 0.003
|
|||||||
|
|
||||||
var _input_direction := Vector2.ZERO
|
var _input_direction := Vector2.ZERO
|
||||||
var _camera_pitch: float = -0.12
|
var _camera_pitch: float = -0.12
|
||||||
|
var _spawn_position: Vector3 = Vector3(0, 1.5, 0)
|
||||||
|
|
||||||
## Vertical offset applied to the assembled avatar so its feet (origin y=0)
|
## Vertical offset applied to the assembled avatar so its feet (origin y=0)
|
||||||
## line up with the bottom of the player's collision capsule.
|
## line up with the bottom of the player's collision capsule.
|
||||||
@@ -69,6 +70,7 @@ func _ready() -> void:
|
|||||||
else:
|
else:
|
||||||
push_warning("player.gd: no PlayerSpawn marker found; spawning at origin")
|
push_warning("player.gd: no PlayerSpawn marker found; spawning at origin")
|
||||||
global_position = Vector3(0, 1.0, 0)
|
global_position = Vector3(0, 1.0, 0)
|
||||||
|
_spawn_position = global_position
|
||||||
|
|
||||||
# The spring arm must not collide with the player's own capsule (they're
|
# The spring arm must not collide with the player's own capsule (they're
|
||||||
# on the same layer), or the camera gets yanked right up against the
|
# on the same layer), or the camera gets yanked right up against the
|
||||||
@@ -192,6 +194,11 @@ func _physics_process(delta: float) -> void:
|
|||||||
|
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
||||||
|
# Safety net -- never fall forever if something goes wrong.
|
||||||
|
if global_position.y < -10.0:
|
||||||
|
global_position = _spawn_position
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
|
||||||
net_input = {
|
net_input = {
|
||||||
"move_x": direction.x, "move_z": direction.z,
|
"move_x": direction.x, "move_z": direction.z,
|
||||||
"jump": jumped, "look_yaw": rotation.y,
|
"jump": jumped, "look_yaw": rotation.y,
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const GRAVITY := 9.8
|
|||||||
|
|
||||||
var peer_id: int = -1
|
var peer_id: int = -1
|
||||||
|
|
||||||
|
## Where the server spawned this player (set by server.gd). Used as the
|
||||||
|
## respawn point if the player somehow ends up below the world.
|
||||||
|
var spawn_position: Vector3 = Vector3(0, 1.5, 0)
|
||||||
|
|
||||||
## Highest input seq applied authoritatively so far (echoed in snapshots so
|
## Highest input seq applied authoritatively so far (echoed in snapshots so
|
||||||
## the client can drop acked inputs and replay the rest).
|
## the client can drop acked inputs and replay the rest).
|
||||||
var last_applied_seq: int = 0
|
var last_applied_seq: int = 0
|
||||||
@@ -38,6 +42,13 @@ func _physics_process(delta: float) -> void:
|
|||||||
|
|
||||||
rotation.y = _look_yaw
|
rotation.y = _look_yaw
|
||||||
|
|
||||||
|
# Safety net: if a player ends up below the world (physics shove, tunnel
|
||||||
|
# through the floor), put them back on their spawn instead of falling
|
||||||
|
# forever.
|
||||||
|
if global_position.y < -10.0:
|
||||||
|
global_position = spawn_position
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
|
||||||
|
|
||||||
func _integrate(move_x: float, move_z: float, jump: bool, delta: float) -> void:
|
func _integrate(move_x: float, move_z: float, jump: bool, delta: float) -> void:
|
||||||
if not is_on_floor():
|
if not is_on_floor():
|
||||||
|
|||||||
+17
-1
@@ -30,6 +30,9 @@ var status_file_path: String = ""
|
|||||||
var players: Dictionary = {}
|
var players: Dictionary = {}
|
||||||
var tick: int = 0
|
var tick: int = 0
|
||||||
var is_running: bool = false
|
var is_running: bool = false
|
||||||
|
## Monotonic count of joins ever -- drives spawn spread so two players never
|
||||||
|
## land on the same spot (which used to shove one through the floor).
|
||||||
|
var _join_count: int = 0
|
||||||
|
|
||||||
var _player_scene: PackedScene = preload("res://scenes/server_player.tscn")
|
var _player_scene: PackedScene = preload("res://scenes/server_player.tscn")
|
||||||
var _level_scene: PackedScene = preload("res://scenes/worlds/test_level.tscn")
|
var _level_scene: PackedScene = preload("res://scenes/worlds/test_level.tscn")
|
||||||
@@ -65,6 +68,16 @@ func _ready() -> void:
|
|||||||
print("[Server] world=%s listening on ENet %d (%d Hz)" % [world_id, port, PHYSICS_HZ])
|
print("[Server] world=%s listening on ENet %d (%d Hz)" % [world_id, port, PHYSICS_HZ])
|
||||||
|
|
||||||
|
|
||||||
|
## A spawn point offset from the level's marker so joins don't stack. Golden-
|
||||||
|
## angle spiral: evenly spread, never exactly repeats, grows slowly outward.
|
||||||
|
func _spawn_for(index: int) -> Vector3:
|
||||||
|
if index == 0:
|
||||||
|
return _spawn_point
|
||||||
|
var angle := index * 2.399963 # golden angle (radians)
|
||||||
|
var radius := 0.9 + 0.25 * float(index)
|
||||||
|
return _spawn_point + Vector3(cos(angle) * radius, 0.0, sin(angle) * radius)
|
||||||
|
|
||||||
|
|
||||||
func _parse_args() -> void:
|
func _parse_args() -> void:
|
||||||
var args := OS.get_cmdline_user_args()
|
var args := OS.get_cmdline_user_args()
|
||||||
var i := 0
|
var i := 0
|
||||||
@@ -127,7 +140,10 @@ func _on_peer_connected(peer_id: int) -> void:
|
|||||||
player.name = "Player_%d" % peer_id
|
player.name = "Player_%d" % peer_id
|
||||||
player.peer_id = peer_id
|
player.peer_id = peer_id
|
||||||
add_child(player)
|
add_child(player)
|
||||||
player.global_position = _spawn_point
|
var spawn := _spawn_for(_join_count)
|
||||||
|
_join_count += 1
|
||||||
|
player.global_position = spawn
|
||||||
|
player.spawn_position = spawn
|
||||||
players[peer_id] = player
|
players[peer_id] = player
|
||||||
print("[Server] peer %d joined (%d players)" % [peer_id, players.size()])
|
print("[Server] peer %d joined (%d players)" % [peer_id, players.size()])
|
||||||
_write_status()
|
_write_status()
|
||||||
|
|||||||
Reference in New Issue
Block a user