From d2e6bfac2bef502e434c4a6c1b92c2ade1f42f6e Mon Sep 17 00:00:00 2001 From: copygirl Date: Sun, 2 Nov 2025 14:42:42 +0100 Subject: [PATCH] Predict player entity, unify movement system --- client/src/main.rs | 6 ++--- common/src/network/protocol.rs | 17 ++++++++++++- common/src/network/server.rs | 10 ++++---- common/src/player.rs | 44 ++++++++++++++++++---------------- 4 files changed, 47 insertions(+), 30 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 4a498ae..baac5c9 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -90,7 +90,7 @@ fn main() -> Result { ); app.add_observer(spawn_initial_blocks); - app.add_observer(setup_player); + app.add_observer(handle_predicted_spawn); app.run(); Ok(()) @@ -135,8 +135,8 @@ fn spawn_initial_blocks(_event: On, mut blocks: Blocks) { } } -/// When the player we control is being spawned, insert necessary components. -fn setup_player(event: On, mut commands: Commands) { +/// When the `Predicted` player we control is being spawned, insert necessary components. +fn handle_predicted_spawn(event: On, mut commands: Commands) { let player = event.entity; commands .entity(player) diff --git a/common/src/network/protocol.rs b/common/src/network/protocol.rs index 287f5ea..4c26c96 100644 --- a/common/src/network/protocol.rs +++ b/common/src/network/protocol.rs @@ -25,8 +25,23 @@ impl Plugin for ProtocolPlugin { fn build(&self, app: &mut App) { app.add_plugins(InputPlugin::::default()); - app.register_component::(); + // marker components app.register_component::(); app.register_component::(); + + app.register_component::() + .add_prediction() + .add_interpolation_with(interpolate_transform); + + // unified update systems + app.add_systems(FixedUpdate, crate::player::movement); + } +} + +fn interpolate_transform(start: Transform, other: Transform, t: f32) -> Transform { + Transform { + translation: start.translation.lerp(other.translation, t), + rotation: start.rotation.slerp(other.rotation, t), + scale: start.scale.lerp(other.scale, t), } } diff --git a/common/src/network/server.rs b/common/src/network/server.rs index e093222..58b26b5 100644 --- a/common/src/network/server.rs +++ b/common/src/network/server.rs @@ -26,8 +26,6 @@ impl Plugin for ServerPlugin { #[cfg(not(target_family = "wasm"))] app.add_observer(super::server_webtransport::print_certificate_digest); - - app.add_systems(FixedUpdate, crate::player::server_movement); } } @@ -57,11 +55,11 @@ fn on_server_stopped(event: On) { fn handle_client_connected( event: On, - clients: Query<&LinkOf>, + clients: Query<(&LinkOf, &RemoteId)>, mut commands: Commands, ) { let client = event.entity; - let Ok(LinkOf { server }) = clients.get(client) else { + let Ok((LinkOf { server }, RemoteId(peer_id))) = clients.get(client) else { return; // Not a client of the server. (client-side?) }; info!("Client '{client}' connected to server '{server}'"); @@ -74,11 +72,13 @@ fn handle_client_connected( commands.spawn(( Player, Name::from("Player"), - Replicate::to_clients(NetworkTarget::All), ControlledBy { owner: client, lifetime: Lifetime::SessionBased, }, + Replicate::to_clients(NetworkTarget::All), + PredictionTarget::to_clients(NetworkTarget::Single(*peer_id)), + InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(*peer_id)), )); } diff --git a/common/src/player.rs b/common/src/player.rs index 52f7f16..4e24d58 100644 --- a/common/src/player.rs +++ b/common/src/player.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use lightyear::prelude::*; use lightyear::prelude::input::native::ActionState; use serde::{Deserialize, Serialize}; @@ -11,28 +12,29 @@ const MOVEMENT_SPEED: f32 = 5.0; #[require(Transform)] pub struct Player; -pub fn server_movement( +pub fn movement( time: Res>, - mut players: Query<(&mut Transform, &ActionState)>, + mut players: Query< + (&mut Transform, &ActionState), + // Must be a `Player` which is either be `ControlledBy` a remote + // client (server-side) or its movement `Predicted` on the client. + (With, Or<(With, With)>), + >, ) { - for (mut transform, inputs) in players.iter_mut() { - shared_movement(&mut transform, *time, inputs); + for (mut transform, input) in players.iter_mut() { + let dt = time.delta_secs(); + let mut translation = Vec3::ZERO; + if input.movement != Vec2::ZERO { + let movement = input.movement.clamp_length_max(1.0); + translation.x += movement.x; + translation.z += movement.y; + } + if input.up { + translation.y += 1.0; + } + if input.down { + translation.y -= 1.0; + } + transform.translation += translation * MOVEMENT_SPEED * dt; } } - -pub fn shared_movement(transform: &mut Transform, time: Time, input: &Inputs) { - let dt = time.delta_secs(); - let mut translation = Vec3::ZERO; - if input.movement != Vec2::ZERO { - let movement = input.movement.clamp_length_max(1.0); - translation.x += movement.x; - translation.z += movement.y; - } - if input.up { - translation.y += 1.0; - } - if input.down { - translation.y -= 1.0; - } - transform.translation += translation * MOVEMENT_SPEED * dt; -}