mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +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_run_criteria(FixedTimestep::step(TIME_STEP as f64))
|
||||||
.with_system(paddle_movement_system)
|
.with_system(paddle_movement_system)
|
||||||
.with_system(ball_collision_system)
|
.with_system(ball_collision_system)
|
||||||
.with_system(ball_movement_system),
|
.with_system(apply_velocity_system),
|
||||||
)
|
)
|
||||||
.add_system(scoreboard_system)
|
.add_system(scoreboard_system)
|
||||||
.add_system(bevy::input::system::exit_on_esc_system)
|
.add_system(bevy::input::system::exit_on_esc_system)
|
||||||
|
@ -62,22 +62,21 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Paddle {
|
struct Paddle;
|
||||||
speed: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Ball {
|
struct Ball;
|
||||||
velocity: Vec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
enum Collider {
|
struct Velocity(Vec2);
|
||||||
Solid,
|
|
||||||
Scorable,
|
|
||||||
Paddle,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Collider;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Brick;
|
||||||
|
|
||||||
|
// This resource tracks the game's score
|
||||||
struct Scoreboard {
|
struct Scoreboard {
|
||||||
score: usize,
|
score: usize,
|
||||||
}
|
}
|
||||||
|
@ -90,7 +89,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
commands.spawn_bundle(UiCameraBundle::default());
|
commands.spawn_bundle(UiCameraBundle::default());
|
||||||
// paddle
|
// paddle
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(SpriteBundle {
|
.spawn()
|
||||||
|
.insert(Paddle)
|
||||||
|
.insert_bundle(SpriteBundle {
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
translation: Vec3::new(0.0, PADDLE_HEIGHT, 0.0),
|
translation: Vec3::new(0.0, PADDLE_HEIGHT, 0.0),
|
||||||
scale: PADDLE_SIZE,
|
scale: PADDLE_SIZE,
|
||||||
|
@ -102,13 +103,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Paddle {
|
.insert(Collider);
|
||||||
speed: PADDLE_SPEED,
|
|
||||||
})
|
|
||||||
.insert(Collider::Paddle);
|
|
||||||
// ball
|
// ball
|
||||||
|
let ball_velocity = INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED;
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(SpriteBundle {
|
.spawn()
|
||||||
|
.insert(Ball)
|
||||||
|
.insert_bundle(SpriteBundle {
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
scale: BALL_SIZE,
|
scale: BALL_SIZE,
|
||||||
translation: BALL_STARTING_POSITION,
|
translation: BALL_STARTING_POSITION,
|
||||||
|
@ -120,10 +122,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Ball {
|
.insert(Velocity(ball_velocity));
|
||||||
// We can create a velocity by multiplying our speed by a normalized direction.
|
|
||||||
velocity: BALL_SPEED * INITIAL_BALL_DIRECTION.extend(0.0).normalize(),
|
|
||||||
});
|
|
||||||
// scoreboard
|
// scoreboard
|
||||||
commands.spawn_bundle(TextBundle {
|
commands.spawn_bundle(TextBundle {
|
||||||
text: Text {
|
text: Text {
|
||||||
|
@ -173,7 +172,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Collider::Solid);
|
.insert(Collider);
|
||||||
// right
|
// right
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(SpriteBundle {
|
.spawn_bundle(SpriteBundle {
|
||||||
|
@ -188,7 +187,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Collider::Solid);
|
.insert(Collider);
|
||||||
// bottom
|
// bottom
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(SpriteBundle {
|
.spawn_bundle(SpriteBundle {
|
||||||
|
@ -203,7 +202,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Collider::Solid);
|
.insert(Collider);
|
||||||
// top
|
// top
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(SpriteBundle {
|
.spawn_bundle(SpriteBundle {
|
||||||
|
@ -218,7 +217,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Collider::Solid);
|
.insert(Collider);
|
||||||
|
|
||||||
// Add bricks
|
// Add bricks
|
||||||
let bricks_width = BRICK_COLUMNS as f32 * (BRICK_SIZE.x + BRICK_SPACING) - BRICK_SPACING;
|
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;
|
) + bricks_offset;
|
||||||
// brick
|
// brick
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(SpriteBundle {
|
.spawn()
|
||||||
|
.insert(Brick)
|
||||||
|
.insert_bundle(SpriteBundle {
|
||||||
sprite: Sprite {
|
sprite: Sprite {
|
||||||
color: BRICK_COLOR,
|
color: BRICK_COLOR,
|
||||||
..default()
|
..default()
|
||||||
|
@ -246,16 +247,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Collider::Scorable);
|
.insert(Collider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paddle_movement_system(
|
fn paddle_movement_system(
|
||||||
keyboard_input: Res<Input<KeyCode>>,
|
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;
|
let mut direction = 0.0;
|
||||||
if keyboard_input.pressed(KeyCode::Left) {
|
if keyboard_input.pressed(KeyCode::Left) {
|
||||||
direction -= 1.0;
|
direction -= 1.0;
|
||||||
|
@ -267,14 +268,16 @@ fn paddle_movement_system(
|
||||||
|
|
||||||
let translation = &mut transform.translation;
|
let translation = &mut transform.translation;
|
||||||
// move the paddle horizontally
|
// move the paddle horizontally
|
||||||
translation.x += direction * paddle.speed * TIME_STEP;
|
translation.x += direction * PADDLE_SPEED * TIME_STEP;
|
||||||
// bound the paddle within the walls
|
// bound the paddle within the walls
|
||||||
translation.x = translation.x.min(PADDLE_BOUNDS).max(-PADDLE_BOUNDS);
|
translation.x = translation.x.min(PADDLE_BOUNDS).max(-PADDLE_BOUNDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ball_movement_system(mut ball_query: Query<(&Ball, &mut Transform)>) {
|
fn apply_velocity_system(mut query: Query<(&mut Transform, &Velocity)>) {
|
||||||
let (ball, mut transform) = ball_query.single_mut();
|
for (mut transform, velocity) in query.iter_mut() {
|
||||||
transform.translation += ball.velocity * TIME_STEP;
|
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>) {
|
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(
|
fn ball_collision_system(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut scoreboard: ResMut<Scoreboard>,
|
mut scoreboard: ResMut<Scoreboard>,
|
||||||
mut ball_query: Query<(&mut Ball, &Transform)>,
|
mut ball_query: Query<(&mut Velocity, &Transform), With<Ball>>,
|
||||||
collider_query: Query<(Entity, &Collider, &Transform)>,
|
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 ball_size = ball_transform.scale.truncate();
|
||||||
let velocity = &mut ball.velocity;
|
|
||||||
|
|
||||||
// check collision with walls
|
// 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(
|
let collision = collide(
|
||||||
ball_transform.translation,
|
ball_transform.translation,
|
||||||
ball_size,
|
ball_size,
|
||||||
|
@ -301,8 +303,8 @@ fn ball_collision_system(
|
||||||
transform.scale.truncate(),
|
transform.scale.truncate(),
|
||||||
);
|
);
|
||||||
if let Some(collision) = collision {
|
if let Some(collision) = collision {
|
||||||
// scorable colliders should be despawned and increment the scoreboard on collision
|
// Bricks should be despawned and increment the scoreboard on collision
|
||||||
if let Collider::Scorable = *collider {
|
if maybe_brick.is_some() {
|
||||||
scoreboard.score += 1;
|
scoreboard.score += 1;
|
||||||
commands.entity(collider_entity).despawn();
|
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
|
// only reflect if the ball's velocity is going in the opposite direction of the
|
||||||
// collision
|
// collision
|
||||||
match collision {
|
match collision {
|
||||||
Collision::Left => reflect_x = velocity.x > 0.0,
|
Collision::Left => reflect_x = ball_velocity.0.x > 0.0,
|
||||||
Collision::Right => reflect_x = velocity.x < 0.0,
|
Collision::Right => reflect_x = ball_velocity.0.x < 0.0,
|
||||||
Collision::Top => reflect_y = velocity.y < 0.0,
|
Collision::Top => reflect_y = ball_velocity.0.y < 0.0,
|
||||||
Collision::Bottom => reflect_y = velocity.y > 0.0,
|
Collision::Bottom => reflect_y = ball_velocity.0.y > 0.0,
|
||||||
Collision::Inside => { /* do nothing */ }
|
Collision::Inside => { /* do nothing */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
// reflect velocity on the x-axis if we hit something on the x-axis
|
// reflect velocity on the x-axis if we hit something on the x-axis
|
||||||
if reflect_x {
|
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
|
// reflect velocity on the y-axis if we hit something on the y-axis
|
||||||
if reflect_y {
|
if reflect_y {
|
||||||
velocity.y = -velocity.y;
|
ball_velocity.0.y = -ball_velocity.0.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue