From 4fb7a538004c7b3fb5880cf3e71dbd782e877910 Mon Sep 17 00:00:00 2001 From: core Date: Tue, 1 Jul 2025 16:03:34 -0400 Subject: [PATCH] gravity --- Cargo.lock | 1 - crates/unified/Cargo.toml | 1 - crates/unified/assets/config/world.wc.toml | 7 ++- crates/unified/src/client/incoming_parts.rs | 35 +++++++++++++ crates/unified/src/client/mod.rs | 38 ++------------ crates/unified/src/config/world.rs | 17 ++++++- crates/unified/src/ecs.rs | 25 ++++++++- crates/unified/src/server/gravity.rs | 46 +++++++++++++++++ crates/unified/src/server/mod.rs | 42 ++++------------ crates/unified/src/server/player.rs | 30 +++++++++++ crates/unified/src/server/world_config.rs | 56 +++++++++++++++++++++ crates/unified/src/server_plugins.rs | 4 +- crates/unified/src/shared_plugins.rs | 21 ++++++-- 13 files changed, 245 insertions(+), 78 deletions(-) create mode 100644 crates/unified/src/client/incoming_parts.rs create mode 100644 crates/unified/src/server/gravity.rs create mode 100644 crates/unified/src/server/player.rs create mode 100644 crates/unified/src/server/world_config.rs diff --git a/Cargo.lock b/Cargo.lock index 8e587c92ec31a0050f13e11500d8e68a35b228b1..2b65a025ccd2ed47376f10c53b13430ed4e4cf2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7857,7 +7857,6 @@ dependencies = [ "bevy_replicon", "bevy_replicon_renet2", "clap", - "crossbeam-channel", "log", "serde", "tracing-subscriber", diff --git a/crates/unified/Cargo.toml b/crates/unified/Cargo.toml index bcc5c4ff27091ada4d631b7e8d880bc2bacf88be..0e7d7de643b88b931b994e17bbc8288b32f7e2a7 100644 --- a/crates/unified/Cargo.toml +++ b/crates/unified/Cargo.toml @@ -17,5 +17,4 @@ clap = { version = "4", features = ["derive", "cargo"] } tracing-subscriber = "0.3" log = { version = "*", features = ["max_level_debug", "release_max_level_warn"] } -crossbeam-channel = "0.5" serde = { version = "1", features = ["derive"] } \ No newline at end of file diff --git a/crates/unified/assets/config/world.wc.toml b/crates/unified/assets/config/world.wc.toml index 06d4a392233361d25a6da588df4834dcbc2732d7..7a9fbae37eac85ffc5bc2d79740bd8016dcda5e4 100644 --- a/crates/unified/assets/config/world.wc.toml +++ b/crates/unified/assets/config/world.wc.toml @@ -1,2 +1,7 @@ [world] -gravity = 0.015 \ No newline at end of file +gravity = 0.015 + +[part] +default_height = 25 +default_width = 25 +default_mass = 50 \ No newline at end of file diff --git a/crates/unified/src/client/incoming_parts.rs b/crates/unified/src/client/incoming_parts.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e42e3c6b363442178e572169d5bfc2856128795 --- /dev/null +++ b/crates/unified/src/client/incoming_parts.rs @@ -0,0 +1,35 @@ +use bevy::prelude::*; +use bevy_rapier2d::prelude::{AdditionalMassProperties, ReadMassProperties, RigidBody}; +use crate::config::planet::Planet; +use crate::ecs::Part; + +pub fn incoming_parts_plugin(app: &mut App) { + app.add_systems(Update, (handle_incoming_parts, handle_updated_parts)); +} + +fn handle_incoming_parts(mut commands: Commands, mut new_parts: Query<(Entity, &Part), Added>, asset_server: Res) { + for (new_entity, new_part) in new_parts.iter() { + let mut sprite = Sprite::from_image(asset_server.load(&new_part.sprite)); + sprite.custom_size = Some(Vec2::new(new_part.width, new_part.height)); + + commands.entity(new_entity) + .insert(sprite) + .insert(AdditionalMassProperties::Mass(new_part.mass)) + .insert(ReadMassProperties::default()) + .insert(RigidBody::Dynamic); + info!(?new_part, "prepared new part"); + } +} +fn handle_updated_parts(mut commands: Commands, mut updated_parts: Query<(Entity, &Part), Changed>, asset_server: Res) { + for (updated_entity, updated_part) in updated_parts.iter() { + let mut sprite = Sprite::from_image(asset_server.load(&updated_part.sprite)); + sprite.custom_size = Some(Vec2::new(updated_part.width, updated_part.height)); + + commands.entity(updated_entity) + .remove::() + .remove::() + .insert(sprite) + .insert(AdditionalMassProperties::Mass(updated_part.mass)); + info!(?updated_part, "updated part"); + } +} \ No newline at end of file diff --git a/crates/unified/src/client/mod.rs b/crates/unified/src/client/mod.rs index d089004d95a6c47e752a9eb73427065e7d68c8b2..30955db2a46834c5cb96988be441479bc73073c8 100644 --- a/crates/unified/src/client/mod.rs +++ b/crates/unified/src/client/mod.rs @@ -1,4 +1,5 @@ mod incoming_planets; +mod incoming_parts; use std::net::{SocketAddr, UdpSocket}; use std::time::SystemTime; @@ -9,6 +10,7 @@ use bevy_replicon::prelude::RepliconChannels; use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport, NativeSocket}; use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetClient}; use bevy_replicon_renet2::RenetChannelsExt; +use crate::client::incoming_parts::incoming_parts_plugin; use crate::client::incoming_planets::incoming_planets_plugin; use crate::ecs::{Ball, CursorWorldCoordinates, Ground, MainCamera, SendBallHere}; @@ -43,11 +45,9 @@ impl Plugin for ClientPlugin { info!(?client_id, "connected!"); }) .add_systems(Startup, setup_graphics) - //.add_systems(Update, add_ball_sprite) - //.add_systems(Update, add_ground_sprite) .add_systems(Update, update_cursor_position) - .add_systems(Update, teleport_cube_around) - .add_plugins(incoming_planets_plugin); + .add_plugins(incoming_planets_plugin) + .add_plugins(incoming_parts_plugin); } } @@ -56,27 +56,6 @@ fn setup_graphics(mut commands: Commands) { .insert(MainCamera); } -fn add_ball_sprite(mut commands: Commands, q: Query>, asset_server: Res) { - for item in &q { - let mut sprite = Sprite::from_image(asset_server.load("textures/earth.png")); - sprite.custom_size = Some(Vec2::new(100.0, 100.0)); - - commands - .entity(item) - .insert(sprite); - } -} -fn add_ground_sprite(mut commands: Commands, q: Query>, asset_server: Res) { - for item in &q { - let mut sprite = Sprite::from_image(asset_server.load("textures/hearty.png")); - sprite.custom_size = Some(Vec2::new(1000.0, 100.0)); - - commands - .entity(item) - .insert(sprite); - } -} - fn update_cursor_position( q_windows: Query<&Window, With>, q_camera: Query<(&Camera, &GlobalTransform), With>, @@ -92,13 +71,4 @@ fn update_cursor_position( } else { coords.0 = None; } -} -fn teleport_cube_around( - cursor_world_coordinates: Res, - button_input: Res>, - mut events: EventWriter -) { - if let Some(position) = cursor_world_coordinates.0 && button_input.pressed(MouseButton::Left) { - events.write(SendBallHere(position)); - } } \ No newline at end of file diff --git a/crates/unified/src/config/world.rs b/crates/unified/src/config/world.rs index 997bd814b51b7914739bfaee4bf3cfdd397da85f..54aa0e914f632eb129af2613cec7201fe711205f 100644 --- a/crates/unified/src/config/world.rs +++ b/crates/unified/src/config/world.rs @@ -2,7 +2,20 @@ use bevy::asset::Asset; use bevy::prelude::TypePath; use serde::Deserialize; -#[derive(Deserialize, Asset, TypePath)] +#[derive(Deserialize, Asset, TypePath, Clone)] +pub struct GlobalWorldConfig { + pub world: WorldConfig, + pub part: PartConfig, +} + +#[derive(Deserialize, Asset, TypePath, Clone)] pub struct WorldConfig { - pub gravity: f64 + pub gravity: f32 +} + +#[derive(Deserialize, Asset, TypePath, Clone)] +pub struct PartConfig { + pub default_width: f32, + pub default_height: f32, + pub default_mass: f32 } \ No newline at end of file diff --git a/crates/unified/src/ecs.rs b/crates/unified/src/ecs.rs index a5b730ce0cf84caa44c32d9bc3988ad82286a8a9..2c72842c60e7383e6ecb5c4da75f264d17bb40d4 100644 --- a/crates/unified/src/ecs.rs +++ b/crates/unified/src/ecs.rs @@ -1,5 +1,10 @@ use bevy::math::Vec2; -use bevy::prelude::{Component, Event, Resource}; +use bevy::prelude::{Bundle, Component, Event, Resource, Transform}; +use bevy_rapier2d::dynamics::AdditionalMassProperties; +use bevy_replicon::prelude::Replicated; +use bevy_rapier2d::dynamics::RigidBody; +use bevy_rapier2d::geometry::Collider; +use bevy_rapier2d::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Component, Serialize, Deserialize)] @@ -12,4 +17,20 @@ pub struct MainCamera; pub struct CursorWorldCoordinates(pub Option); #[derive(Debug, Default, Deserialize, Event, Serialize)] -pub struct SendBallHere(pub Vec2); \ No newline at end of file +pub struct SendBallHere(pub Vec2); + +#[derive(Component, Serialize, Deserialize, Debug)] +#[require(ReadMassProperties, RigidBody::Dynamic, ExternalForce, ExternalImpulse)] +pub struct Part { + pub sprite: String, + pub width: f32, + pub height: f32, + pub mass: f32 +} +#[derive(Bundle)] +pub struct PartBundle { + pub part: Part, + pub transform: Transform, + pub collider: Collider, + pub additional_mass_properties: AdditionalMassProperties, +} \ No newline at end of file diff --git a/crates/unified/src/server/gravity.rs b/crates/unified/src/server/gravity.rs new file mode 100644 index 0000000000000000000000000000000000000000..5c9f2d6a526b2afdde88453d446a363f5ed39ec3 --- /dev/null +++ b/crates/unified/src/server/gravity.rs @@ -0,0 +1,46 @@ +use bevy::math::FloatPow; +use bevy::prelude::*; +use bevy_rapier2d::prelude::*; +use crate::config::planet::Planet; +use crate::ecs::Part; +use crate::server::world_config::WorldConfigResource; + +pub fn newtonian_gravity_plugin(mut app: &mut App) { + app.add_systems(Update, update_gravity); +} + +fn update_gravity( + mut part_query: Query< + ( + &Transform, + &ReadMassProperties, + &mut ExternalForce, + &mut ExternalImpulse + ), + With + >, + planet_query: Query<(&Transform, &ReadMassProperties), With>, + world_config: Res, +) { + let Some(world_config) = &world_config.config else { return; }; + + for (part_transform, part_mass, mut forces, mut impulses) in &mut part_query { + forces.force = Vec2::ZERO; + impulses.impulse = Vec2::ZERO; + forces.torque = 0.0; + + let part_mass = part_mass.mass; + let part_translation = part_transform.translation; + + for (planet_transform, planet_mass) in &planet_query { + let planet_mass = planet_mass.mass; + let planet_translation = planet_transform.translation; + + let distance = planet_translation.distance(part_translation); + + let force = world_config.world.gravity * ((part_mass * planet_mass) / distance.squared()); + let direction = (planet_translation - part_translation).normalize() * force; + forces.force += direction.xy(); + } + } +} \ No newline at end of file diff --git a/crates/unified/src/server/mod.rs b/crates/unified/src/server/mod.rs index 235473983f96623fb81c3a3e8421efeb2b2708f7..6018b31b28f92eec61a4df5a667eef1b7d177737 100644 --- a/crates/unified/src/server/mod.rs +++ b/crates/unified/src/server/mod.rs @@ -1,4 +1,7 @@ -mod planets; +pub mod planets; +pub mod player; +mod world_config; +mod gravity; use std::net::{SocketAddr, UdpSocket}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -9,7 +12,10 @@ use bevy_replicon_renet2::netcode::{NativeSocket, NetcodeServerTransport, Server use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetServer}; use bevy_replicon_renet2::RenetChannelsExt; use crate::ecs::{Ball, Ground, SendBallHere}; +use crate::server::gravity::newtonian_gravity_plugin; use crate::server::planets::planets_plugin; +use crate::server::player::player_management_plugin; +use crate::server::world_config::world_config_plugin; pub struct ServerPlugin { pub bind: SocketAddr, @@ -44,35 +50,9 @@ impl Plugin for ServerPlugin { info!("websocket server listening"); }) - .add_systems(Startup, setup_physics) - .add_systems(PreUpdate, receive_send_ball_here) - .add_plugins(planets_plugin); - } -} - -fn setup_physics(mut commands: Commands) { - commands.spawn(Collider::cuboid(500.0, 50.0)) - .insert(Transform::from_xyz(0.0, -100.0, 0.0)) - .insert(Restitution::coefficient(1.0)) - .insert(Ground) - .insert(Replicated); - - commands.spawn(RigidBody::Dynamic) - .insert(Collider::ball(50.0)) - .insert(Restitution::coefficient(1.0)) - .insert(Transform::from_xyz(0.0, 400.0, 0.0)) - .insert(Velocity::default()) - .insert(Ball) - .insert(Replicated); -} - -fn receive_send_ball_here(mut events: EventReader>, mut ball: Query<(&mut Transform, &mut Velocity), With>) { - for FromClient { client_entity, event } in events.read() { - for (mut position, mut velocity) in &mut ball { - position.translation.x = event.0.x; - position.translation.y = event.0.y; - velocity.linvel = Vec2::ZERO; - velocity.angvel = 0.0; - } + .add_plugins(planets_plugin) + .add_plugins(world_config_plugin) + .add_plugins(newtonian_gravity_plugin) + .add_plugins(player_management_plugin); } } \ No newline at end of file diff --git a/crates/unified/src/server/player.rs b/crates/unified/src/server/player.rs new file mode 100644 index 0000000000000000000000000000000000000000..926b31963dd0dd6cd60f9a7edb14d319b34aacc4 --- /dev/null +++ b/crates/unified/src/server/player.rs @@ -0,0 +1,30 @@ +use bevy::prelude::*; +use bevy_rapier2d::prelude::{AdditionalMassProperties, Collider, ReadMassProperties, RigidBody}; +use bevy_replicon::prelude::{ConnectedClient, Replicated}; +use crate::ecs::{Part, PartBundle}; +use crate::server::world_config::WorldConfigResource; + +pub fn player_management_plugin(mut app: &mut App) { + app.add_systems(Update, handle_new_players); +} + +fn handle_new_players(mut commands: Commands, q_new_clients: Query>, world_config: Res) { + let Some(wc) = &world_config.config else { return; }; + for joined_player in &q_new_clients { + commands.entity(joined_player) + .insert(PartBundle { + part: Part { + sprite: "textures/hearty.png".to_string(), + width: wc.part.default_width, + height: wc.part.default_height, + mass: wc.part.default_mass + }, + transform: Transform::from_xyz(500.0, 0.0, 0.0), // todo, + collider: Collider::cuboid(wc.part.default_width / 2.0, wc.part.default_height / 2.0), + additional_mass_properties: AdditionalMassProperties::Mass(wc.part.default_mass) + }) + .insert(ReadMassProperties::default()) + .insert(RigidBody::Dynamic) + .insert(Replicated); + } +} \ No newline at end of file diff --git a/crates/unified/src/server/world_config.rs b/crates/unified/src/server/world_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a104e27b541f055c02960cb096ffbfa858018a3 --- /dev/null +++ b/crates/unified/src/server/world_config.rs @@ -0,0 +1,56 @@ +use bevy::asset::Handle; +use bevy::prelude::*; +use bevy_rapier2d::dynamics::AdditionalMassProperties; +use bevy_rapier2d::prelude::Collider; +use bevy_replicon::prelude::Replicated; +use crate::config::planet::{Planet, PlanetBundle, PlanetConfigCollection}; +use crate::config::world::{GlobalWorldConfig, WorldConfig}; + +pub fn world_config_plugin(app: &mut App) { + app + .init_resource::() + .add_systems(Startup, start_loading_planets) + .add_systems(Update, update_planets); +} + +#[derive(Resource, Default)] +pub struct WorldConfigResource { + handle: Option>, + pub config: Option +} + +fn start_loading_planets(assets: Res, mut planets: ResMut) { + planets.handle = Some(assets.load("config/world.wc.toml")); +} + + +pub fn update_planets( + mut commands: Commands, + mut ev_config: EventReader>, + mut assets: ResMut>, + mut resource: ResMut, +) { + let Some(handle) = resource.handle.as_ref() else { return; }; + + let waiting_for_asset_id = handle.id(); + + for ev in ev_config.read() { + match ev { + AssetEvent::Added { id } => { + if *id == waiting_for_asset_id { + info!("world config loaded"); + let world_config = assets.get(*id).unwrap(); + resource.config = Some(world_config.clone()); + } + }, + AssetEvent::Modified { id } => { + if *id == waiting_for_asset_id { + info!("world config modified - reloading"); + let world_config = assets.get(*id).unwrap(); + resource.config = Some(world_config.clone()); + } + }, + _ => {} + } + } +} \ No newline at end of file diff --git a/crates/unified/src/server_plugins.rs b/crates/unified/src/server_plugins.rs index 5a840f017a84da71aa272cabe8fe04e66192f329..a54fd4882258c756b067fe4c988d80757e4dd641 100644 --- a/crates/unified/src/server_plugins.rs +++ b/crates/unified/src/server_plugins.rs @@ -8,7 +8,7 @@ use bevy_common_assets::toml::TomlAssetPlugin; use bevy_replicon::RepliconPlugins; use bevy_replicon_renet2::RepliconRenetServerPlugin; use crate::config::planet::{Planet, PlanetConfigCollection}; -use crate::config::world::WorldConfig; +use crate::config::world::{GlobalWorldConfig, WorldConfig}; pub struct ServerPluginGroup { pub bind: SocketAddr, @@ -32,7 +32,7 @@ impl PluginGroup for ServerPluginGroup { ) /* Assets */ .add(AssetPlugin::default()) - .add(TomlAssetPlugin::::new(&["wc.toml"])) + .add(TomlAssetPlugin::::new(&["wc.toml"])) .add(TomlAssetPlugin::::new(&["pc.toml"])) .add(crate::server::ServerPlugin { bind: self.bind, diff --git a/crates/unified/src/shared_plugins.rs b/crates/unified/src/shared_plugins.rs index 6405394a1bcc2bef22ffff16eaf7106f3d038387..3117b133b1fe1d1cf458a56ca0565dfd7ebb20d5 100644 --- a/crates/unified/src/shared_plugins.rs +++ b/crates/unified/src/shared_plugins.rs @@ -1,10 +1,9 @@ use bevy::app::{App, PluginGroup, PluginGroupBuilder}; -use bevy::math::vec2; -use bevy::prelude::{Sprite, Transform}; +use bevy::prelude::*; use bevy_rapier2d::prelude::*; use bevy_replicon::prelude::{AppRuleExt, Channel, ClientEventAppExt}; use crate::config::planet::Planet; -use crate::ecs::{Ball, Ground, SendBallHere}; +use crate::ecs::{Ball, Ground, Part, SendBallHere}; pub struct SharedPluginGroup; @@ -15,6 +14,7 @@ impl PluginGroup for SharedPluginGroup { RapierPhysicsPlugin::::pixels_per_meter(100.0) ) .add(register_everything) + .add(physics_setup_plugin) } } @@ -25,5 +25,18 @@ pub fn register_everything(app: &mut App) { .replicate::() .replicate::() .replicate::() - .replicate::(); + .replicate::() + .replicate::(); +} + +fn physics_setup_plugin(mut app: &mut App) { + app.add_systems(Startup, setup_physics); +} + + +fn setup_physics( + mut rapier_config: Query<&mut RapierConfiguration> +) { + let mut cfg = rapier_config.single_mut().unwrap(); + cfg.gravity = Vec2::ZERO; } \ No newline at end of file