f6d69545c9
Add client-side prediction (t_p1_pred) with: - scripts/network/snapshot.gd — lightweight snapshot resource with to_dict/from_dict serialization for RPC. Stores position (Vector3), rotation (Quaternion), velocity (Vector3), grounded (bool). - scripts/network/client_prediction.gd — prediction/reconciliation controller. 64-entry ring buffer of local snapshots, sends inputs to server each physics tick (128Hz, ENet channel 0), detects misprediction on server state arrival, rewinds and re-applies unconfirmed inputs. Also supports remote player interpolation. - scripts/network/network_manager.gd — new RPC endpoints for client prediction: send_client_input (client->server, ch 0) and send_server_state (server->client, ch 1). New signals for routing. - client/characters/character/fps_character_controller.gd — prediction hooks in _physics_process: on_before_tick() captures pre-input snapshot, on_after_tick() sends input to server. Client prediction path uses local movement (instant feedback) instead of reading from SimulationServer entity. Architecture: Each tick: client applies input → predicts new state locally → sends input to server → server returns authoritative state → client compares and reconciles if mismatch.
61 lines
2.1 KiB
GDScript
61 lines
2.1 KiB
GDScript
## Snapshot — Lightweight world-state snapshot for client prediction & reconciliation.
|
|
##
|
|
## Stores a point-in-time record of a player's physical state.
|
|
## Used by ClientPrediction to build its ring buffer, send inputs,
|
|
## and reconcile against authoritative server state.
|
|
##
|
|
## Serialization: to_dict() / from_dict() for RPC transport over ENet.
|
|
class_name Snapshot
|
|
extends RefCounted
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fields
|
|
# ---------------------------------------------------------------------------
|
|
## Server tick / local tick number this snapshot corresponds to.
|
|
var timestamp: int = 0
|
|
|
|
## World-space position of the character's feet / body origin.
|
|
var position: Vector3 = Vector3.ZERO
|
|
|
|
## Character body rotation as a quaternion (avoids gimbal lock in interpolation).
|
|
var rotation: Quaternion = Quaternion.IDENTITY
|
|
|
|
## Linear velocity in world space.
|
|
var velocity: Vector3 = Vector3.ZERO
|
|
|
|
## Whether the character was on the ground (is_on_floor()) at capture time.
|
|
var grounded: bool = false
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Serialization
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Convert to a Dictionary suitable for RPC transfer.
|
|
## Arrays are used instead of Vector3/Quaternion because Godot's RPC
|
|
## serialization handles basic types more reliably.
|
|
func to_dict() -> Dictionary:
|
|
return {
|
|
"timestamp": timestamp,
|
|
"position": [position.x, position.y, position.z],
|
|
"rotation": [rotation.x, rotation.y, rotation.z, rotation.w],
|
|
"velocity": [velocity.x, velocity.y, velocity.z],
|
|
"grounded": grounded,
|
|
}
|
|
|
|
## Deserialize from a Dictionary produced by to_dict().
|
|
static func from_dict(data: Dictionary) -> Snapshot:
|
|
var s = Snapshot.new()
|
|
s.timestamp = data.get("timestamp", 0)
|
|
|
|
var p: Array = data.get("position", [0.0, 0.0, 0.0])
|
|
s.position = Vector3(p[0], p[1], p[2])
|
|
|
|
var r: Array = data.get("rotation", [0.0, 0.0, 0.0, 1.0])
|
|
s.rotation = Quaternion(r[0], r[1], r[2], r[3])
|
|
|
|
var v: Array = data.get("velocity", [0.0, 0.0, 0.0])
|
|
s.velocity = Vector3(v[0], v[1], v[2])
|
|
|
|
s.grounded = data.get("grounded", false)
|
|
return s
|