M Cargo.lock => Cargo.lock +0 -1
@@ 7857,7 7857,6 @@ dependencies = [
"bevy_replicon",
"bevy_replicon_renet2",
"clap",
- "crossbeam-channel",
"log",
"serde",
"tracing-subscriber",
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +0 -1
@@ 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
M crates/unified/assets/config/world.wc.toml => crates/unified/assets/config/world.wc.toml +6 -1
@@ 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
A crates/unified/src/client/incoming_parts.rs => crates/unified/src/client/incoming_parts.rs +35 -0
@@ 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<Part>>, asset_server: Res<AssetServer>) {
+ 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<Part>>, asset_server: Res<AssetServer>) {
+ 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::<Sprite>()
+ .remove::<AdditionalMassProperties>()
+ .insert(sprite)
+ .insert(AdditionalMassProperties::Mass(updated_part.mass));
+ info!(?updated_part, "updated part");
+ }
+}<
\ No newline at end of file
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +4 -34
@@ 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<Entity, Added<Ball>>, asset_server: Res<AssetServer>) {
- 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<Entity, Added<Ground>>, asset_server: Res<AssetServer>) {
- 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<PrimaryWindow>>,
q_camera: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
@@ 92,13 71,4 @@ fn update_cursor_position(
} else {
coords.0 = None;
}
-}
-fn teleport_cube_around(
- cursor_world_coordinates: Res<CursorWorldCoordinates>,
- button_input: Res<ButtonInput<MouseButton>>,
- mut events: EventWriter<SendBallHere>
-) {
- if let Some(position) = cursor_world_coordinates.0 && button_input.pressed(MouseButton::Left) {
- events.write(SendBallHere(position));
- }
}=
\ No newline at end of file
M crates/unified/src/config/world.rs => crates/unified/src/config/world.rs +15 -2
@@ 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
M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +23 -2
@@ 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<Vec2>);
#[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
A crates/unified/src/server/gravity.rs => crates/unified/src/server/gravity.rs +46 -0
@@ 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<Part>
+ >,
+ planet_query: Query<(&Transform, &ReadMassProperties), With<Planet>>,
+ world_config: Res<WorldConfigResource>,
+) {
+ 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
M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +11 -31
@@ 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<FromClient<SendBallHere>>, mut ball: Query<(&mut Transform, &mut Velocity), With<Ball>>) {
- 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
A crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +30 -0
@@ 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<Entity, Added<ConnectedClient>>, world_config: Res<WorldConfigResource>) {
+ 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
A crates/unified/src/server/world_config.rs => crates/unified/src/server/world_config.rs +56 -0
@@ 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::<WorldConfigResource>()
+ .add_systems(Startup, start_loading_planets)
+ .add_systems(Update, update_planets);
+}
+
+#[derive(Resource, Default)]
+pub struct WorldConfigResource {
+ handle: Option<Handle<GlobalWorldConfig>>,
+ pub config: Option<GlobalWorldConfig>
+}
+
+fn start_loading_planets(assets: Res<AssetServer>, mut planets: ResMut<WorldConfigResource>) {
+ planets.handle = Some(assets.load("config/world.wc.toml"));
+}
+
+
+pub fn update_planets(
+ mut commands: Commands,
+ mut ev_config: EventReader<AssetEvent<GlobalWorldConfig>>,
+ mut assets: ResMut<Assets<GlobalWorldConfig>>,
+ mut resource: ResMut<WorldConfigResource>,
+) {
+ 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
M crates/unified/src/server_plugins.rs => crates/unified/src/server_plugins.rs +2 -2
@@ 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::<WorldConfig>::new(&["wc.toml"]))
+ .add(TomlAssetPlugin::<GlobalWorldConfig>::new(&["wc.toml"]))
.add(TomlAssetPlugin::<PlanetConfigCollection>::new(&["pc.toml"]))
.add(crate::server::ServerPlugin {
bind: self.bind,
M crates/unified/src/shared_plugins.rs => crates/unified/src/shared_plugins.rs +17 -4
@@ 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::<NoUserData>::pixels_per_meter(100.0)
)
.add(register_everything)
+ .add(physics_setup_plugin)
}
}
@@ 25,5 25,18 @@ pub fn register_everything(app: &mut App) {
.replicate::<Ball>()
.replicate::<Ground>()
.replicate::<Collider>()
- .replicate::<Planet>();
+ .replicate::<Planet>()
+ .replicate::<Part>();
+}
+
+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