PC3 Phase 2.3: networked play path in the game
- Session autoload: carries {world_id, host, port} from the world browser
to the game scene.
- world_list.gd "Enter World": POST /api/worlds/{id}/join -> Session.begin
-> load game.tscn. On 503 (backend has no orchestration) falls back to
local single-player; other errors surface on the card.
- api_client.gd: join_world().
- game.gd (new, on game.tscn root): local single-player by default; when
Session.active, connects a NetworkClient to the world's dedicated server
and runs networked -- forwards the local Player's input each physics
frame, snaps to the server position past a generous drift threshold
(the Player keeps predicting locally with real collision), renders other
players from snapshots (labelled capsules, lerped -- avatar appearance
sync is a follow-up, the server doesn't replicate loadouts yet), and
returns to the world list on disconnect.
- player.gd: exposes net_input (the input frame it applied) and
apply_server_correction().
- network_client.gd: send_input() + get_own_server_state() for callers that
do their own collision-aware prediction.
- scripts/mp_game_flow_client.gd + run_game_multiplayer_test.sh: two real
clients each register -> join via the API -> game.tscn -> networked;
A sees B and moves. PASS.
Verified end to end against an orchestration-enabled backend. Non-networked
smokes green (enter_world 9/9, world_list 5/5, player 15/15,
avatar_in_world 10/10, main_menu_full_flow 12/12).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -56,6 +56,8 @@ var _on_floor: bool = true
|
||||
## peer_id -> latest authoritative state dict from the most recent snapshot,
|
||||
## for every peer that isn't us. The game reads this to render other players.
|
||||
var _remote_states: Dictionary = {}
|
||||
## Our own latest authoritative state from the most recent snapshot.
|
||||
var _own_state: Dictionary = {}
|
||||
var _last_snapshot_tick: int = -1
|
||||
|
||||
|
||||
@@ -188,6 +190,7 @@ func _handle_snapshot(snap: Dictionary) -> void:
|
||||
var mine: Variant = players.get(my_peer_id, players.get(str(my_peer_id), null))
|
||||
if not (mine is Dictionary):
|
||||
return
|
||||
_own_state = mine
|
||||
var auth_pos: Vector3 = mine.get("position", position)
|
||||
var auth_vel: Vector3 = mine.get("velocity", velocity)
|
||||
var acked_seq: int = int(mine.get("last_seq", 0))
|
||||
@@ -238,3 +241,19 @@ func _handle_snapshot(snap: Dictionary) -> void:
|
||||
## recent snapshot. Read this each frame to spawn/update remote avatars.
|
||||
func get_remote_states() -> Dictionary:
|
||||
return _remote_states
|
||||
|
||||
|
||||
## Our own latest authoritative state from the most recent snapshot, or {}.
|
||||
func get_own_server_state() -> Dictionary:
|
||||
return _own_state
|
||||
|
||||
|
||||
## Send an input frame to the server WITHOUT running NetworkClient's own
|
||||
## flat-plane prediction -- for callers that predict locally with their own
|
||||
## collision-aware CharacterBody3D (scripts/game.gd) and only want the server
|
||||
## as an authority/correction backstop.
|
||||
func send_input(move_x: float, move_z: float, jump: bool, look_yaw: float) -> void:
|
||||
_seq += 1
|
||||
if _player_proxy and is_instance_valid(_player_proxy):
|
||||
_player_proxy.rpc_id(1, "rpc_request_input",
|
||||
{"move_x": move_x, "move_z": move_z, "jump": jump, "look_yaw": look_yaw}, _seq)
|
||||
|
||||
Reference in New Issue
Block a user