diff options
Diffstat (limited to 'examples/breakout/server.rs')
-rw-r--r-- | examples/breakout/server.rs | 212 |
1 files changed, 173 insertions, 39 deletions
diff --git a/examples/breakout/server.rs b/examples/breakout/server.rs index 150a1da..556fc4b 100644 --- a/examples/breakout/server.rs +++ b/examples/breakout/server.rs @@ -1,87 +1,221 @@ use std::collections::HashMap; -use bevy::prelude::{info, warn, EventReader, ResMut, State}; -use bevy_quinnet::{ - server::{ - CertificateRetrievalMode, ConnectionEvent, ConnectionLostEvent, Server, - ServerConfigurationData, +use bevy::{ + prelude::{ + default, Commands, Component, Entity, EventReader, Query, ResMut, Transform, Vec2, Vec3, }, + transform::TransformBundle, +}; +use bevy_quinnet::{ + server::{CertificateRetrievalMode, ConnectionEvent, Server, ServerConfigurationData}, ClientId, }; use crate::{ - protocol::{ClientMessage, ServerMessage}, - GameState, + protocol::{ClientMessage, PaddleInput, ServerMessage}, + Ball, Collider, Velocity, BALL_SIZE, BALL_SPEED, BOTTOM_WALL, GAP_BETWEEN_PADDLE_AND_FLOOR, + LEFT_WALL, PADDLE_PADDING, PADDLE_SIZE, PADDLE_SPEED, RIGHT_WALL, TIME_STEP, TOP_WALL, + WALL_THICKNESS, }; +const GAP_BETWEEN_PADDLE_AND_BALL: f32 = 35.; + +// We set the z-value of the ball to 1 so it renders on top in the case of overlapping sprites. +const BALLS_STARTING_POSITION: [Vec3; 2] = [ + Vec3::new( + 0.0, + BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR + GAP_BETWEEN_PADDLE_AND_BALL, + 1.0, + ), + Vec3::new( + 0.0, + TOP_WALL - GAP_BETWEEN_PADDLE_AND_FLOOR - GAP_BETWEEN_PADDLE_AND_BALL, + 1.0, + ), +]; +const INITIAL_BALLS_DIRECTION: [Vec2; 2] = [Vec2::new(0.5, -0.5), Vec2::new(-0.5, 0.5)]; + +const PADDLES_STARTING_POSITION: [Vec3; 2] = [ + Vec3::new(0.0, BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR, 0.0), + Vec3::new(0.0, TOP_WALL - GAP_BETWEEN_PADDLE_AND_FLOOR, 0.0), +]; + #[derive(Debug, Clone, Default)] pub(crate) struct Player { + // paddle: Option<Entity>, + input: PaddleInput, score: u64, } #[derive(Debug, Clone, Default)] pub(crate) struct Players { - scores: HashMap<ClientId, Player>, + map: HashMap<ClientId, Player>, +} + +#[derive(Component)] +pub(crate) struct Paddle { + player_id: ClientId, +} + +pub(crate) fn start_listening(mut server: ResMut<Server>) { + server + .start( + ServerConfigurationData::new("127.0.0.1".to_string(), 6000, "0.0.0.0".to_string()), + CertificateRetrievalMode::GenerateSelfSigned, + ) + .unwrap(); } -pub(crate) fn handle_client_messages(mut server: ResMut<Server>, mut users: ResMut<Players>) { +pub(crate) fn handle_client_messages(mut server: ResMut<Server>, mut players: ResMut<Players>) { while let Ok(Some((message, client_id))) = server.receive_message::<ClientMessage>() { match message { - // ClientMessage::Disconnect {} => { - // // We tell the server to disconnect this user - // server.disconnect_client(client_id); - // handle_disconnect(&mut server, &mut users, client_id); - // } - ClientMessage::PaddleInput {} => todo!(), + ClientMessage::PaddleInput { input } => { + if let Some(player) = players.map.get_mut(&client_id) { + player.input = input; + } + } } } } pub(crate) fn handle_server_events( + mut commands: Commands, mut connection_events: EventReader<ConnectionEvent>, - mut connection_lost_events: EventReader<ConnectionLostEvent>, mut server: ResMut<Server>, mut players: ResMut<Players>, - mut game_state: ResMut<State<GameState>>, ) { // The server signals us about new connections for client in connection_events.iter() { // Refuse connection once we already have two players - if players.scores.len() >= 2 { + if players.map.len() >= 2 { server.disconnect_client(client.id) } else { - players.scores.insert(client.id, Player { score: 0 }); - if players.scores.len() == 2 { + players.map.insert( + client.id, + Player { + score: 0, + input: PaddleInput::None, + // paddle: None, + }, + ); + if players.map.len() == 2 { + start_game(&mut commands, &mut server, &players); + } + } + } +} + +pub(crate) fn update_paddles( + mut server: ResMut<Server>, + players: ResMut<Players>, + // mut query: Query<&mut Transform, With<Paddle>>, + mut paddles: Query<(&mut Transform, &Paddle, Entity)>, +) { + for (mut paddle_transform, paddle, paddle_entity) in paddles.iter_mut() { + if let Some(player) = players.map.get(&paddle.player_id) { + if player.input != PaddleInput::None { + let mut direction = 0.0; + match player.input { + PaddleInput::Left => direction -= 1.0, + PaddleInput::Right => direction = 1.0, + _ => {} + } + // Calculate the new horizontal paddle position based on player input + let new_paddle_position = + paddle_transform.translation.x + direction * PADDLE_SPEED * TIME_STEP; + + // Update the paddle position, + // making sure it doesn't cause the paddle to leave the arena + let left_bound = + LEFT_WALL + WALL_THICKNESS / 2.0 + PADDLE_SIZE.x / 2.0 + PADDLE_PADDING; + let right_bound = + RIGHT_WALL - WALL_THICKNESS / 2.0 - PADDLE_SIZE.x / 2.0 - PADDLE_PADDING; + + paddle_transform.translation.x = new_paddle_position.clamp(left_bound, right_bound); + server .send_group_message( - players.scores.keys().into_iter(), - ServerMessage::GameStart {}, + players.map.keys().into_iter(), + ServerMessage::PaddlePosition { + entity: paddle_entity, + position: paddle_transform.translation, + }, ) .unwrap(); } } } - // The server signals us about users that lost connection - // for client in connection_lost_events.iter() { - // handle_disconnect(&mut server, &mut players, client.id); - // } } -// /// Shared disconnection behaviour, whether the client lost connection or asked to disconnect -// pub(crate) fn handle_disconnect( -// server: &mut ResMut<Server>, -// players: &mut ResMut<Players>, -// client_id: ClientId, -// ) { -// // Remove this user +fn start_game(commands: &mut Commands, server: &mut ResMut<Server>, players: &ResMut<Players>) { + // Paddles + for (index, (client_id, _)) in players.map.iter().enumerate() { + let paddle = spawn_paddle(commands, *client_id, &PADDLES_STARTING_POSITION[index]); + server + .send_group_message( + players.map.keys().into_iter(), + ServerMessage::SpawnPaddle { + client_id: *client_id, + entity: paddle, + position: PADDLES_STARTING_POSITION[index], + }, + ) + .unwrap(); + } -// } + // Balls + for (position, direction) in BALLS_STARTING_POSITION + .iter() + .zip(INITIAL_BALLS_DIRECTION.iter()) + { + let ball = spawn_ball(commands, position, direction); + server + .send_group_message( + players.map.keys().into_iter(), + ServerMessage::SpawnBall { + entity: ball, + position: *position, + direction: *direction, + }, + ) + .unwrap(); + } -pub(crate) fn start_listening(mut server: ResMut<Server>) { server - .start( - ServerConfigurationData::new("127.0.0.1".to_string(), 6000, "0.0.0.0".to_string()), - CertificateRetrievalMode::GenerateSelfSigned, - ) + .send_group_message(players.map.keys().into_iter(), ServerMessage::StartGame {}) .unwrap(); } + +fn spawn_paddle(commands: &mut Commands, client_id: ClientId, pos: &Vec3) -> Entity { + commands + .spawn() + .insert(Paddle { + player_id: client_id, + }) + .insert_bundle(TransformBundle { + local: Transform { + translation: *pos, + scale: PADDLE_SIZE, + ..default() + }, + ..default() + }) + .insert(Collider) + .id() +} + +fn spawn_ball(commands: &mut Commands, pos: &Vec3, direction: &Vec2) -> Entity { + commands + .spawn() + .insert(Ball) + .insert_bundle(TransformBundle { + local: Transform { + scale: BALL_SIZE, + translation: *pos, + ..default() + }, + ..default() + }) + .insert(Velocity(direction.normalize() * BALL_SPEED)) + .id() +} |