Reorganize modules

main
copygirl 4 weeks ago
parent 138a218fc5
commit df8cc62ece
  1. 9
      client/src/assets/block.rs
  2. 13
      client/src/assets/mod.rs
  3. 92
      client/src/camera.rs
  4. 5
      client/src/input/client_inputs.rs
  5. 63
      client/src/input/cursor_grab.rs
  6. 10
      client/src/input/mod.rs
  7. 34
      client/src/main.rs
  8. 68
      client/src/ui/crosshair.rs
  9. 0
      client/src/ui/loading_screen.rs
  10. 8
      client/src/ui/mod.rs
  11. 12
      common/src/lib.rs
  12. 20
      common/src/network/client.rs
  13. 19
      common/src/network/mod.rs
  14. 34
      common/src/network/protocol.rs
  15. 31
      common/src/network/server.rs
  16. 2
      common/src/player.rs

@ -2,8 +2,8 @@ use bevy::prelude::*;
use common::prelude::*; use common::prelude::*;
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_observer(insert_block_visuals);
app.load_resource::<BlockAssets>(); app.load_resource::<BlockAssets>();
app.add_observer(insert_block_visuals);
} }
#[derive(Resource, Asset, Reflect, Clone)] #[derive(Resource, Asset, Reflect, Clone)]
@ -25,14 +25,13 @@ impl FromWorld for BlockAssets {
} }
} }
/// Observer which automatically inserts block visuals (mesh and
/// material) when an entity has the `Block` component added to it.
fn insert_block_visuals( fn insert_block_visuals(
add: On<Add, Block>, event: On<Add, Block>,
mut commands: Commands, mut commands: Commands,
block_assets: Res<BlockAssets>, block_assets: Res<BlockAssets>,
) { ) {
commands.entity(add.entity).insert(( let block = event.entity;
commands.entity(block).insert((
Mesh3d(block_assets.mesh.clone()), Mesh3d(block_assets.mesh.clone()),
MeshMaterial3d(block_assets.material.clone()), MeshMaterial3d(block_assets.material.clone()),
)); ));

@ -0,0 +1,13 @@
//! Common module to handle asset loading and adding visual components
//! (such as [`Mesh`]) to entities when they are spawned from the server.
//!
//! This doesn't mean this is the only place assets are loaded.
//! For example UI related assets may be loaded in `ui` plugins.
use bevy::prelude::*;
mod block;
pub fn plugin(app: &mut App) {
app.add_plugins(block::plugin);
}

@ -3,58 +3,6 @@ use std::f32::consts::TAU;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::input::mouse::AccumulatedMouseMotion; use bevy::input::mouse::AccumulatedMouseMotion;
use bevy::window::{CursorGrabMode, CursorOptions};
pub fn cursor_grab(
mut mouse_button_input: ResMut<ButtonInput<MouseButton>>,
key_input: Res<ButtonInput<KeyCode>>,
window: Single<(&mut Window, &mut CursorOptions)>,
) {
let (mut window, mut cursor) = window.into_inner();
let is_grabbed = cursor.grab_mode != CursorGrabMode::None;
let request_grab = mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]);
let request_ungrab = !window.focused || key_input.just_pressed(KeyCode::Escape);
if !is_grabbed && request_grab && !request_ungrab {
cursor.grab_mode = CursorGrabMode::Locked;
// To prevent other systems (such as `place_break_blocks`)
// from seeing the mouse button inputs, clear the state here.
mouse_button_input.clear();
}
if is_grabbed && request_ungrab && !request_grab {
cursor.grab_mode = CursorGrabMode::None;
}
if is_grabbed && !request_ungrab {
// Set the cursor position to the center of the window, so that when
// it is ungrabbed, it will reappear there. Because `is_grabbed` is
// not updated on grab, this block is delayed by one frame.
//
// On Wayland, since the cursor is locked into place, this only needs
// to be done once. Unfortunately, for some reason this doesn't work
// in the same frame as setting `grab_mode`, and would log an error.
//
// On X11, the cursor can't be locked into place, only confined to the
// window bounds, so we repeatedly move the cursor back to the center
// while it's grabbed.
//
// On the web, the cursor can be locked, but setting its position is
// not supported at all, so this would instead log a bunch of errors.
let center = window.resolution.size() / 2.;
#[cfg(not(target_family = "wasm"))] // skip on web
window.set_cursor_position(Some(center));
}
// Keep cursor visbility in sync with `grab_mode`.
cursor.visible = cursor.grab_mode == CursorGrabMode::None;
}
pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool {
cursor.grab_mode != CursorGrabMode::None
}
#[derive(Component, Debug)] #[derive(Component, Debug)]
pub struct CameraFreeLook { pub struct CameraFreeLook {
@ -106,43 +54,3 @@ pub fn camera_look(
// Override the camera transform's rotation. // Override the camera transform's rotation.
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, look.yaw, look.pitch); transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, look.yaw, look.pitch);
} }
#[derive(Component)]
pub struct Crosshair;
pub fn setup_crosshair(mut commands: Commands, assets: Res<AssetServer>) {
commands.spawn((
Node {
width: percent(100),
height: percent(100),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
Crosshair,
Node {
width: px(64),
height: px(64),
..default()
},
ImageNode {
image: assets.load("crosshair.png"),
..default()
},
// Hidden by default, because cursor shouldn't be grabbed at startup either.
Visibility::Hidden,
)],
));
}
pub fn update_crosshair_visibility(
cursor: Single<&CursorOptions, Changed<CursorOptions>>,
crosshair: Single<&mut Visibility, With<Crosshair>>,
) {
let is_grabbed = cursor.grab_mode != CursorGrabMode::None;
let mut crosshair_visibility = crosshair.into_inner();
*crosshair_visibility = (!is_grabbed || cursor.visible)
.then_some(Visibility::Hidden)
.unwrap_or_default();
}

@ -1,11 +1,14 @@
//! Handles client-side input using [`lightyear`], updating the
//! [`ActionState`] of the affected entities (such as the `Player`).
use bevy::prelude::*; use bevy::prelude::*;
use common::prelude::*;
use lightyear::prelude::input::client::*; use lightyear::prelude::input::client::*;
use lightyear::prelude::input::native::*; use lightyear::prelude::input::native::*;
use bevy::window::{CursorGrabMode, CursorOptions}; use bevy::window::{CursorGrabMode, CursorOptions};
use crate::camera::CameraFreeLook; use crate::camera::CameraFreeLook;
use common::network::Inputs;
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_systems( app.add_systems(

@ -0,0 +1,63 @@
use bevy::prelude::*;
use bevy::window::{CursorGrabMode, CursorOptions};
use crate::Screen;
pub fn plugin(app: &mut App) {
app.add_systems(
PreUpdate,
update_cursor_grab.run_if(in_state(Screen::Gameplay)),
);
}
fn update_cursor_grab(
mut mouse_button_input: ResMut<ButtonInput<MouseButton>>,
key_input: Res<ButtonInput<KeyCode>>,
window: Single<(&mut Window, &mut CursorOptions)>,
) {
let (mut window, mut cursor) = window.into_inner();
let is_grabbed = cursor.grab_mode != CursorGrabMode::None;
let request_grab = mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]);
let request_ungrab = !window.focused || key_input.just_pressed(KeyCode::Escape);
if !is_grabbed && request_grab && !request_ungrab {
cursor.grab_mode = CursorGrabMode::Locked;
// To prevent other systems (such as `place_break_blocks`)
// from seeing the mouse button inputs, clear the state here.
mouse_button_input.clear();
}
if is_grabbed && request_ungrab && !request_grab {
cursor.grab_mode = CursorGrabMode::None;
}
if is_grabbed && !request_ungrab {
// Set the cursor position to the center of the window, so that when
// it is ungrabbed, it will reappear there. Because `is_grabbed` is
// not updated on grab, this block is delayed by one frame.
//
// On Wayland, since the cursor is locked into place, this only needs
// to be done once. Unfortunately, for some reason this doesn't work
// in the same frame as setting `grab_mode`, and would log an error.
//
// On X11, the cursor can't be locked into place, only confined to the
// window bounds, so we repeatedly move the cursor back to the center
// while it's grabbed.
//
// On the web, the cursor can be locked, but setting its position is
// not supported at all, so this would instead log a bunch of errors.
let center = window.resolution.size() / 2.;
#[cfg(not(target_family = "wasm"))] // skip on web
window.set_cursor_position(Some(center));
}
// Keep cursor visbility in sync with `grab_mode`.
cursor.visible = cursor.grab_mode == CursorGrabMode::None;
}
pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool {
cursor.grab_mode != CursorGrabMode::None
}

@ -0,0 +1,10 @@
use bevy::prelude::*;
mod client_inputs;
mod cursor_grab;
pub use cursor_grab::is_cursor_grabbed;
pub fn plugin(app: &mut App) {
app.add_plugins((client_inputs::plugin, cursor_grab::plugin));
}

@ -9,16 +9,18 @@ use lightyear::prelude::input::native::InputMarker;
use lightyear::prelude::server::Started; use lightyear::prelude::server::Started;
mod args; mod args;
mod block_assets; mod assets;
mod camera; mod camera;
mod input; mod input;
mod loading;
mod placement; mod placement;
mod ui;
use args::*; use args::*;
use camera::*; use camera::*;
use placement::*; use placement::*;
use input::is_cursor_grabbed;
#[derive(States, Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(States, Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Screen { pub enum Screen {
Loading, Loading,
@ -26,6 +28,7 @@ pub enum Screen {
} }
fn main() -> Result { fn main() -> Result {
let args = Args::parse();
let mut app = App::new(); let mut app = App::new();
app.add_plugins( app.add_plugins(
@ -53,32 +56,23 @@ fn main() -> Result {
app.add_plugins(( app.add_plugins((
bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin, bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin,
ServerPlugin,
ClientPlugin,
common::asset_loading::plugin, common::asset_loading::plugin,
block_assets::plugin, common::network::plugin,
loading::plugin, assets::plugin,
input::plugin, input::plugin,
ui::plugin,
)); ));
app.insert_resource(Args::parse()); app.insert_resource(args);
app.insert_state(Screen::Loading); app.insert_state(Screen::Loading);
app.add_systems( app.add_systems(
OnEnter(Screen::Gameplay), OnEnter(Screen::Gameplay),
(setup_crosshair, setup_scene, start_server_or_connect), (setup_scene, start_server_or_connect),
); );
// TODO: Create and configure `SystemSet`s for gameplay, input, etc. // TODO: Create and configure `SystemSet`s for gameplay, etc.
app.add_systems( app.add_systems(Update, camera_look.run_if(is_cursor_grabbed));
Update,
(
cursor_grab,
update_crosshair_visibility.after(cursor_grab),
camera_look.after(cursor_grab).run_if(is_cursor_grabbed),
)
.run_if(in_state(Screen::Gameplay)),
);
// `place_break_blocks` requires the camera's `GlobalTransform`. // `place_break_blocks` requires the camera's `GlobalTransform`.
// For a most up-to-date value, run it after that's been updated. // For a most up-to-date value, run it after that's been updated.
@ -90,7 +84,7 @@ fn main() -> Result {
); );
app.add_observer(spawn_initial_blocks); app.add_observer(spawn_initial_blocks);
app.add_observer(handle_predicted_spawn); app.add_observer(handle_predicted_player_spawn);
app.run(); app.run();
Ok(()) Ok(())
@ -136,7 +130,7 @@ fn spawn_initial_blocks(_event: On<Add, Started>, mut blocks: Blocks) {
} }
/// When the `Predicted` player we control is being spawned, insert necessary components. /// When the `Predicted` player we control is being spawned, insert necessary components.
fn handle_predicted_spawn(event: On<Add, Predicted>, mut commands: Commands) { fn handle_predicted_player_spawn(event: On<Add, Predicted>, mut commands: Commands) {
let player = event.entity; let player = event.entity;
commands commands
.entity(player) .entity(player)

@ -0,0 +1,68 @@
use bevy::prelude::*;
use common::prelude::*;
use bevy::window::{CursorGrabMode, CursorOptions};
use crate::Screen;
pub fn plugin(app: &mut App) {
app.load_resource::<CrosshairAssets>();
app.add_systems(OnEnter(Screen::Gameplay), setup_crosshair);
app.add_systems(Update, update_crosshair_visibility);
}
#[derive(Resource, Asset, Reflect, Clone)]
#[reflect(Resource)]
pub struct CrosshairAssets {
#[dependency]
image: Handle<Image>,
}
impl FromWorld for CrosshairAssets {
fn from_world(world: &mut World) -> Self {
let assets = world.resource::<AssetServer>();
Self {
image: assets.load("crosshair.png"),
}
}
}
#[derive(Component)]
pub struct Crosshair;
fn setup_crosshair(mut commands: Commands, assets: Res<CrosshairAssets>) {
commands.spawn((
Node {
width: percent(100),
height: percent(100),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
Crosshair,
Node {
width: px(64),
height: px(64),
..default()
},
ImageNode {
image: assets.image.clone(),
..default()
},
// Hidden by default, because cursor shouldn't be grabbed at startup either.
Visibility::Hidden,
)],
));
}
fn update_crosshair_visibility(
cursor: Single<&CursorOptions, Changed<CursorOptions>>,
crosshair: Single<&mut Visibility, With<Crosshair>>,
) {
let is_grabbed = cursor.grab_mode != CursorGrabMode::None;
let mut crosshair_visibility = crosshair.into_inner();
*crosshair_visibility = (!is_grabbed || cursor.visible)
.then_some(Visibility::Hidden)
.unwrap_or_default();
}

@ -0,0 +1,8 @@
use bevy::prelude::*;
mod crosshair;
mod loading_screen;
pub fn plugin(app: &mut App) {
app.add_plugins((crosshair::plugin, loading_screen::plugin));
}

@ -3,13 +3,13 @@ pub mod block;
pub mod network; pub mod network;
pub mod player; pub mod player;
// This is mostly just re-exporting everything for now, but in the future,
// we might want to select exactly what's exposed by the `prelude` module.
pub mod prelude { pub mod prelude {
pub use super::network; pub use crate::network;
pub use super::block::*; pub use crate::block::*;
pub use super::player::*; pub use crate::network::protocol::*;
pub use crate::player::*;
pub use super::asset_loading::LoadResource; // Allows use of the `App::load_resource` extension trait function.
pub use crate::asset_loading::LoadResource;
} }

@ -2,23 +2,13 @@ use bevy::prelude::*;
use lightyear::prelude::client::*; use lightyear::prelude::client::*;
use lightyear::prelude::*; use lightyear::prelude::*;
pub use super::client_webtransport::ConnectWebTransportCommand; pub(super) fn plugin(app: &mut App) {
app.add_plugins(ClientPlugins::default());
pub struct ClientPlugin; app.add_observer(on_connected);
app.add_observer(on_disconnected);
impl Plugin for ClientPlugin { app.add_observer(autoconnect_host_client);
fn build(&self, app: &mut App) {
app.add_plugins(ClientPlugins::default());
if !app.is_plugin_added::<super::ProtocolPlugin>() {
app.add_plugins(super::ProtocolPlugin);
}
app.add_observer(on_connected);
app.add_observer(on_disconnected);
app.add_observer(autoconnect_host_client);
}
} }
/// Automatically creates a "host client" to connect to the local server when it is started. /// Automatically creates a "host client" to connect to the local server when it is started.

@ -1,12 +1,19 @@
mod client; use bevy::prelude::*;
mod protocol;
mod server; pub(crate) mod protocol;
mod client;
mod client_webtransport; mod client_webtransport;
mod server;
mod server_webtransport; mod server_webtransport;
pub use client::*; pub use crate::network::client_webtransport::ConnectWebTransportCommand;
pub use protocol::*; pub use crate::network::server::StartLocalServerCommand;
pub use server::*; #[cfg(not(target_family = "wasm"))]
pub use crate::network::server_webtransport::StartWebTransportServerCommand;
pub const DEFAULT_PORT: u16 = 13580; pub const DEFAULT_PORT: u16 = 13580;
pub fn plugin(app: &mut App) {
app.add_plugins((protocol::plugin, client::plugin, server::plugin));
}

@ -8,6 +8,21 @@ use serde::{Deserialize, Serialize};
use crate::block::Block; use crate::block::Block;
use crate::player::Player; use crate::player::Player;
pub(super) fn plugin(app: &mut App) {
app.add_plugins(InputPlugin::<Inputs>::default());
// marker components
app.register_component::<Player>();
app.register_component::<Block>();
app.register_component::<Transform>()
.add_prediction()
.add_interpolation_with(interpolate_transform);
// unified update systems
app.add_systems(FixedUpdate, crate::player::movement);
}
#[derive(Default, Deserialize, Serialize, Reflect, Clone, PartialEq, Debug)] #[derive(Default, Deserialize, Serialize, Reflect, Clone, PartialEq, Debug)]
pub struct Inputs { pub struct Inputs {
pub movement: Vec2, pub movement: Vec2,
@ -19,25 +34,6 @@ impl MapEntities for Inputs {
fn map_entities<E: EntityMapper>(&mut self, _entity_mapper: &mut E) {} fn map_entities<E: EntityMapper>(&mut self, _entity_mapper: &mut E) {}
} }
pub struct ProtocolPlugin;
impl Plugin for ProtocolPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(InputPlugin::<Inputs>::default());
// marker components
app.register_component::<Player>();
app.register_component::<Block>();
app.register_component::<Transform>()
.add_prediction()
.add_interpolation_with(interpolate_transform);
// unified update systems
app.add_systems(FixedUpdate, crate::player::movement);
}
}
fn interpolate_transform(start: Transform, other: Transform, t: f32) -> Transform { fn interpolate_transform(start: Transform, other: Transform, t: f32) -> Transform {
Transform { Transform {
translation: start.translation.lerp(other.translation, t), translation: start.translation.lerp(other.translation, t),

@ -4,29 +4,18 @@ use lightyear::prelude::*;
use crate::player::Player; use crate::player::Player;
#[cfg(not(target_family = "wasm"))] pub(super) fn plugin(app: &mut App) {
pub use super::server_webtransport::StartWebTransportServerCommand; app.add_plugins(ServerPlugins::default());
pub struct ServerPlugin; // TODO: See what happens when we try to start a server, but it
// can't, for example due to the port already being in use.
app.add_observer(on_server_started);
app.add_observer(on_server_stopped);
app.add_observer(handle_client_connected);
app.add_observer(handle_client_disconnected);
impl Plugin for ServerPlugin { #[cfg(not(target_family = "wasm"))]
fn build(&self, app: &mut App) { app.add_observer(super::server_webtransport::print_certificate_digest);
app.add_plugins(ServerPlugins::default());
if !app.is_plugin_added::<super::ProtocolPlugin>() {
app.add_plugins(super::ProtocolPlugin);
}
// TODO: See what happens when we try to start a server, but it
// can't, for example due to the port already being in use.
app.add_observer(on_server_started);
app.add_observer(on_server_stopped);
app.add_observer(handle_client_connected);
app.add_observer(handle_client_disconnected);
#[cfg(not(target_family = "wasm"))]
app.add_observer(super::server_webtransport::print_certificate_digest);
}
} }
/// Starts a local-only server that only a host-client can connect to. /// Starts a local-only server that only a host-client can connect to.

@ -4,7 +4,7 @@ use lightyear::prelude::*;
use lightyear::prelude::input::native::ActionState; use lightyear::prelude::input::native::ActionState;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::network::Inputs; use crate::network::protocol::Inputs;
const MOVEMENT_SPEED: f32 = 5.0; const MOVEMENT_SPEED: f32 = 5.0;

Loading…
Cancel
Save