diff --git a/Cargo.lock b/Cargo.lock index d9b1c5b..6ba3ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4182,6 +4182,7 @@ dependencies = [ "bevy_mod_openxr", "bevy_spatial_egui", "bevy_vr_controller", + "bevy_vrm", "bevy_web_file_drop", "bincode", "futures-channel", diff --git a/Cargo.toml b/Cargo.toml index 1607fdc..aaa9a4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,10 @@ bevy_mod_openxr = { git = "https://github.com/awtterpip/bevy_oxr" } bevy_spatial_egui = { path = "../bevy_spatial_egui" } bevy_egui = "0.29.0" bevy-suis = { git = "https://github.com/Schmarni-Dev/bevy-suis", branch = "further_work" } - +bevy_vrm = "0.0.12" [profile.release] lto = "thin" opt-level = "z" + +[profile.dev] +opt-level = 3 diff --git a/src/file_sharing.rs b/src/file_sharing.rs index 6a17509..774fdd7 100644 --- a/src/file_sharing.rs +++ b/src/file_sharing.rs @@ -1,11 +1,11 @@ use crate::networking::{Connection, ConnectionTrait, ReliableMessage}; use bevy::prelude::*; use bevy::reflect::List; +use bevy_matchbox::prelude::MultipleChannels; +use bevy_matchbox::MatchboxSocket; use futures_channel::mpsc::{channel, Receiver, SendError, Sender}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use bevy_matchbox::MatchboxSocket; -use bevy_matchbox::prelude::MultipleChannels; use uuid::Uuid; #[derive(Clone, Debug, Serialize, Deserialize, Event)] @@ -41,8 +41,16 @@ impl Plugin for FileSharingPlugin { let (tx, rx) = channel(100); app.insert_resource(P2pFileRx(rx)); app.insert_resource(P2pFileSender(tx)); - app.add_systems(Update, handle_file_part.run_if(resource_exists::>)); - app.add_systems(Update, send_parts_of_file.run_if(resource_exists::>)); + app.add_systems( + Update, + handle_file_part + .run_if(resource_exists::>), + ); + app.add_systems( + Update, + send_parts_of_file + .run_if(resource_exists::>), + ); } } diff --git a/src/main.rs b/src/main.rs index 9ff9623..2152ca7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use crate::file_sharing::FileSharingPlugin; -use crate::networking::{ConnectToRoom, NetworkingPlugin}; -use avian3d::prelude::{Collider, CollisionLayers, RigidBody}; +use crate::networking::{ConnectToRoom, LocalPlayer, NetworkingPlugin, SpawnPlayer}; +use crate::player_networking::PlayerNetworking; +use avian3d::prelude::{Collider, CollisionLayers, GravityScale, LockedAxes, RigidBody}; use avian3d::PhysicsPlugins; use bevy::app::App; use bevy::asset::{AssetMetaCheck, AssetServer, Assets, Handle}; @@ -17,36 +18,46 @@ use bevy_suis::debug::SuisDebugGizmosPlugin; use bevy_suis::window_pointers::SuisWindowPointerPlugin; use bevy_suis::SuisCorePlugin; use bevy_vr_controller::animation::defaults::default_character_animations; -use bevy_vr_controller::player::PlayerSettings; +use bevy_vr_controller::movement::PlayerInputState; +use bevy_vr_controller::player::{ + PlayerAvatar, PlayerBody, PlayerHeight, PlayerJumpHeight, PlayerSettings, + PlayerSpawn, PlayerSpeed, SpawnedPlayer, VoidTeleport, +}; +use bevy_vr_controller::velocity::AverageVelocity; use bevy_vr_controller::VrControllerPlugin; +use bevy_vrm::VrmBundle; +use uuid::Uuid; +use crate::physics_sync::PhysicsSyncNetworkingPlugin; mod file_sharing; pub mod networking; +mod player_networking; +mod physics_sync; pub fn main() { App::new() - .add_plugins(( - EmbeddedAssetPlugin::default(), - bevy_web_file_drop::WebFileDropPlugin, - DefaultPlugins.set(AssetPlugin { - meta_check: AssetMetaCheck::Never, - ..AssetPlugin::default() - }), - PhysicsPlugins::default(), - VrControllerPlugin, - )) - .add_plugins(( - SuisCorePlugin, - SuisWindowPointerPlugin, - SuisDebugGizmosPlugin, - )) - .add_plugins(bevy_spatial_egui::SpatialEguiPlugin) - .add_plugins(EguiPlugin) - .add_plugins((NetworkingPlugin, FileSharingPlugin)) - .add_systems(Startup, setup_main_window) - .add_systems(Startup, (setup_scene, setup_player)) - .add_systems(Update, draw_ui) - .run(); + .add_plugins(( + EmbeddedAssetPlugin::default(), + bevy_web_file_drop::WebFileDropPlugin, + DefaultPlugins.set(AssetPlugin { + meta_check: AssetMetaCheck::Never, + ..AssetPlugin::default() + }), + PhysicsPlugins::default(), + VrControllerPlugin, + )) + .add_plugins(( + SuisCorePlugin, + SuisWindowPointerPlugin, + //SuisDebugGizmosPlugin, + )) + .add_plugins(bevy_spatial_egui::SpatialEguiPlugin) + .add_plugins(EguiPlugin) + .add_plugins((NetworkingPlugin, FileSharingPlugin, PlayerNetworking, PhysicsSyncNetworkingPlugin)) + .add_systems(Startup, setup_main_window) + .add_systems(Startup, (setup_scene, setup_player)) + .add_systems(Update, draw_ui) + .run(); } fn draw_ui(mut query: Query<&mut EguiContext, With>) { @@ -81,14 +92,61 @@ const GROUND_SIZE: f32 = 30.0; const GROUND_THICK: f32 = 0.2; fn setup_player(asset_server: Res, mut commands: Commands) { - PlayerSettings { + let awa = PlayerSettings { animations: Some(default_character_animations(&asset_server)), vrm: Some(asset_server.load("embedded://models/robot.vrm")), void_level: Some(-20.0), spawn: Vec3::new(0.0, 3.0, 0.0), ..default() } - .spawn(&mut commands); + .spawn(&mut commands); + commands.entity(awa.body).insert(LocalPlayer(Uuid::new_v4())); +} + +pub fn spawn_avatar(this: PlayerSettings, commands: &mut Commands) -> Entity { + let mut body = commands.spawn(( + Collider::capsule(this.width / 2.0, this.height - this.width), + LockedAxes::ROTATION_LOCKED, + PlayerBody, + PlayerSpawn(this.spawn), + RigidBody::Dynamic, + SpatialBundle { + global_transform: GlobalTransform::from_translation(this.spawn), + ..default() + }, + GravityScale(0.0) + )); + + if let Some(value) = this.void_level { + body.insert(VoidTeleport(value)); + } + + let body = body.id(); + + let mut avatar = commands.spawn(( + PlayerAvatar, + AverageVelocity { + target: Some(body), + ..default() + }, + VrmBundle { + scene_bundle: SceneBundle { + transform: Transform::from_xyz(0.0, -this.height / 2.0, 0.0), + ..default() + }, + vrm: this.vrm.clone().unwrap_or_default(), + ..default() + }, + )); + + if let Some(value) = &this.animations { + avatar.insert(value.clone()); + } + + let avatar = avatar.id(); + + commands.entity(body).push_children(&[avatar]); + body } fn setup_scene( diff --git a/src/networking.rs b/src/networking.rs index 837d8d7..9ab77fc 100644 --- a/src/networking.rs +++ b/src/networking.rs @@ -1,10 +1,13 @@ +use crate::file_sharing::FileParts; use avian3d::prelude::{AngularVelocity, LinearVelocity, Position}; use bevy::app::App; use bevy::ecs::system::{EntityCommand, SystemParam}; use bevy::ecs::world::Command; use bevy::prelude::*; use bevy_derive::{Deref, DerefMut}; -use bevy_matchbox::prelude::{MultipleChannels, PeerId, WebRtcSocketBuilder}; +use bevy_matchbox::prelude::{ + MultipleChannels, PeerId, PeerState, WebRtcSocketBuilder, +}; use bevy_matchbox::MatchboxSocket; use futures_channel::mpsc::SendError; use serde::{Deserialize, Serialize}; @@ -25,6 +28,7 @@ pub enum UnreliableMessage { #[derive(Clone, Debug, Serialize, Deserialize, Event)] pub struct SpawnPlayer { pub uuid: Uuid, + pub peer_id: Option, } #[derive(Clone, Debug, Serialize, Deserialize, Event)] @@ -55,9 +59,9 @@ pub struct PlayerPositionUpdate { pub struct LocalPlayer(pub Uuid); #[derive( - Reflect, Clone, Debug, Serialize, Deserialize, Deref, DerefMut, PartialEq, Component, + Clone, Debug, Serialize, Deserialize, PartialEq, Component, )] -pub struct RemotePlayer(pub Uuid); +pub struct RemotePlayer(pub Uuid, pub PeerId); pub trait ConnectToRoom { fn connect_to_room(&mut self, room: impl AsRef); @@ -80,15 +84,77 @@ impl ConnectToRoom for Commands<'_, '_> { pub struct NetworkingPlugin; impl Plugin for NetworkingPlugin { fn build(&self, app: &mut App) { + app.add_event::(); + app.add_event::(); + app.add_event::(); + app.add_event::(); + app.add_systems( FixedUpdate, update_peers.run_if(resource_exists::>), ); + app.add_systems( + Update, + (handle_messages_reliable, handle_messages_unreliable), + ); + } +} + +fn handle_messages_reliable( + mut connection: Connection, + mut spawn_players: EventWriter, + mut file_parts: ResMut, +) { + for (message, peer_id) in connection.recv() { + let message: ReliableMessage = message; + match message { + ReliableMessage::SpawnPlayer(mut spawn_player) => { + spawn_player.peer_id.replace(peer_id); + spawn_players.send(spawn_player); + } + ReliableMessage::FilePart(file_part) => { + file_parts.0.push(file_part); + } + } + } +} + +fn handle_messages_unreliable( + mut connection: Connection, + mut update_physics_positions: EventWriter, +) { + for (message, _peer_id) in connection.recv() { + let message: UnreliableMessage = message; + match message { + UnreliableMessage::UpdatePhysicsPosition(update_physics_position) => { + update_physics_positions.send(update_physics_position); + } + UnreliableMessage::PlayerPositionUpdate(_) => todo!(), + } } } -fn update_peers(mut socket: ResMut>) { - socket.update_peers(); +#[derive(Event)] +pub struct PlayerDisconnected(pub PeerId); + +#[derive(Event)] +pub struct PlayerConnected(pub PeerId); + +fn update_peers( + mut socket: ResMut>, + mut player_connected: EventWriter, + mut player_disconnected: EventWriter, +) { + for (peer, state) in socket.update_peers() { + match state { + PeerState::Connected => { + player_connected.send(PlayerConnected(peer)); + } + PeerState::Disconnected => { + player_disconnected.send(PlayerDisconnected(peer)); + } + } + } } #[derive(SystemParam)] @@ -98,7 +164,7 @@ pub struct Connection<'w> { pub trait ConnectionTrait { fn peers(&self) -> Vec; - fn update_peers(&mut self); + fn update_peers(&mut self) -> Vec<(PeerId, PeerState)>; fn send( &mut self, peer: bevy_matchbox::prelude::PeerId, @@ -125,6 +191,8 @@ pub trait ConnectionTrait { Some(vec) => Err(vec), } } + + fn recv(&mut self) -> Vec<(MessageType, PeerId)>; } impl ConnectionTrait for Connection<'_> { @@ -132,8 +200,8 @@ impl ConnectionTrait for Connection<'_> { self.socket.connected_peers().collect::>() } - fn update_peers(&mut self) { - self.socket.update_peers(); + fn update_peers(&mut self) -> Vec<(PeerId, PeerState)> { + self.socket.update_peers() } fn send( @@ -146,6 +214,15 @@ impl ConnectionTrait for Connection<'_> { .channel_mut(1) .try_send(Box::from(encoded), peer) } + + fn recv(&mut self) -> Vec<(UnreliableMessage, PeerId)> { + self.socket + .channel_mut(1) + .receive() + .into_iter() + .map(|(peer_id, packet)| (bincode::deserialize(&packet).unwrap(), peer_id)) + .collect() + } } impl ConnectionTrait for Connection<'_> { @@ -153,8 +230,8 @@ impl ConnectionTrait for Connection<'_> { self.socket.connected_peers().collect::>() } - fn update_peers(&mut self) { - self.socket.update_peers(); + fn update_peers(&mut self) -> Vec<(PeerId, PeerState)> { + self.socket.update_peers() } fn send( @@ -167,4 +244,13 @@ impl ConnectionTrait for Connection<'_> { .channel_mut(0) .try_send(Box::from(encoded), peer) } + + fn recv(&mut self) -> Vec<(ReliableMessage, PeerId)> { + self.socket + .channel_mut(0) + .receive() + .into_iter() + .map(|(peer_id, packet)| (bincode::deserialize(&packet).unwrap(), peer_id)) + .collect() + } } diff --git a/src/physics_sync.rs b/src/physics_sync.rs new file mode 100644 index 0000000..90ba2a4 --- /dev/null +++ b/src/physics_sync.rs @@ -0,0 +1,33 @@ +use avian3d::math::AsF32; +use avian3d::prelude::{AngularVelocity, LinearVelocity, Position, Rotation}; +use bevy::prelude::*; +use crate::networking::{RemotePlayer, UpdatePhysicsPosition}; + +pub struct PhysicsSyncNetworkingPlugin; + +impl Plugin for PhysicsSyncNetworkingPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Update, sync_positions); + } +} + +fn sync_positions(mut event_reader: EventReader, mut players: Query<(&RemotePlayer, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity)>) { + for update in event_reader.read() { + for (uuid, mut pos, mut rot, mut lin, mut ang) in players.iter_mut() { + if uuid.0 != update.uuid { + continue; + } + if pos.distance(*update.position) <= 0.01 { + if rot.0.angle_between(update.rotation.0).abs() <= 0.01 { + continue; + } + } + + *pos = update.position.clone(); + *rot = update.rotation.clone(); + *lin = update.linear_velocity.clone(); + //*ang = update.angular_velocity.clone(); + } + } + +} \ No newline at end of file diff --git a/src/player_networking.rs b/src/player_networking.rs new file mode 100644 index 0000000..8081aac --- /dev/null +++ b/src/player_networking.rs @@ -0,0 +1,109 @@ +use crate::networking::{Connection, ConnectionTrait, LocalPlayer, PlayerConnected, PlayerDisconnected, ReliableMessage, RemotePlayer, SpawnPlayer, UnreliableMessage, UpdatePhysicsPosition}; +use crate::spawn_avatar; +use avian3d::prelude::{AngularVelocity, LinearVelocity, Position, Rotation}; +use bevy::prelude::*; +use bevy_matchbox::prelude::MultipleChannels; +use bevy_matchbox::MatchboxSocket; +use bevy_vr_controller::animation::defaults::default_character_animations; +use bevy_vr_controller::player::PlayerSettings; + +pub struct PlayerNetworking; + +impl Plugin for PlayerNetworking { + fn build(&self, app: &mut App) { + app.add_systems(Update, (send_players_when_connected, when_disconnected_player)); + app.add_systems( + Update, + update_player_position + .run_if(resource_exists::>), + ); + app.add_systems(Update, when_spawn_player); + } +} + +fn when_spawn_player( + mut commands: Commands, + mut spawn_players: EventReader, + asset_server: Res, +) { + for player in spawn_players.read() { + println!("got spawn player"); + let id = spawn_avatar( + PlayerSettings { + animations: Some(default_character_animations(&asset_server)), + vrm: Some(asset_server.load("embedded://models/robot.vrm")), + void_level: Some(-20.0), + spawn: Vec3::new(0.0, 3.0, 0.0), + ..default() + }, + &mut commands, + ); + commands + .entity(id) + .insert(RemotePlayer(player.uuid.clone(), player.peer_id.as_ref().unwrap().clone())); + } +} + +fn when_disconnected_player( + mut commands: Commands, + mut event_reader: EventReader, + players: Query<(Entity, &RemotePlayer)> +) { + for disconnected_peer_id in event_reader.read() { + for (e, p) in players.iter() { + if p.1 == disconnected_peer_id.0 { + commands.entity(e).despawn_recursive(); + } + } + } +} + +fn send_players_when_connected( + mut player_connected: EventReader, + mut connection: Connection, + local_player: Query<&LocalPlayer>, +) { + let Ok(local_player) = local_player.get_single() else { return }; + for new_player in player_connected.read() { + println!("sending connection spawn player"); + let _ = connection.send( + new_player.0, + &ReliableMessage::SpawnPlayer(SpawnPlayer { + uuid: local_player.0.clone(), + peer_id: None, + }), + ); + } +} + +fn update_player_position( + local_player: Query< + ( + &Position, + &Rotation, + &LinearVelocity, + &AngularVelocity, + &LocalPlayer, + ), + ( + Or<( + Changed, + Changed, + Changed, + )>, + ), + >, + mut connection: Connection, +) { + let Ok((position, rotation, lin_vel, ang_vel, local_player)) = local_player.get_single() else {return}; + let message = UnreliableMessage::UpdatePhysicsPosition(UpdatePhysicsPosition { + authority: 0, + uuid: local_player.0, + position: position.clone(), + rotation: rotation.clone(), + linear_velocity: lin_vel.clone(), + angular_velocity: ang_vel.clone(), + }); + + let _ = connection.send_all(&message); +}