Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 49 additions & 53 deletions examples/game/breakout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,29 @@ 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)
.run();
}

#[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,
}
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -173,7 +172,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
})
.insert(Collider::Solid);
.insert(Collider);
// right
commands
.spawn_bundle(SpriteBundle {
Expand All @@ -188,7 +187,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
})
.insert(Collider::Solid);
.insert(Collider);
// bottom
commands
.spawn_bundle(SpriteBundle {
Expand All @@ -203,7 +202,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
})
.insert(Collider::Solid);
.insert(Collider);
// top
commands
.spawn_bundle(SpriteBundle {
Expand All @@ -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;
Expand All @@ -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()
Expand All @@ -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;
Expand All @@ -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>) {
Expand All @@ -285,24 +288,23 @@ 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,
transform.translation,
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();
}
Expand All @@ -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;
}
}
}
Expand Down