Skip to content

Commit d51b54a

Browse files
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.
1 parent 9e450f2 commit d51b54a

File tree

1 file changed

+49
-53
lines changed

1 file changed

+49
-53
lines changed

examples/game/breakout.rs

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,29 @@ fn main() {
5454
.with_run_criteria(FixedTimestep::step(TIME_STEP as f64))
5555
.with_system(paddle_movement_system)
5656
.with_system(ball_collision_system)
57-
.with_system(ball_movement_system),
57+
.with_system(apply_velocity_system),
5858
)
5959
.add_system(scoreboard_system)
6060
.add_system(bevy::input::system::exit_on_esc_system)
6161
.run();
6262
}
6363

6464
#[derive(Component)]
65-
struct Paddle {
66-
speed: f32,
67-
}
65+
struct Paddle;
6866

6967
#[derive(Component)]
70-
struct Ball {
71-
velocity: Vec3,
72-
}
68+
struct Ball;
7369

7470
#[derive(Component)]
75-
enum Collider {
76-
Solid,
77-
Scorable,
78-
Paddle,
79-
}
71+
struct Velocity(Vec2);
72+
73+
#[derive(Component)]
74+
struct Collider;
75+
76+
#[derive(Component)]
77+
struct Brick;
8078

79+
// This resource tracks the game's score
8180
struct Scoreboard {
8281
score: usize,
8382
}
@@ -90,7 +89,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
9089
commands.spawn_bundle(UiCameraBundle::default());
9190
// paddle
9291
commands
93-
.spawn_bundle(SpriteBundle {
92+
.spawn()
93+
.insert(Paddle)
94+
.insert_bundle(SpriteBundle {
9495
transform: Transform {
9596
translation: Vec3::new(0.0, PADDLE_HEIGHT, 0.0),
9697
scale: PADDLE_SIZE,
@@ -102,13 +103,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
102103
},
103104
..default()
104105
})
105-
.insert(Paddle {
106-
speed: PADDLE_SPEED,
107-
})
108-
.insert(Collider::Paddle);
106+
.insert(Collider);
109107
// ball
108+
let ball_velocity = INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED;
109+
110110
commands
111-
.spawn_bundle(SpriteBundle {
111+
.spawn()
112+
.insert(Ball)
113+
.insert_bundle(SpriteBundle {
112114
transform: Transform {
113115
scale: BALL_SIZE,
114116
translation: BALL_STARTING_POSITION,
@@ -120,10 +122,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
120122
},
121123
..default()
122124
})
123-
.insert(Ball {
124-
// We can create a velocity by multiplying our speed by a normalized direction.
125-
velocity: BALL_SPEED * INITIAL_BALL_DIRECTION.extend(0.0).normalize(),
126-
});
125+
.insert(Velocity(ball_velocity));
127126
// scoreboard
128127
commands.spawn_bundle(TextBundle {
129128
text: Text {
@@ -173,7 +172,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
173172
},
174173
..default()
175174
})
176-
.insert(Collider::Solid);
175+
.insert(Collider);
177176
// right
178177
commands
179178
.spawn_bundle(SpriteBundle {
@@ -188,7 +187,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
188187
},
189188
..default()
190189
})
191-
.insert(Collider::Solid);
190+
.insert(Collider);
192191
// bottom
193192
commands
194193
.spawn_bundle(SpriteBundle {
@@ -203,7 +202,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
203202
},
204203
..default()
205204
})
206-
.insert(Collider::Solid);
205+
.insert(Collider);
207206
// top
208207
commands
209208
.spawn_bundle(SpriteBundle {
@@ -218,7 +217,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
218217
},
219218
..default()
220219
})
221-
.insert(Collider::Solid);
220+
.insert(Collider);
222221

223222
// Add bricks
224223
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>) {
234233
) + bricks_offset;
235234
// brick
236235
commands
237-
.spawn_bundle(SpriteBundle {
236+
.spawn()
237+
.insert(Brick)
238+
.insert_bundle(SpriteBundle {
238239
sprite: Sprite {
239240
color: BRICK_COLOR,
240241
..default()
@@ -246,16 +247,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
246247
},
247248
..default()
248249
})
249-
.insert(Collider::Scorable);
250+
.insert(Collider);
250251
}
251252
}
252253
}
253254

254255
fn paddle_movement_system(
255256
keyboard_input: Res<Input<KeyCode>>,
256-
mut query: Query<(&Paddle, &mut Transform)>,
257+
mut query: Query<&mut Transform, With<Paddle>>,
257258
) {
258-
let (paddle, mut transform) = query.single_mut();
259+
let mut transform = query.single_mut();
259260
let mut direction = 0.0;
260261
if keyboard_input.pressed(KeyCode::Left) {
261262
direction -= 1.0;
@@ -267,14 +268,16 @@ fn paddle_movement_system(
267268

268269
let translation = &mut transform.translation;
269270
// move the paddle horizontally
270-
translation.x += direction * paddle.speed * TIME_STEP;
271+
translation.x += direction * PADDLE_SPEED * TIME_STEP;
271272
// bound the paddle within the walls
272273
translation.x = translation.x.min(PADDLE_BOUNDS).max(-PADDLE_BOUNDS);
273274
}
274275

275-
fn ball_movement_system(mut ball_query: Query<(&Ball, &mut Transform)>) {
276-
let (ball, mut transform) = ball_query.single_mut();
277-
transform.translation += ball.velocity * TIME_STEP;
276+
fn apply_velocity_system(mut query: Query<(&mut Transform, &Velocity)>) {
277+
for (mut transform, velocity) in query.iter_mut() {
278+
transform.translation.x += velocity.0.x * TIME_STEP;
279+
transform.translation.y += velocity.0.y * TIME_STEP;
280+
}
278281
}
279282

280283
fn scoreboard_system(scoreboard: Res<Scoreboard>, mut query: Query<&mut Text>) {
@@ -285,24 +288,23 @@ fn scoreboard_system(scoreboard: Res<Scoreboard>, mut query: Query<&mut Text>) {
285288
fn ball_collision_system(
286289
mut commands: Commands,
287290
mut scoreboard: ResMut<Scoreboard>,
288-
mut ball_query: Query<(&mut Ball, &Transform)>,
289-
collider_query: Query<(Entity, &Collider, &Transform)>,
291+
mut ball_query: Query<(&mut Velocity, &Transform), With<Ball>>,
292+
collider_query: Query<(Entity, &Transform, Option<&Brick>), With<Collider>>,
290293
) {
291-
let (mut ball, ball_transform) = ball_query.single_mut();
294+
let (mut ball_velocity, ball_transform) = ball_query.single_mut();
292295
let ball_size = ball_transform.scale.truncate();
293-
let velocity = &mut ball.velocity;
294296

295297
// check collision with walls
296-
for (collider_entity, collider, transform) in collider_query.iter() {
298+
for (collider_entity, transform, maybe_brick) in collider_query.iter() {
297299
let collision = collide(
298300
ball_transform.translation,
299301
ball_size,
300302
transform.translation,
301303
transform.scale.truncate(),
302304
);
303305
if let Some(collision) = collision {
304-
// scorable colliders should be despawned and increment the scoreboard on collision
305-
if let Collider::Scorable = *collider {
306+
// Bricks should be despawned and increment the scoreboard on collision
307+
if maybe_brick.is_some() {
306308
scoreboard.score += 1;
307309
commands.entity(collider_entity).despawn();
308310
}
@@ -314,27 +316,21 @@ fn ball_collision_system(
314316
// only reflect if the ball's velocity is going in the opposite direction of the
315317
// collision
316318
match collision {
317-
Collision::Left => reflect_x = velocity.x > 0.0,
318-
Collision::Right => reflect_x = velocity.x < 0.0,
319-
Collision::Top => reflect_y = velocity.y < 0.0,
320-
Collision::Bottom => reflect_y = velocity.y > 0.0,
319+
Collision::Left => reflect_x = ball_velocity.0.x > 0.0,
320+
Collision::Right => reflect_x = ball_velocity.0.x < 0.0,
321+
Collision::Top => reflect_y = ball_velocity.0.y < 0.0,
322+
Collision::Bottom => reflect_y = ball_velocity.0.y > 0.0,
321323
Collision::Inside => { /* do nothing */ }
322324
}
323325

324326
// reflect velocity on the x-axis if we hit something on the x-axis
325327
if reflect_x {
326-
velocity.x = -velocity.x;
328+
ball_velocity.0.x = -ball_velocity.0.x;
327329
}
328330

329331
// reflect velocity on the y-axis if we hit something on the y-axis
330332
if reflect_y {
331-
velocity.y = -velocity.y;
332-
}
333-
334-
// break if this collide is on a solid, otherwise continue check whether a solid is
335-
// also in collision
336-
if let Collider::Solid = *collider {
337-
break;
333+
ball_velocity.0.y = -ball_velocity.0.y;
338334
}
339335
}
340336
}

0 commit comments

Comments
 (0)