From 69584a302d132dc2bcc3837437e7347a3e0a5114 Mon Sep 17 00:00:00 2001 From: Alexander Foremny Date: Thu, 14 Mar 2024 06:43:53 +0100 Subject: feat: players can teleport --- src/client.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/client/network.rs | 35 ++++++++++++++++++++++ src/main.rs | 26 +++++++++++++++- src/protocol.rs | 47 +++++++++++++++++++++++++++++ src/server.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/server/network.rs | 31 +++++++++++++++++++ src/shared.rs | 40 +++++++++++++++++++++++++ 7 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 src/client.rs create mode 100644 src/client/network.rs create mode 100644 src/protocol.rs create mode 100644 src/server.rs create mode 100644 src/server/network.rs create mode 100644 src/shared.rs (limited to 'src') diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..b36bba9 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,82 @@ +use crate::client::network::*; +use crate::protocol::*; +use crate::shared::*; +use bevy::input::mouse::MouseButton; +use bevy::prelude::*; +use bevy::sprite::{MaterialMesh2dBundle, Mesh2dHandle}; +use lightyear::client::input::InputSystemSet; +use lightyear::prelude::*; + +mod network; + +pub fn main(transport: TransportConfig) { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(ClientPlugin { transport }) + .run(); +} + +struct ClientPlugin { + pub transport: TransportConfig, +} + +impl Plugin for ClientPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(NetworkPlugin { + transport: self.transport.clone(), + }) + .add_systems(Startup, setup) + .add_systems(Update, spawn_players) + .add_systems(Update, move_players) + .add_systems( + FixedPreUpdate, + buffer_input.in_set(InputSystemSet::BufferInputs), + ); + } +} + +fn setup(mut client: ClientMut, mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); + client.connect().unwrap(); +} + +fn spawn_players( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + players: Query<(Entity, &PlayerPosition, &PlayerColor), Added>, +) { + for (entity, position, color) in players.iter() { + commands.entity(entity).insert(MaterialMesh2dBundle { + mesh: Mesh2dHandle(meshes.add(Circle { radius: 10. })), + material: materials.add(color.0), + transform: Transform::from_xyz(position.0.x, position.0.y, 0.), + ..Default::default() + }); + } +} + +fn move_players(mut players: Query<(&mut Transform, &PlayerPosition), Changed>) { + for (mut transform, player_position) in players.iter_mut() { + transform.translation = Vec3::new(player_position.0.x, player_position.0.y, 0.); + } +} + +fn buffer_input( + cameras: Query<(&Camera, &GlobalTransform)>, + mouse_input: Res>, + mut client: ClientMut, + windows: Query<&Window>, +) { + let window = windows.single(); + let (camera, camera_transform) = cameras.single(); + if mouse_input.just_pressed(MouseButton::Left) { + if let Some(cursor_position) = window.cursor_position() { + if let Some(world_position) = + camera.viewport_to_world_2d(camera_transform, cursor_position) + { + client.add_input(Inputs::Teleport(world_position)); + } + } + } +} diff --git a/src/client/network.rs b/src/client/network.rs new file mode 100644 index 0000000..4d1a128 --- /dev/null +++ b/src/client/network.rs @@ -0,0 +1,35 @@ +use crate::protocol::*; +use crate::shared::*; +use bevy::prelude::*; +use lightyear::client::config::*; +use lightyear::client::plugin::ClientPlugin; +use lightyear::client::plugin::PluginConfig; +use lightyear::client::resource::Authentication; +use lightyear::prelude::client::NetConfig; +use lightyear::prelude::*; +use lightyear::transport::LOCAL_SOCKET; + +pub struct NetworkPlugin { + pub transport: TransportConfig, +} + +impl Plugin for NetworkPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(ClientPlugin::new(PluginConfig::new( + ClientConfig { + net: NetConfig::Netcode { + config: Default::default(), + auth: Authentication::Manual { + server_addr: LOCAL_SOCKET, + client_id: CLIENT_ID, + private_key: KEY, + protocol_id: PROTOCOL_ID, + }, + io: IoConfig::from_transport(self.transport.clone()), + }, + ..Default::default() + }, + protocol(), + ))); + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..cdd7c17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,27 @@ +use lightyear::transport::io::TransportConfig; +use shared::SERVER_PORT; +use std::net::Ipv4Addr; +use std::net::SocketAddr; +use std::thread; + +mod client; +mod protocol; +mod server; +mod shared; + fn main() { - println!("Hello, world!"); + let (from_server_send, from_server_recv) = crossbeam_channel::unbounded(); + let (to_server_send, to_server_recv) = crossbeam_channel::unbounded(); + + thread::spawn(|| { + let server_addr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), SERVER_PORT); + server::main(TransportConfig::Channels { + channels: [(server_addr, to_server_recv, from_server_send)].to_vec(), + }); + }); + + client::main(TransportConfig::LocalChannel { + recv: from_server_recv, + send: to_server_send, + }); } diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..a9e2086 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,47 @@ +use crate::shared::*; +use bevy::prelude::*; +use lightyear::prelude::*; +use serde::Deserialize; +use serde::Serialize; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum Inputs { + Teleport(Vec2), + None, +} +impl UserAction for Inputs {} + +#[derive(Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Message1(pub usize); + +#[message_protocol(protocol = "MyProtocol")] +pub enum Messages { + Message1(Message1), +} + +#[component_protocol(protocol = "MyProtocol")] +pub enum Components { + #[sync(once)] + PlayerId(PlayerId), + PlayerPosition(PlayerPosition), + PlayerColor(PlayerColor), +} + +#[derive(Channel)] +struct Channel1; + +protocolize! { + Self = MyProtocol, + Message = Messages, + Component = Components, + Input = Inputs, +} + +pub fn protocol() -> MyProtocol { + let mut protocol = MyProtocol::default(); + protocol.add_channel::(ChannelSettings { + mode: ChannelMode::OrderedReliable(ReliableSettings::default()), + ..Default::default() + }); + protocol +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..3a7dd40 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,83 @@ +use crate::protocol::*; +use crate::server::network::*; +use crate::shared::*; +use bevy::prelude::*; +use bevy::utils::HashMap; +use lightyear::prelude::*; +use rand::Rng; + +mod network; + +#[derive(Resource, Default)] +struct EntityMap(HashMap); + +pub fn main(transport: TransportConfig) { + App::new() + .add_plugins(MinimalPlugins) + .add_plugins(ServerPlugin { transport }) + .run(); +} + +struct ServerPlugin { + pub transport: TransportConfig, +} + +impl Plugin for ServerPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(EntityMap::default()) + .add_plugins(NetworkPlugin { + transport: self.transport.clone(), + }) + .add_systems(Update, connections) + .add_systems(FixedUpdate, player_input); + } +} + +fn connections( + mut commands: Commands, + mut connects: EventReader, + mut disconnects: EventReader, + mut entity_map: ResMut, +) { + let mut rng = rand::thread_rng(); + for connection in connects.read() { + let client_id = connection.context(); + info!("connected: {:?}", client_id); + let position = Vec2::new( + 50. * rng.gen_range(-2..=2) as f32, + 50. * rng.gen_range(-2..=2) as f32, + ); + let color = Color::hsl(360. * rng.gen_range(0..=15) as f32 / 16., 0.95, 0.7); + let entity = commands.spawn(PlayerBundle::new(*client_id, position, color)); + entity_map.0.insert(*client_id, entity.id()); + } + for connection in disconnects.read() { + let client_id = connection.context(); + info!("disconnected: {:?}", client_id); + if let Some(entity_id) = entity_map.0.remove(client_id) { + commands.entity(entity_id).despawn(); + } + } +} + +fn player_input( + entity_map: Res, + mut input_reader: EventReader>, + mut positions: Query<&mut PlayerPosition>, +) { + for input in input_reader.read() { + let client_id = input.context(); + if let Some(input) = input.input() { + if let Some(entity_id) = entity_map.0.get(client_id) { + match input { + Inputs::Teleport(new_position) => { + if let Ok(mut position) = positions.get_mut(*entity_id) { + position.0 = *new_position; + } + } + _ => {} + } + } + } + } +} diff --git a/src/server/network.rs b/src/server/network.rs new file mode 100644 index 0000000..0beead7 --- /dev/null +++ b/src/server/network.rs @@ -0,0 +1,31 @@ +use crate::protocol::*; +use crate::shared::*; +use bevy::prelude::*; +use lightyear::prelude::server::NetConfig; +use lightyear::prelude::*; +use lightyear::server::config::*; +use lightyear::server::plugin::PluginConfig; +use lightyear::server::plugin::ServerPlugin; + +pub struct NetworkPlugin { + pub transport: TransportConfig, +} + +impl Plugin for NetworkPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(ServerPlugin::new(PluginConfig::new( + ServerConfig { + net: [NetConfig::Netcode { + config: NetcodeConfig::default() + .with_protocol_id(PROTOCOL_ID) + .with_key(KEY), + io: IoConfig::from_transport(self.transport.clone()), + }] + .to_vec(), + ping: PingConfig::default(), + ..Default::default() + }, + protocol(), + ))); + } +} diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..b907b57 --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,40 @@ +use crate::protocol::Replicate; +use bevy::prelude::*; +use lightyear::prelude::*; +use serde::Deserialize; +use serde::Serialize; + +pub const CLIENT_ID: u64 = 0; +pub const KEY: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; +pub const PROTOCOL_ID: u64 = 0; +pub const SERVER_PORT: u16 = 16384; + +#[derive(Bundle)] +pub struct PlayerBundle { + id: PlayerId, + position: PlayerPosition, + color: PlayerColor, + replicate: Replicate, +} + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct PlayerId(pub ClientId); + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct PlayerPosition(pub Vec2); + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct PlayerColor(pub Color); + +impl PlayerBundle { + pub fn new(id: ClientId, position: Vec2, color: Color) -> Self { + PlayerBundle { + id: PlayerId(id), + position: PlayerPosition(position), + color: PlayerColor(color), + replicate: Replicate::default(), + } + } +} -- cgit v1.2.3