aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorHenauxg <19689618+Henauxg@users.noreply.github.com>2022-11-03 16:57:55 +0100
committerHenauxg <19689618+Henauxg@users.noreply.github.com>2022-11-03 16:57:55 +0100
commita6f63d755caf51cd9866ba5aaf0c9fd3c0d56bde (patch)
tree8c9a9498ea877d6df02893caddf5717e57152c0b /examples
parent4103125aa0f3ad3368ccf97fbdae5eedbe6e7233 (diff)
[breakout:example] Networked bricks spawn
Diffstat (limited to 'examples')
-rw-r--r--examples/breakout/breakout.rs5
-rw-r--r--examples/breakout/client.rs125
-rw-r--r--examples/breakout/protocol.rs7
-rw-r--r--examples/breakout/server.rs97
4 files changed, 152 insertions, 82 deletions
diff --git a/examples/breakout/breakout.rs b/examples/breakout/breakout.rs
index 1183dbb..96527c2 100644
--- a/examples/breakout/breakout.rs
+++ b/examples/breakout/breakout.rs
@@ -12,6 +12,8 @@ mod client;
mod protocol;
mod server;
+const SERVER_PORT: u16 = 6000;
+
// Defines the amount of time that should elapse between each physics step.
const TIME_STEP: f32 = 1.0 / 60.0;
@@ -148,9 +150,6 @@ struct Collider;
struct CollisionEvent;
#[derive(Component)]
-struct Brick;
-
-#[derive(Component)]
struct Score;
struct CollisionSound(Handle<AudioSource>);
diff --git a/examples/breakout/client.rs b/examples/breakout/client.rs
index a7b549b..6598ec4 100644
--- a/examples/breakout/client.rs
+++ b/examples/breakout/client.rs
@@ -20,10 +20,8 @@ use bevy_quinnet::{
use crate::{
protocol::{ClientMessage, PaddleInput, ServerMessage},
- Brick, Collider, CollisionEvent, CollisionSound, GameState, Score, Scoreboard, Velocity,
- WallLocation, BALL_SIZE, BALL_SPEED, BOTTOM_WALL, BRICK_SIZE, GAP_BETWEEN_BRICKS,
- GAP_BETWEEN_BRICKS_AND_CEILING, GAP_BETWEEN_BRICKS_AND_SIDES, GAP_BETWEEN_PADDLE_AND_BRICKS,
- GAP_BETWEEN_PADDLE_AND_FLOOR, LEFT_WALL, PADDLE_SIZE, RIGHT_WALL, TIME_STEP, TOP_WALL,
+ Collider, CollisionEvent, CollisionSound, GameState, Score, Scoreboard, Velocity, WallLocation,
+ BALL_SIZE, BALL_SPEED, BRICK_SIZE, GAP_BETWEEN_BRICKS, PADDLE_SIZE, SERVER_PORT, TIME_STEP,
};
const SCOREBOARD_FONT_SIZE: f32 = 40.0;
@@ -62,6 +60,10 @@ pub(crate) struct Paddle;
#[derive(Component)]
pub(crate) struct Ball;
+pub type BrickId = u64;
+#[derive(Component)]
+pub(crate) struct Brick(BrickId);
+
/// The buttons in the main menu.
#[derive(Clone, Copy, Component)]
pub(crate) enum MenuItem {
@@ -82,7 +84,12 @@ struct WallBundle {
pub(crate) fn start_connection(client: ResMut<Client>) {
client
.connect(
- ClientConfigurationData::new("127.0.0.1".to_string(), 6000, "0.0.0.0".to_string(), 0),
+ ClientConfigurationData::new(
+ "127.0.0.1".to_string(),
+ SERVER_PORT,
+ "0.0.0.0".to_string(),
+ 0,
+ ),
CertificateVerificationMode::SkipVerification,
)
.unwrap();
@@ -128,6 +135,37 @@ fn spawn_ball(commands: &mut Commands, pos: &Vec3, direction: &Vec2) -> Entity {
.id()
}
+pub(crate) fn spawn_bricks(commands: &mut Commands, offset: Vec2, rows: usize, columns: usize) {
+ let mut brick_id = 0;
+ for row in 0..rows {
+ for column in 0..columns {
+ let brick_position = Vec2::new(
+ offset.x + column as f32 * (BRICK_SIZE.x + GAP_BETWEEN_BRICKS),
+ offset.y + row as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS),
+ );
+
+ // brick
+ commands
+ .spawn()
+ .insert(Brick(brick_id))
+ .insert_bundle(SpriteBundle {
+ sprite: Sprite {
+ color: BRICK_COLOR,
+ ..default()
+ },
+ transform: Transform {
+ translation: brick_position.extend(0.0),
+ scale: Vec3::new(BRICK_SIZE.x, BRICK_SIZE.y, 1.0),
+ ..default()
+ },
+ ..default()
+ })
+ .insert(Collider);
+ brick_id += 1;
+ }
+ }
+}
+
pub(crate) fn handle_server_messages(
mut commands: Commands,
mut client: ResMut<Client>,
@@ -136,6 +174,10 @@ pub(crate) fn handle_server_messages(
mut game_state: ResMut<State<GameState>>,
mut paddles: Query<&mut Transform, With<Paddle>>,
mut balls: Query<(&mut Transform, &mut Velocity), (With<Ball>, Without<Paddle>)>,
+ mut bricks: Query<
+ (&mut Transform, &mut Velocity),
+ (With<Brick>, Without<Paddle>, Without<Ball>),
+ >,
mut collision_events: EventWriter<CollisionEvent>,
) {
while let Ok(Some(message)) = client.receive_message::<ServerMessage>() {
@@ -159,6 +201,11 @@ pub(crate) fn handle_server_messages(
let ball = spawn_ball(&mut commands, &position, &direction);
entity_mapping.map.insert(entity, ball);
}
+ ServerMessage::SpawnBricks {
+ offset,
+ rows,
+ columns,
+ } => spawn_bricks(&mut commands, offset, rows, columns),
ServerMessage::StartGame {} => game_state.set(GameState::Running).unwrap(),
ServerMessage::BrickDestroyed { client_id } => todo!(),
ServerMessage::BallCollided {
@@ -311,7 +358,6 @@ pub(crate) fn teardown_main_menu(mut commands: Commands, query: Query<Entity, Wi
}
}
-// Add the game's entities to our world
pub(crate) fn setup_breakout(mut commands: Commands, asset_server: Res<AssetServer>) {
// Sound
let ball_collision_sound = asset_server.load(COLLISION_SOUND_EFFECT);
@@ -353,73 +399,6 @@ pub(crate) fn setup_breakout(mut commands: Commands, asset_server: Res<AssetServ
commands.spawn_bundle(WallBundle::new(WallLocation::Right));
commands.spawn_bundle(WallBundle::new(WallLocation::Bottom));
commands.spawn_bundle(WallBundle::new(WallLocation::Top));
-
- // Bricks
- // Negative scales result in flipped sprites / meshes,
- // which is definitely not what we want here
- assert!(BRICK_SIZE.x > 0.0);
- assert!(BRICK_SIZE.y > 0.0);
-
- let total_width_of_bricks = (RIGHT_WALL - LEFT_WALL) - 2. * GAP_BETWEEN_BRICKS_AND_SIDES;
- let bottom_edge_of_bricks =
- BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR + GAP_BETWEEN_PADDLE_AND_BRICKS;
- let available_height_for_bricks = TOP_WALL
- - bottom_edge_of_bricks
- - (GAP_BETWEEN_PADDLE_AND_FLOOR + GAP_BETWEEN_PADDLE_AND_BRICKS);
-
- assert!(total_width_of_bricks > 0.0);
- assert!(available_height_for_bricks > 0.0);
-
- // Given the space available, compute how many rows and columns of bricks we can fit
- let n_columns = (total_width_of_bricks / (BRICK_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize;
- let n_rows =
- (available_height_for_bricks / (BRICK_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize;
- let height_occupied_by_bricks =
- n_rows as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS) - GAP_BETWEEN_BRICKS;
- let n_vertical_gaps = n_columns - 1;
-
- // Because we need to round the number of columns,
- // the space on the top and sides of the bricks only captures a lower bound, not an exact value
- let center_of_bricks = (LEFT_WALL + RIGHT_WALL) / 2.0;
- let left_edge_of_bricks = center_of_bricks
- // Space taken up by the bricks
- - (n_columns as f32 / 2.0 * BRICK_SIZE.x)
- // Space taken up by the gaps
- - n_vertical_gaps as f32 / 2.0 * GAP_BETWEEN_BRICKS;
-
- // In Bevy, the `translation` of an entity describes the center point,
- // not its bottom-left corner
- let offset_x = left_edge_of_bricks + BRICK_SIZE.x / 2.;
- let offset_y = bottom_edge_of_bricks
- + BRICK_SIZE.y / 2.
- + (available_height_for_bricks - height_occupied_by_bricks) / 2.; // Offset so that both players are at an equal distance of the bricks
-
- for row in 0..n_rows {
- for column in 0..n_columns {
- let brick_position = Vec2::new(
- offset_x + column as f32 * (BRICK_SIZE.x + GAP_BETWEEN_BRICKS),
- offset_y + row as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS),
- );
-
- // brick
- commands
- .spawn()
- .insert(Brick)
- .insert_bundle(SpriteBundle {
- sprite: Sprite {
- color: BRICK_COLOR,
- ..default()
- },
- transform: Transform {
- translation: brick_position.extend(0.0),
- scale: Vec3::new(BRICK_SIZE.x, BRICK_SIZE.y, 1.0),
- ..default()
- },
- ..default()
- })
- .insert(Collider);
- }
- }
}
pub(crate) fn apply_velocity(mut query: Query<(&mut Transform, &Velocity), With<Ball>>) {
diff --git a/examples/breakout/protocol.rs b/examples/breakout/protocol.rs
index a42e0e4..4e2b309 100644
--- a/examples/breakout/protocol.rs
+++ b/examples/breakout/protocol.rs
@@ -32,7 +32,12 @@ pub(crate) enum ServerMessage {
position: Vec3,
direction: Vec2,
},
- StartGame {},
+ SpawnBricks {
+ offset: Vec2,
+ rows: usize,
+ columns: usize,
+ },
+ StartGame,
BrickDestroyed {
client_id: ClientId,
},
diff --git a/examples/breakout/server.rs b/examples/breakout/server.rs
index 663cd00..fb430f4 100644
--- a/examples/breakout/server.rs
+++ b/examples/breakout/server.rs
@@ -15,9 +15,10 @@ use bevy_quinnet::{
use crate::{
protocol::{ClientMessage, PaddleInput, ServerMessage},
- Brick, Collider, Scoreboard, Velocity, BALL_SIZE, BALL_SPEED, BOTTOM_WALL,
+ Collider, Scoreboard, Velocity, BALL_SIZE, BALL_SPEED, BOTTOM_WALL, BRICK_SIZE,
+ GAP_BETWEEN_BRICKS, GAP_BETWEEN_BRICKS_AND_SIDES, GAP_BETWEEN_PADDLE_AND_BRICKS,
GAP_BETWEEN_PADDLE_AND_FLOOR, LEFT_WALL, PADDLE_PADDING, PADDLE_SIZE, PADDLE_SPEED, RIGHT_WALL,
- TIME_STEP, TOP_WALL, WALL_THICKNESS,
+ SERVER_PORT, TIME_STEP, TOP_WALL, WALL_THICKNESS,
};
const GAP_BETWEEN_PADDLE_AND_BALL: f32 = 35.;
@@ -58,13 +59,21 @@ pub(crate) struct Paddle {
player_id: ClientId,
}
+pub type BrickId = u64;
+#[derive(Component)]
+pub(crate) struct Brick(BrickId);
+
#[derive(Component)]
pub(crate) struct Ball;
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()),
+ ServerConfigurationData::new(
+ "127.0.0.1".to_string(),
+ SERVER_PORT,
+ "0.0.0.0".to_string(),
+ ),
CertificateRetrievalMode::GenerateSelfSigned,
)
.unwrap();
@@ -219,7 +228,7 @@ pub(crate) fn apply_velocity(mut query: Query<(&mut Transform, &Velocity), With<
}
fn start_game(commands: &mut Commands, server: &mut ResMut<Server>, players: &ResMut<Players>) {
- // Paddles
+ // Spawn paddles
for (index, (client_id, _)) in players.map.iter().enumerate() {
let paddle = spawn_paddle(commands, *client_id, &PADDLES_STARTING_POSITION[index]);
server
@@ -234,7 +243,7 @@ fn start_game(commands: &mut Commands, server: &mut ResMut<Server>, players: &Re
.unwrap();
}
- // Balls
+ // Spawn balls
for (position, direction) in BALLS_STARTING_POSITION
.iter()
.zip(INITIAL_BALLS_DIRECTION.iter())
@@ -252,6 +261,84 @@ fn start_game(commands: &mut Commands, server: &mut ResMut<Server>, players: &Re
.unwrap();
}
+ // Spawn bricks
+ // Negative scales result in flipped sprites / meshes,
+ // which is definitely not what we want here
+ assert!(BRICK_SIZE.x > 0.0);
+ assert!(BRICK_SIZE.y > 0.0);
+
+ let total_width_of_bricks = (RIGHT_WALL - LEFT_WALL) - 2. * GAP_BETWEEN_BRICKS_AND_SIDES;
+ let bottom_edge_of_bricks =
+ BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR + GAP_BETWEEN_PADDLE_AND_BRICKS;
+ let available_height_for_bricks = TOP_WALL
+ - bottom_edge_of_bricks
+ - (GAP_BETWEEN_PADDLE_AND_FLOOR + GAP_BETWEEN_PADDLE_AND_BRICKS);
+
+ assert!(total_width_of_bricks > 0.0);
+ assert!(available_height_for_bricks > 0.0);
+
+ // Given the space available, compute how many rows and columns of bricks we can fit
+ let n_columns = (total_width_of_bricks / (BRICK_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize;
+ let n_rows =
+ (available_height_for_bricks / (BRICK_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize;
+ let height_occupied_by_bricks =
+ n_rows as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS) - GAP_BETWEEN_BRICKS;
+ let n_vertical_gaps = n_columns - 1;
+
+ // Because we need to round the number of columns,
+ // the space on the top and sides of the bricks only captures a lower bound, not an exact value
+ let center_of_bricks = (LEFT_WALL + RIGHT_WALL) / 2.0;
+ let left_edge_of_bricks = center_of_bricks
+ // Space taken up by the bricks
+ - (n_columns as f32 / 2.0 * BRICK_SIZE.x)
+ // Space taken up by the gaps
+ - n_vertical_gaps as f32 / 2.0 * GAP_BETWEEN_BRICKS;
+
+ // In Bevy, the `translation` of an entity describes the center point,
+ // not its bottom-left corner
+ let offset_x = left_edge_of_bricks + BRICK_SIZE.x / 2.;
+ let offset_y = bottom_edge_of_bricks
+ + BRICK_SIZE.y / 2.
+ + (available_height_for_bricks - height_occupied_by_bricks) / 2.; // Offset so that both players are at an equal distance of the bricks
+
+ let mut brick_id = 0;
+ for row in 0..n_rows {
+ for column in 0..n_columns {
+ let brick_position = Vec2::new(
+ offset_x + column as f32 * (BRICK_SIZE.x + GAP_BETWEEN_BRICKS),
+ offset_y + row as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS),
+ );
+
+ // brick
+ commands
+ .spawn()
+ .insert(Brick(brick_id))
+ .insert_bundle(TransformBundle {
+ local: Transform {
+ translation: brick_position.extend(0.0),
+ scale: Vec3::new(BRICK_SIZE.x, BRICK_SIZE.y, 1.0),
+ ..default()
+ },
+ ..default()
+ })
+ .insert(Collider);
+ brick_id += 1;
+ }
+ }
+ server
+ .send_group_message(
+ players.map.keys().into_iter(),
+ ServerMessage::SpawnBricks {
+ offset: Vec2 {
+ x: offset_x,
+ y: offset_y,
+ },
+ rows: n_rows,
+ columns: n_columns,
+ },
+ )
+ .unwrap();
+
server
.send_group_message(players.map.keys().into_iter(), ServerMessage::StartGame {})
.unwrap();