mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 20:53:53 +00:00
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.
This commit is contained in:
parent
9e450f2827
commit
d51b54a658
1 changed files with 49 additions and 53 deletions
|
@ -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<AssetServer>) {
|
|||
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<AssetServer>) {
|
|||
},
|
||||
..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<AssetServer>) {
|
|||
},
|
||||
..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<AssetServer>) {
|
|||
},
|
||||
..default()
|
||||
})
|
||||
.insert(Collider::Solid);
|
||||
.insert(Collider);
|
||||
// right
|
||||
commands
|
||||
.spawn_bundle(SpriteBundle {
|
||||
|
@ -188,7 +187,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
},
|
||||
..default()
|
||||
})
|
||||
.insert(Collider::Solid);
|
||||
.insert(Collider);
|
||||
// bottom
|
||||
commands
|
||||
.spawn_bundle(SpriteBundle {
|
||||
|
@ -203,7 +202,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
},
|
||||
..default()
|
||||
})
|
||||
.insert(Collider::Solid);
|
||||
.insert(Collider);
|
||||
// top
|
||||
commands
|
||||
.spawn_bundle(SpriteBundle {
|
||||
|
@ -218,7 +217,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
},
|
||||
..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<AssetServer>) {
|
|||
) + 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<AssetServer>) {
|
|||
},
|
||||
..default()
|
||||
})
|
||||
.insert(Collider::Scorable);
|
||||
.insert(Collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paddle_movement_system(
|
||||
keyboard_input: Res<Input<KeyCode>>,
|
||||
mut query: Query<(&Paddle, &mut Transform)>,
|
||||
mut query: Query<&mut Transform, With<Paddle>>,
|
||||
) {
|
||||
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<Scoreboard>, mut query: Query<&mut Text>) {
|
||||
|
@ -285,15 +288,14 @@ fn scoreboard_system(scoreboard: Res<Scoreboard>, mut query: Query<&mut Text>) {
|
|||
fn ball_collision_system(
|
||||
mut commands: Commands,
|
||||
mut scoreboard: ResMut<Scoreboard>,
|
||||
mut ball_query: Query<(&mut Ball, &Transform)>,
|
||||
collider_query: Query<(Entity, &Collider, &Transform)>,
|
||||
mut ball_query: Query<(&mut Velocity, &Transform), With<Ball>>,
|
||||
collider_query: Query<(Entity, &Transform, Option<&Brick>), With<Collider>>,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue