From d51b54a6580431ca2fa1e154b8162aa53bc3d430 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 21:17:50 +0000 Subject: [PATCH] Break out Breakout components into a more sensible organization (#4261) # Objective - The components in the Breakout game are defined in a strange fashion. - Components should decouple behavior wherever possible. - Systems should be as general as possible, to make extending behavior easier. - Marker components are idiomatic and useful, but marker components and query filters were not used. - The existing design makes it challenging for beginners to extend the example into a high-quality game. ## Solution - Refactor component definitions in the Breakout example to reflect principles above. ## Context A small portion of the changes made in #2094. Interacts with changes in #4255; merge conflicts will have to be resolved. --- examples/game/breakout.rs | 102 ++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/examples/game/breakout.rs b/examples/game/breakout.rs index bd420df48b..b7afd4db03 100644 --- a/examples/game/breakout.rs +++ b/examples/game/breakout.rs @@ -54,7 +54,7 @@ fn main() { .with_run_criteria(FixedTimestep::step(TIME_STEP as f64)) .with_system(paddle_movement_system) .with_system(ball_collision_system) - .with_system(ball_movement_system), + .with_system(apply_velocity_system), ) .add_system(scoreboard_system) .add_system(bevy::input::system::exit_on_esc_system) @@ -62,22 +62,21 @@ fn main() { } #[derive(Component)] -struct Paddle { - speed: f32, -} +struct Paddle; #[derive(Component)] -struct Ball { - velocity: Vec3, -} +struct Ball; #[derive(Component)] -enum Collider { - Solid, - Scorable, - Paddle, -} +struct Velocity(Vec2); +#[derive(Component)] +struct Collider; + +#[derive(Component)] +struct Brick; + +// This resource tracks the game's score struct Scoreboard { score: usize, } @@ -90,7 +89,9 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn_bundle(UiCameraBundle::default()); // paddle commands - .spawn_bundle(SpriteBundle { + .spawn() + .insert(Paddle) + .insert_bundle(SpriteBundle { transform: Transform { translation: Vec3::new(0.0, PADDLE_HEIGHT, 0.0), scale: PADDLE_SIZE, @@ -102,13 +103,14 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Paddle { - speed: PADDLE_SPEED, - }) - .insert(Collider::Paddle); + .insert(Collider); // ball + let ball_velocity = INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED; + commands - .spawn_bundle(SpriteBundle { + .spawn() + .insert(Ball) + .insert_bundle(SpriteBundle { transform: Transform { scale: BALL_SIZE, translation: BALL_STARTING_POSITION, @@ -120,10 +122,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Ball { - // We can create a velocity by multiplying our speed by a normalized direction. - velocity: BALL_SPEED * INITIAL_BALL_DIRECTION.extend(0.0).normalize(), - }); + .insert(Velocity(ball_velocity)); // scoreboard commands.spawn_bundle(TextBundle { text: Text { @@ -173,7 +172,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Collider::Solid); + .insert(Collider); // right commands .spawn_bundle(SpriteBundle { @@ -188,7 +187,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Collider::Solid); + .insert(Collider); // bottom commands .spawn_bundle(SpriteBundle { @@ -203,7 +202,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Collider::Solid); + .insert(Collider); // top commands .spawn_bundle(SpriteBundle { @@ -218,7 +217,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Collider::Solid); + .insert(Collider); // Add bricks let bricks_width = BRICK_COLUMNS as f32 * (BRICK_SIZE.x + BRICK_SPACING) - BRICK_SPACING; @@ -234,7 +233,9 @@ fn setup(mut commands: Commands, asset_server: Res) { ) + bricks_offset; // brick commands - .spawn_bundle(SpriteBundle { + .spawn() + .insert(Brick) + .insert_bundle(SpriteBundle { sprite: Sprite { color: BRICK_COLOR, ..default() @@ -246,16 +247,16 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }) - .insert(Collider::Scorable); + .insert(Collider); } } } fn paddle_movement_system( keyboard_input: Res>, - mut query: Query<(&Paddle, &mut Transform)>, + mut query: Query<&mut Transform, With>, ) { - let (paddle, mut transform) = query.single_mut(); + let mut transform = query.single_mut(); let mut direction = 0.0; if keyboard_input.pressed(KeyCode::Left) { direction -= 1.0; @@ -267,14 +268,16 @@ fn paddle_movement_system( let translation = &mut transform.translation; // move the paddle horizontally - translation.x += direction * paddle.speed * TIME_STEP; + translation.x += direction * PADDLE_SPEED * TIME_STEP; // bound the paddle within the walls translation.x = translation.x.min(PADDLE_BOUNDS).max(-PADDLE_BOUNDS); } -fn ball_movement_system(mut ball_query: Query<(&Ball, &mut Transform)>) { - let (ball, mut transform) = ball_query.single_mut(); - transform.translation += ball.velocity * TIME_STEP; +fn apply_velocity_system(mut query: Query<(&mut Transform, &Velocity)>) { + for (mut transform, velocity) in query.iter_mut() { + transform.translation.x += velocity.0.x * TIME_STEP; + transform.translation.y += velocity.0.y * TIME_STEP; + } } fn scoreboard_system(scoreboard: Res, mut query: Query<&mut Text>) { @@ -285,15 +288,14 @@ fn scoreboard_system(scoreboard: Res, mut query: Query<&mut Text>) { fn ball_collision_system( mut commands: Commands, mut scoreboard: ResMut, - mut ball_query: Query<(&mut Ball, &Transform)>, - collider_query: Query<(Entity, &Collider, &Transform)>, + mut ball_query: Query<(&mut Velocity, &Transform), With>, + collider_query: Query<(Entity, &Transform, Option<&Brick>), With>, ) { - let (mut ball, ball_transform) = ball_query.single_mut(); + let (mut ball_velocity, ball_transform) = ball_query.single_mut(); let ball_size = ball_transform.scale.truncate(); - let velocity = &mut ball.velocity; // check collision with walls - for (collider_entity, collider, transform) in collider_query.iter() { + for (collider_entity, transform, maybe_brick) in collider_query.iter() { let collision = collide( ball_transform.translation, ball_size, @@ -301,8 +303,8 @@ fn ball_collision_system( transform.scale.truncate(), ); if let Some(collision) = collision { - // scorable colliders should be despawned and increment the scoreboard on collision - if let Collider::Scorable = *collider { + // Bricks should be despawned and increment the scoreboard on collision + if maybe_brick.is_some() { scoreboard.score += 1; commands.entity(collider_entity).despawn(); } @@ -314,27 +316,21 @@ fn ball_collision_system( // only reflect if the ball's velocity is going in the opposite direction of the // collision match collision { - Collision::Left => reflect_x = velocity.x > 0.0, - Collision::Right => reflect_x = velocity.x < 0.0, - Collision::Top => reflect_y = velocity.y < 0.0, - Collision::Bottom => reflect_y = velocity.y > 0.0, + Collision::Left => reflect_x = ball_velocity.0.x > 0.0, + Collision::Right => reflect_x = ball_velocity.0.x < 0.0, + Collision::Top => reflect_y = ball_velocity.0.y < 0.0, + Collision::Bottom => reflect_y = ball_velocity.0.y > 0.0, Collision::Inside => { /* do nothing */ } } // reflect velocity on the x-axis if we hit something on the x-axis if reflect_x { - velocity.x = -velocity.x; + ball_velocity.0.x = -ball_velocity.0.x; } // reflect velocity on the y-axis if we hit something on the y-axis if reflect_y { - velocity.y = -velocity.y; - } - - // break if this collide is on a solid, otherwise continue check whether a solid is - // also in collision - if let Collider::Solid = *collider { - break; + ball_velocity.0.y = -ball_velocity.0.y; } } }