aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-14 06:43:53 +0100
committerLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-15 02:50:57 +0100
commit69584a302d132dc2bcc3837437e7347a3e0a5114 (patch)
treefae29ea706451e91a0dee99200fa661b78de3517 /src
parent9671dd79163e654f61896ec4f61142a71c2f82c0 (diff)
feat: players can teleport
Diffstat (limited to 'src')
-rw-r--r--src/client.rs82
-rw-r--r--src/client/network.rs35
-rw-r--r--src/main.rs26
-rw-r--r--src/protocol.rs47
-rw-r--r--src/server.rs83
-rw-r--r--src/server/network.rs31
-rw-r--r--src/shared.rs40
7 files changed, 343 insertions, 1 deletions
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<Assets<ColorMaterial>>,
+ mut meshes: ResMut<Assets<Mesh>>,
+ players: Query<(Entity, &PlayerPosition, &PlayerColor), Added<PlayerId>>,
+) {
+ 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<PlayerPosition>>) {
+ 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<ButtonInput<MouseButton>>,
+ 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::<Channel1>(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<ClientId, Entity>);
+
+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<server::ConnectEvent>,
+ mut disconnects: EventReader<server::DisconnectEvent>,
+ mut entity_map: ResMut<EntityMap>,
+) {
+ 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<EntityMap>,
+ mut input_reader: EventReader<server::InputEvent<Inputs>>,
+ 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(),
+ }
+ }
+}