From 48c6475b2c83e62ef93be47f4f45fe7a6ae3678c Mon Sep 17 00:00:00 2001 From: ghostly_zsh Date: Wed, 2 Jul 2025 12:50:59 -0500 Subject: [PATCH] player input --- crates/unified/assets/config/world.wc.toml | 5 +- crates/unified/src/client/incoming_parts.rs | 2 +- crates/unified/src/client/key_input.rs | 36 ++++++++ crates/unified/src/client/mod.rs | 7 +- crates/unified/src/config/world.rs | 8 +- crates/unified/src/ecs.rs | 18 +++- crates/unified/src/server/gravity.rs | 8 +- crates/unified/src/server/mod.rs | 2 +- crates/unified/src/server/player.rs | 91 +++++++++++++++++++-- crates/unified/src/shared_plugins.rs | 7 +- 10 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 crates/unified/src/client/key_input.rs diff --git a/crates/unified/assets/config/world.wc.toml b/crates/unified/assets/config/world.wc.toml index a2eca17a7e68e39453c63688d47fadcf42b24674..7640dfeafade42bc1503706101d3fc942eea8ea0 100644 --- a/crates/unified/assets/config/world.wc.toml +++ b/crates/unified/assets/config/world.wc.toml @@ -4,4 +4,7 @@ gravity = 2 [part] default_height = 50 default_width = 50 -default_mass = 100 \ No newline at end of file +default_mass = 100 + +[hearty] +thrust = 50000 diff --git a/crates/unified/src/client/incoming_parts.rs b/crates/unified/src/client/incoming_parts.rs index 67e1db3af2e67572521d7f3fabb1c100541010ca..aac1ffb105e468e2885e2d74c7d48a4cab226278 100644 --- a/crates/unified/src/client/incoming_parts.rs +++ b/crates/unified/src/client/incoming_parts.rs @@ -41,4 +41,4 @@ fn handle_updated_parts(mut commands: Commands, mut updated_parts: Query<(Entity })); info!(?updated_part, "updated part"); } -} \ No newline at end of file +} diff --git a/crates/unified/src/client/key_input.rs b/crates/unified/src/client/key_input.rs new file mode 100644 index 0000000000000000000000000000000000000000..07375c318b04843ae74700c96294bb2d0b075f05 --- /dev/null +++ b/crates/unified/src/client/key_input.rs @@ -0,0 +1,36 @@ +use bevy::{app::{App, Update}, ecs::{event::EventWriter, system::Res}, input::{keyboard::KeyCode, ButtonInput}}; + +use crate::ecs::ThrustEvent; + +pub fn key_input_plugin(app: &mut App) { + app.add_systems(Update, directional_keys); +} + +pub fn directional_keys( + keys: Res>, + mut thrust_event: EventWriter, +) { + if keys.just_pressed(KeyCode::KeyW) || keys.just_pressed(KeyCode::ArrowUp) { + thrust_event.write(ThrustEvent::Up(true)); + } else if keys.just_released(KeyCode::KeyW) || keys.just_released(KeyCode::ArrowUp) { + thrust_event.write(ThrustEvent::Up(false)); + } + + if keys.just_pressed(KeyCode::KeyS) || keys.just_pressed(KeyCode::ArrowDown) { + thrust_event.write(ThrustEvent::Down(true)); + } else if keys.just_released(KeyCode::KeyS) || keys.just_released(KeyCode::ArrowDown) { + thrust_event.write(ThrustEvent::Down(false)); + } + + if keys.just_pressed(KeyCode::KeyA) || keys.just_pressed(KeyCode::ArrowLeft) { + thrust_event.write(ThrustEvent::Left(true)); + } else if keys.just_released(KeyCode::KeyA) || keys.just_released(KeyCode::ArrowLeft) { + thrust_event.write(ThrustEvent::Left(false)); + } + + if keys.just_pressed(KeyCode::KeyD) || keys.just_pressed(KeyCode::ArrowRight) { + thrust_event.write(ThrustEvent::Right(true)); + } else if keys.just_released(KeyCode::KeyD) || keys.just_released(KeyCode::ArrowRight) { + thrust_event.write(ThrustEvent::Right(false)); + } +} diff --git a/crates/unified/src/client/mod.rs b/crates/unified/src/client/mod.rs index c7a3802d5be19ca5be1d37f03099b26131d910a3..37b15ff89b31324e52cf703c2e7fd9a40774ba82 100644 --- a/crates/unified/src/client/mod.rs +++ b/crates/unified/src/client/mod.rs @@ -1,5 +1,6 @@ mod incoming_planets; mod incoming_parts; +mod key_input; use std::net::{SocketAddr, UdpSocket}; use std::time::SystemTime; @@ -13,6 +14,7 @@ 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::client::key_input::key_input_plugin; use crate::ecs::{Ball, CursorWorldCoordinates, Ground, MainCamera, Player, SendBallHere}; pub struct ClientPlugin { @@ -50,7 +52,8 @@ impl Plugin for ClientPlugin { .add_systems(Update, follow_camera) .add_systems(Update, find_me) .add_plugins(incoming_planets_plugin) - .add_plugins(incoming_parts_plugin); + .add_plugins(incoming_parts_plugin) + .add_plugins(key_input_plugin); } } @@ -94,4 +97,4 @@ fn update_cursor_position( } else { coords.0 = None; } -} \ No newline at end of file +} diff --git a/crates/unified/src/config/world.rs b/crates/unified/src/config/world.rs index 54aa0e914f632eb129af2613cec7201fe711205f..a45fd2d5591154b6f1aaf6fe94d21641f7df35c9 100644 --- a/crates/unified/src/config/world.rs +++ b/crates/unified/src/config/world.rs @@ -6,6 +6,7 @@ use serde::Deserialize; pub struct GlobalWorldConfig { pub world: WorldConfig, pub part: PartConfig, + pub hearty: HeartyConfig, } #[derive(Deserialize, Asset, TypePath, Clone)] @@ -18,4 +19,9 @@ pub struct PartConfig { pub default_width: f32, pub default_height: f32, pub default_mass: f32 -} \ No newline at end of file +} + +#[derive(Deserialize, Asset, TypePath, Clone)] +pub struct HeartyConfig { + pub thrust: f32, +} diff --git a/crates/unified/src/ecs.rs b/crates/unified/src/ecs.rs index 1c551f1bbbe663cd8e073ef2154364610b73303c..07c1b1c18c8895e979941e5a874219314bd4a957 100644 --- a/crates/unified/src/ecs.rs +++ b/crates/unified/src/ecs.rs @@ -19,6 +19,14 @@ pub struct CursorWorldCoordinates(pub Option); #[derive(Debug, Default, Deserialize, Event, Serialize)] pub struct SendBallHere(pub Vec2); +#[derive(Debug, Deserialize, Event, Serialize)] +pub enum ThrustEvent { + Up(bool), + Down(bool), + Left(bool), + Right(bool), +} + #[derive(Component, Serialize, Deserialize, Debug)] #[require(ReadMassProperties, RigidBody::Dynamic, ExternalForce, ExternalImpulse)] pub struct Part { @@ -38,4 +46,12 @@ pub struct PartBundle { #[derive(Component, Serialize, Deserialize, Debug)] pub struct Player { pub client: Entity -} \ No newline at end of file +} + +#[derive(Component, Default, Serialize, Deserialize, Debug)] +pub struct PlayerThrust { + pub up: bool, + pub down: bool, + pub left: bool, + pub right: bool, +} diff --git a/crates/unified/src/server/gravity.rs b/crates/unified/src/server/gravity.rs index 5c9f2d6a526b2afdde88453d446a363f5ed39ec3..3e3ac8dd3e5eee2ac214bcc4e861297baedde7f5 100644 --- a/crates/unified/src/server/gravity.rs +++ b/crates/unified/src/server/gravity.rs @@ -5,7 +5,7 @@ use crate::config::planet::Planet; use crate::ecs::Part; use crate::server::world_config::WorldConfigResource; -pub fn newtonian_gravity_plugin(mut app: &mut App) { +pub fn newtonian_gravity_plugin(app: &mut App) { app.add_systems(Update, update_gravity); } @@ -25,10 +25,6 @@ fn update_gravity( 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; @@ -43,4 +39,4 @@ fn update_gravity( 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 6018b31b28f92eec61a4df5a667eef1b7d177737..d3d851e092454d2508247a052db7706542c92961 100644 --- a/crates/unified/src/server/mod.rs +++ b/crates/unified/src/server/mod.rs @@ -55,4 +55,4 @@ impl Plugin for ServerPlugin { .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 index 01af78baf695ae6a404677e060ebd62bf726bb54..5ea5ca5b2c93c3ec8c22fea2b640e78ce02aa57c 100644 --- a/crates/unified/src/server/player.rs +++ b/crates/unified/src/server/player.rs @@ -1,12 +1,26 @@ +use std::f32::consts::PI; + use bevy::prelude::*; -use bevy_rapier2d::prelude::{AdditionalMassProperties, Collider, MassProperties, ReadMassProperties, RigidBody}; -use bevy_replicon::prelude::{ConnectedClient, Replicated}; +use bevy_rapier2d::prelude::{AdditionalMassProperties, Collider, ExternalForce, ExternalImpulse, MassProperties, ReadMassProperties, RigidBody, Sensor}; +use bevy_replicon::prelude::{ConnectedClient, FromClient, Replicated}; use crate::config::planet::Planet; -use crate::ecs::{Part, PartBundle, Player}; +use crate::ecs::{Part, PartBundle, Player, PlayerThrust, ThrustEvent}; use crate::server::world_config::WorldConfigResource; -pub fn player_management_plugin(mut app: &mut App) { - app.add_systems(Update, handle_new_players); +pub fn player_management_plugin(app: &mut App) { + app + .add_systems(PreUpdate, reset_movement) + .add_systems(Update, (handle_new_players, player_thrust)); +} + +fn reset_movement( + mut players: Query<(&mut ExternalForce, &mut ExternalImpulse)> +) { + for (mut force, mut impulse) in &mut players { + force.force = Vec2::ZERO; + force.torque = 0.0; + impulse.impulse = Vec2::ZERO; + } } fn handle_new_players(mut commands: Commands, q_new_clients: Query>, world_config: Res, planets: Query<(&Transform, &Planet)>) { @@ -38,8 +52,73 @@ fn handle_new_players(mut commands: Commands, q_new_clients: Query, + mut thrust_event: EventReader>, + world_config: Res, +) { + use ThrustEvent::*; + for event in thrust_event.read() { + let FromClient { client_entity, event } = event; + let Ok((_, _, mut thrust)) = players.get_mut(*client_entity) else { continue }; + match *event { + Up(on) => thrust.up = on, + Down(on) => thrust.down = on, + Left(on) => thrust.left = on, + Right(on) => thrust.right = on, + } + } + for (transform, mut force, thrust) in &mut players { + let Some(world_config) = &world_config.config else { return; }; + + let forward = (transform.rotation * Vec3::Y).xy(); + let mut external_force = ExternalForce::default(); + let mut thrusters = [0.0; 4]; // counterclockwise wrt +y axis + if thrust.up { + thrusters[1] = 1.0; + thrusters[2] = 1.0; + } + if thrust.down { + thrusters[0] = 1.0; + thrusters[3] = 1.0; + } + if thrust.left { + thrusters[0] = 1.0; + thrusters[2] = 1.0; + } + if thrust.right { + thrusters[1] = 1.0; + thrusters[3] = 1.0; + } + let half_size = Vec2::new(world_config.part.default_width/2.0, world_config.part.default_height/2.0).length(); + external_force += ExternalForce::at_point( + -forward*thrusters[0]*world_config.hearty.thrust, + transform.translation.xy() + half_size*Vec2::new((1.0*PI/4.0).cos(), (1.0*PI/4.0).sin()).rotate(forward), + transform.translation.xy(), + ); + external_force += ExternalForce::at_point( + forward*thrusters[1]*world_config.hearty.thrust, + transform.translation.xy() + half_size*Vec2::new((3.0*PI/4.0).cos(), (3.0*PI/4.0).sin()).rotate(forward), + transform.translation.xy(), + ); + external_force += ExternalForce::at_point( + forward*thrusters[2]*world_config.hearty.thrust, + transform.translation.xy() + half_size*Vec2::new((5.0*PI/4.0).cos(), (5.0*PI/4.0).sin()).rotate(forward), + transform.translation.xy(), + ); + external_force += ExternalForce::at_point( + -forward*thrusters[3]*world_config.hearty.thrust, + transform.translation.xy() + half_size*Vec2::new((7.0*PI/4.0).cos(), (7.0*PI/4.0).sin()).rotate(forward), + transform.translation.xy(), + ); + *force += external_force; + } +} diff --git a/crates/unified/src/shared_plugins.rs b/crates/unified/src/shared_plugins.rs index 9596503cc99b09f85b7bab7d14981efe793f5162..c34e90f4a3cdfc708f60422868b152d5a19046a0 100644 --- a/crates/unified/src/shared_plugins.rs +++ b/crates/unified/src/shared_plugins.rs @@ -1,9 +1,9 @@ use bevy::app::{App, PluginGroup, PluginGroupBuilder}; use bevy::prelude::*; use bevy_rapier2d::prelude::*; -use bevy_replicon::prelude::{AppRuleExt, Channel, ClientEventAppExt}; +use bevy_replicon::prelude::{AppRuleExt, Channel, ClientEventAppExt, FromClient, ServerEventAppExt}; use crate::config::planet::Planet; -use crate::ecs::{Ball, Ground, Part, Player, SendBallHere}; +use crate::ecs::{Ball, Ground, Part, Player, SendBallHere, ThrustEvent}; pub struct SharedPluginGroup; @@ -21,6 +21,7 @@ impl PluginGroup for SharedPluginGroup { pub fn register_everything(app: &mut App) { app .add_client_event::(Channel::Ordered) + .add_client_event::(Channel::Ordered) .replicate::() .replicate::() .replicate::() @@ -40,4 +41,4 @@ fn setup_physics( ) { let mut cfg = rapier_config.single_mut().unwrap(); cfg.gravity = Vec2::ZERO; -} \ No newline at end of file +}