M Cargo.lock => Cargo.lock +27 -0
@@ 1012,6 1012,30 @@ dependencies = [
]
[[package]]
+name = "bevy_dev_tools"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f29a3abf7b8e0148ef4f74a4ec63f4efb00a3d62aba361921a5d259410556d5"
+dependencies = [
+ "bevy_app 0.16.1",
+ "bevy_asset 0.16.1",
+ "bevy_color",
+ "bevy_diagnostic 0.16.1",
+ "bevy_ecs 0.16.1",
+ "bevy_input 0.16.1",
+ "bevy_picking",
+ "bevy_reflect 0.16.1",
+ "bevy_render 0.16.1",
+ "bevy_state",
+ "bevy_text",
+ "bevy_time 0.16.1",
+ "bevy_ui 0.16.1",
+ "bevy_utils 0.16.1",
+ "bevy_window 0.16.1",
+ "tracing",
+]
+
+[[package]]
name = "bevy_diagnostic"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 1427,6 1451,7 @@ dependencies = [
"bevy_color",
"bevy_core_pipeline 0.16.1",
"bevy_derive 0.16.1",
+ "bevy_dev_tools",
"bevy_diagnostic 0.16.1",
"bevy_ecs 0.16.1",
"bevy_gizmos 0.16.1",
@@ 2043,11 2068,13 @@ dependencies = [
"bevy_ecs 0.16.1",
"bevy_image",
"bevy_math 0.16.1",
+ "bevy_picking",
"bevy_platform",
"bevy_reflect 0.16.1",
"bevy_render 0.16.1",
"bevy_transform 0.16.1",
"bevy_utils 0.16.1",
+ "bevy_window 0.16.1",
"bitflags 2.9.1",
"bytemuck",
"derive_more 1.0.0",
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +3 -1
@@ 24,7 24,9 @@ bevy = { version = "0.16", default-features = false, features = [
"bevy_log",
"x11",
"wayland",
- "multi_threaded"
+ "multi_threaded",
+ "bevy_dev_tools",
+ "bevy_sprite_picking_backend"
] }
bevy_rapier2d = { version = "0.30", features = ["serde-serialize", "simd-stable"] }
bevy_common_assets = { version = "0.13", features = ["toml"] }
M crates/unified/src/client/key_input.rs => crates/unified/src/client/key_input.rs +8 -1
@@ 3,6 3,8 @@ use bevy::{
ecs::{event::EventWriter, system::Res},
input::{ButtonInput, keyboard::KeyCode},
};
+use bevy::dev_tools::picking_debug::DebugPickingMode;
+use bevy::log::info;
use bevy::prelude::ResMut;
use bevy_rapier2d::render::DebugRenderContext;
use crate::ecs::ThrustEvent;
@@ 14,11 16,16 @@ pub fn key_input_plugin(app: &mut App) {
fn debug_render_keybind(
keys: Res<ButtonInput<KeyCode>>,
- mut debug_render: ResMut<DebugRenderContext>
+ mut debug_render: ResMut<DebugRenderContext>,
+ mut picking_debug_mode: ResMut<DebugPickingMode>
) {
if keys.just_pressed(KeyCode::F3) {
debug_render.enabled = !debug_render.enabled;
}
+ if keys.just_pressed(KeyCode::F4) {
+ *picking_debug_mode = DebugPickingMode::Noisy;
+ info!("{:?}", picking_debug_mode);
+ }
}
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +6 -4
@@ 1,6 1,6 @@
mod colors;
mod incoming_particles;
-mod incoming_parts;
+mod parts;
mod key_input;
mod net;
mod starfield;
@@ 10,7 10,7 @@ mod zoom;
mod particles;
use crate::client::incoming_particles::replicated_particles_plugin;
-use crate::client::incoming_parts::incoming_parts_plugin;
+use crate::client::parts::parts_plugin;
use planet::incoming_planets::incoming_planets_plugin;
use crate::client::key_input::key_input_plugin;
use crate::client::starfield::starfield_plugin;
@@ 21,6 21,7 @@ use aeronet_websocket::client::WebSocketClient;
use bevy::core_pipeline::bloom::Bloom;
use bevy::core_pipeline::fxaa::Fxaa;
use bevy::core_pipeline::tonemapping::DebandDither;
+use bevy::dev_tools::picking_debug::DebugPickingMode;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use bevy_replicon::shared::server_entity_map::ServerEntityMap;
@@ 50,12 51,13 @@ impl Plugin for ClientPlugin {
.add_systems(Update, find_me)
.add_systems(Update, set_config)
.add_plugins((incoming_planets_plugin, indicators_plugin))
- .add_plugins(incoming_parts_plugin)
+ .add_plugins(parts_plugin)
.add_plugins(key_input_plugin)
.add_plugins(starfield_plugin)
.add_plugins(ui_plugin)
.add_plugins(replicated_particles_plugin)
- .add_plugins(zoom_plugin);
+ .add_plugins(zoom_plugin)
+ .insert_resource(DebugPickingMode::Disabled);
}
}
R crates/unified/src/client/incoming_parts.rs => crates/unified/src/client/parts.rs +44 -4
@@ 1,10 1,15 @@
-use crate::ecs::Part;
+use std::fmt::Debug;
+use crate::ecs::{CursorWorldCoordinates, Part};
use bevy::prelude::*;
use bevy_rapier2d::dynamics::MassProperties;
use bevy_rapier2d::prelude::{AdditionalMassProperties, ReadMassProperties, RigidBody};
+use crate::client::Me;
+use crate::clientevent::{PartDragControlEvent, PartDragEvent};
-pub fn incoming_parts_plugin(app: &mut App) {
- app.add_systems(Update, (handle_incoming_parts, handle_updated_parts));
+pub fn parts_plugin(app: &mut App) {
+ app.insert_resource(DragResource(None));
+ app.add_systems(Update, (handle_incoming_parts, handle_updated_parts, send_drag));
+ app.add_observer(on_part_release);
}
fn handle_incoming_parts(
@@ 25,7 30,10 @@ fn handle_incoming_parts(
principal_inertia: 7.5,
}))
.insert(ReadMassProperties::default())
- .insert(RigidBody::Dynamic);
+ .insert(RigidBody::Dynamic)
+ .insert(Pickable::default())
+ .observe(on_part_click);
+
info!(?new_part, ?new_entity, "prepared new part");
}
}
@@ 51,3 59,35 @@ fn handle_updated_parts(
info!(?updated_part, ?updated_entity, "updated part");
}
}
+
+#[derive(Resource)]
+struct DragResource(Option<Entity>);
+
+
+fn on_part_click(ev: Trigger<Pointer<Pressed>>, sprites: Query<&Sprite, Without<Me>>, mut drag: ResMut<DragResource>, mut events: EventWriter<PartDragControlEvent>) {
+ if ev.button != PointerButton::Primary { return; };
+ let Ok(sprite) = sprites.get(ev.target()) else { return; };
+
+ drag.0 = Some(ev.target());
+
+ events.write(PartDragControlEvent::Start(ev.target()));
+
+ info!(?sprite, ?ev, "start drag");
+}
+fn on_part_release(ev: Trigger<Pointer<Released>>, mut drag: ResMut<DragResource>, mut events: EventWriter<PartDragControlEvent>) {
+ if ev.button != PointerButton::Primary { return; };
+
+ if let Some(e) = drag.0 {
+ events.write(PartDragControlEvent::Stop(e));
+ }
+
+ drag.0 = None;
+
+ info!(?ev, "stop drag");
+}
+fn send_drag(drag: ResMut<DragResource>, coords: Res<CursorWorldCoordinates>, mut events: EventWriter<PartDragEvent>) {
+ let Some(dragging) = drag.0 else { return; };
+ let Some(coordinates) = coords.0 else { return; };
+
+ events.write(PartDragEvent(dragging, coordinates));
+}<
\ No newline at end of file
M crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +5 -0
@@ 3,7 3,10 @@ use aeronet_replicon::client::AeronetRepliconClientPlugin;
use aeronet_websocket::client::WebSocketClientPlugin;
use bevy::DefaultPlugins;
use bevy::app::{PluginGroup, PluginGroupBuilder};
+use bevy::dev_tools::picking_debug::DebugPickingPlugin;
use bevy::log::LogPlugin;
+use bevy::prelude::MeshPickingPlugin;
+use bevy::sprite::prelude::SpritePickingPlugin;
use bevy_enoki::EnokiPlugin;
use bevy_rapier2d::prelude::RapierDebugRenderPlugin;
use bevy_replicon::RepliconPlugins;
@@ 19,6 22,8 @@ impl PluginGroup for ClientPluginGroup {
.add(WebSocketClientPlugin)
.add(AeronetRepliconClientPlugin)
.add(EnokiPlugin)
+ .add(MeshPickingPlugin)
+ .add(DebugPickingPlugin)
.add(ClientPlugin {
server: self.server,
})
A crates/unified/src/clientevent.rs => crates/unified/src/clientevent.rs +25 -0
@@ 0,0 1,25 @@
+use bevy::ecs::entity::MapEntities;
+use bevy::math::Vec2;
+use bevy::prelude::{Entity, EntityMapper, Event};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Deserialize, Event, Serialize, Clone)]
+pub struct PartDragEvent(pub Entity, pub Vec2);
+impl MapEntities for PartDragEvent {
+ fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
+ self.0 = entity_mapper.get_mapped(self.0);
+ }
+}
+#[derive(Debug, Deserialize, Event, Serialize, Clone)]
+pub enum PartDragControlEvent {
+ Start(Entity),
+ Stop(Entity)
+}
+impl MapEntities for PartDragControlEvent {
+ fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
+ match self {
+ PartDragControlEvent::Start(entity) => *entity = entity_mapper.get_mapped(*entity),
+ PartDragControlEvent::Stop(entity) => *entity = entity_mapper.get_mapped(*entity),
+ }
+ }
+}<
\ No newline at end of file
M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +0 -2
@@ 21,8 21,6 @@ pub struct StarfieldBack;
#[derive(Resource, Default)]
pub struct CursorWorldCoordinates(pub Option<Vec2>);
-#[derive(Debug, Default, Deserialize, Event, Serialize)]
-pub struct SendBallHere(pub Vec2);
#[derive(Debug, Deserialize, Event, Serialize)]
pub enum ThrustEvent {
M crates/unified/src/lib.rs => crates/unified/src/lib.rs +1 -0
@@ 27,3 27,4 @@ pub mod server_plugins;
pub mod shared_plugins;
#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))]
pub mod particle_editor;
+pub mod clientevent;
M crates/unified/src/server/gravity.rs => crates/unified/src/server/gravity.rs +2 -1
@@ 4,13 4,14 @@ use crate::server::world_config::WorldConfigResource;
use bevy::math::FloatPow;
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
+use crate::server::part_dragging::BeingDragged;
pub fn newtonian_gravity_plugin(app: &mut App) {
app.add_systems(Update, update_gravity);
}
fn update_gravity(
- mut part_query: Query<(&Transform, &ReadMassProperties, &mut ExternalForce), With<Part>>,
+ mut part_query: Query<(&Transform, &ReadMassProperties, &mut ExternalForce), (With<Part>, Without<BeingDragged>)>,
planet_query: Query<(&Transform, &ReadMassProperties), With<Planet>>,
world_config: Res<WorldConfigResource>,
) {
M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +3 -0
@@ 3,6 3,7 @@ pub mod planets;
pub mod player;
mod world_config;
mod earth_parts;
+mod part_dragging;
use crate::server::gravity::newtonian_gravity_plugin;
use crate::server::planets::planets_plugin;
@@ 17,6 18,7 @@ use bevy::prelude::*;
use bevy_replicon::prelude::Replicated;
use std::net::SocketAddr;
use crate::server::earth_parts::spawn_parts_plugin;
+use crate::server::part_dragging::part_dragging_plugin;
pub struct ServerPlugin {
pub bind: SocketAddr,
@@ 44,6 46,7 @@ impl Plugin for ServerPlugin {
.add_plugins(world_config_plugin)
.add_plugins(newtonian_gravity_plugin)
.add_plugins(player_management_plugin)
+ .add_plugins(part_dragging_plugin)
.add_plugins(spawn_parts_plugin);
}
}
A crates/unified/src/server/part_dragging.rs => crates/unified/src/server/part_dragging.rs +41 -0
@@ 0,0 1,41 @@
+use bevy::prelude::*;
+use bevy_rapier2d::prelude::{Collider, ColliderDisabled};
+use bevy_replicon::prelude::FromClient;
+use crate::clientevent::{PartDragControlEvent, PartDragEvent};
+use crate::ecs::Part;
+
+pub fn part_dragging_plugin(app: &mut App) {
+ app.add_systems(Update, (handle_dragged_parts, handle_start_stop_drag));
+}
+
+// todo: attachment
+
+#[derive(Component)]
+pub struct BeingDragged;
+
+fn handle_dragged_parts(mut transforms: Query<&mut Transform, (With<BeingDragged>)>, mut events: EventReader<FromClient<PartDragEvent>>) {
+ for event in events.read() {
+ let Ok(mut transform) = transforms.get_mut(event.0) else { continue; };
+ transform.translation = (event.1, 0.0).into();
+ }
+}
+fn handle_start_stop_drag(mut colliders: Query<&Collider, With<Part>>, mut events: EventReader<FromClient<PartDragControlEvent>>, mut commands: Commands) {
+ for event in events.read() {
+ let entity = match &event.event {
+ PartDragControlEvent::Start(e) => *e,
+ PartDragControlEvent::Stop(e) => *e,
+ };
+ let Ok(_) = colliders.get(entity) else { continue; };
+ match &event.event {
+ PartDragControlEvent::Start(_) => {
+ commands.entity(entity)
+ .insert(BeingDragged)
+ .insert(ColliderDisabled);
+ },
+ PartDragControlEvent::Stop(_) => {
+ commands.entity(entity).remove::<BeingDragged>()
+ .remove::<ColliderDisabled>();
+ }
+ }
+ }
+}<
\ No newline at end of file
M crates/unified/src/shared_plugins.rs => crates/unified/src/shared_plugins.rs +5 -2
@@ 1,9 1,10 @@
use crate::config::planet::Planet;
-use crate::ecs::{Ball, Ground, Part, Particles, Player, SendBallHere, ThrustEvent};
+use crate::ecs::{Ball, Ground, Part, Particles, Player, ThrustEvent};
use bevy::app::{App, PluginGroup, PluginGroupBuilder};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use bevy_replicon::prelude::{AppRuleExt, Channel, ClientEventAppExt};
+use crate::clientevent::{PartDragControlEvent, PartDragEvent};
pub struct SharedPluginGroup;
@@ 17,8 18,10 @@ impl PluginGroup for SharedPluginGroup {
}
pub fn register_everything(app: &mut App) {
- app.add_client_event::<SendBallHere>(Channel::Ordered)
+ app
.add_client_event::<ThrustEvent>(Channel::Ordered)
+ .add_mapped_client_event::<PartDragEvent>(Channel::Unreliable)
+ .add_mapped_client_event::<PartDragControlEvent>(Channel::Ordered)
.replicate::<Transform>()
.replicate::<Ball>()
.replicate::<Ground>()