diff --git a/examples/game/breakout.rs b/examples/game/breakout.rs index e33b34550a..bd420df48b 100644 --- a/examples/game/breakout.rs +++ b/examples/game/breakout.rs @@ -1,16 +1,53 @@ +//! A simplified implementation of the classic game "Breakout" + use bevy::{ core::FixedTimestep, + math::{const_vec2, const_vec3}, prelude::*, sprite::collide_aabb::{collide, Collision}, }; -/// An implementation of the classic game "Breakout" +// Defines the amount of time that should elapse between each physics step. const TIME_STEP: f32 = 1.0 / 60.0; + +// These constants are defined in `Transform` units. +// Using the default 2D camera they correspond 1:1 with screen pixels. +// The `const_vec3!` macros are needed as functions that operate on floats cannot be constant in Rust. +const PADDLE_HEIGHT: f32 = -215.0; +const PADDLE_SIZE: Vec3 = const_vec3!([120.0, 30.0, 0.0]); +const PADDLE_SPEED: f32 = 500.0; +const PADDLE_BOUNDS: f32 = 380.0; + +// We set the z-value of the ball to 1 so it renders on top in the case of overlapping sprites. +const BALL_STARTING_POSITION: Vec3 = const_vec3!([0.0, -50.0, 1.0]); +const BALL_SIZE: Vec3 = const_vec3!([30.0, 30.0, 0.0]); +const BALL_SPEED: f32 = 400.0; +const INITIAL_BALL_DIRECTION: Vec2 = const_vec2!([0.5, -0.5]); + +const PLAY_AREA_BOUNDS: Vec2 = const_vec2!([900.0, 600.0]); +const WALL_THICKNESS: f32 = 10.0; + +const BRICK_ROWS: u8 = 4; +const BRICK_COLUMNS: u8 = 5; +const BRICK_SPACING: f32 = 20.0; +const BRICK_SIZE: Vec3 = const_vec3!([150.0, 30.0, 1.0]); + +const SCOREBOARD_FONT_SIZE: f32 = 40.0; +const SCOREBOARD_TEXT_PADDING: Val = Val::Px(5.0); + +const BACKGROUND_COLOR: Color = Color::rgb(0.95, 0.95, 0.95); +const PADDLE_COLOR: Color = Color::rgb(0.5, 0.5, 1.0); +const BALL_COLOR: Color = Color::rgb(1.0, 0.5, 0.5); +const BRICK_COLOR: Color = Color::rgb(0.5, 0.5, 1.0); +const WALL_COLOR: Color = Color::DARK_GRAY; +const TEXT_COLOR: Color = Color::BLUE; +const SCORE_COLOR: Color = Color::RED; + fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(Scoreboard { score: 0 }) - .insert_resource(ClearColor(Color::rgb(0.9, 0.9, 0.9))) + .insert_resource(ClearColor(BACKGROUND_COLOR)) .add_startup_system(setup) .add_system_set( SystemSet::new() @@ -55,34 +92,37 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn_bundle(SpriteBundle { transform: Transform { - translation: Vec3::new(0.0, -215.0, 0.0), - scale: Vec3::new(120.0, 30.0, 0.0), + translation: Vec3::new(0.0, PADDLE_HEIGHT, 0.0), + scale: PADDLE_SIZE, ..default() }, sprite: Sprite { - color: Color::rgb(0.5, 0.5, 1.0), + color: PADDLE_COLOR, ..default() }, ..default() }) - .insert(Paddle { speed: 500.0 }) + .insert(Paddle { + speed: PADDLE_SPEED, + }) .insert(Collider::Paddle); // ball commands .spawn_bundle(SpriteBundle { transform: Transform { - scale: Vec3::new(30.0, 30.0, 0.0), - translation: Vec3::new(0.0, -50.0, 1.0), + scale: BALL_SIZE, + translation: BALL_STARTING_POSITION, ..default() }, sprite: Sprite { - color: Color::rgb(1.0, 0.5, 0.5), + color: BALL_COLOR, ..default() }, ..default() }) .insert(Ball { - velocity: 400.0 * Vec3::new(0.5, -0.5, 0.0).normalize(), + // We can create a velocity by multiplying our speed by a normalized direction. + velocity: BALL_SPEED * INITIAL_BALL_DIRECTION.extend(0.0).normalize(), }); // scoreboard commands.spawn_bundle(TextBundle { @@ -92,16 +132,16 @@ fn setup(mut commands: Commands, asset_server: Res) { value: "Score: ".to_string(), style: TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 40.0, - color: Color::rgb(0.5, 0.5, 1.0), + font_size: SCOREBOARD_FONT_SIZE, + color: TEXT_COLOR, }, }, TextSection { value: "".to_string(), style: TextStyle { font: asset_server.load("fonts/FiraMono-Medium.ttf"), - font_size: 40.0, - color: Color::rgb(1.0, 0.5, 0.5), + font_size: SCOREBOARD_FONT_SIZE, + color: SCORE_COLOR, }, }, ], @@ -110,8 +150,8 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { position_type: PositionType::Absolute, position: Rect { - top: Val::Px(5.0), - left: Val::Px(5.0), + top: SCOREBOARD_TEXT_PADDING, + left: SCOREBOARD_TEXT_PADDING, ..default() }, ..default() @@ -119,21 +159,16 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }); - // Add walls - let wall_color = Color::rgb(0.8, 0.8, 0.8); - let wall_thickness = 10.0; - let bounds = Vec2::new(900.0, 600.0); - // left commands .spawn_bundle(SpriteBundle { transform: Transform { - translation: Vec3::new(-bounds.x / 2.0, 0.0, 0.0), - scale: Vec3::new(wall_thickness, bounds.y + wall_thickness, 1.0), + translation: Vec3::new(-PLAY_AREA_BOUNDS.x / 2.0, 0.0, 0.0), + scale: Vec3::new(WALL_THICKNESS, PLAY_AREA_BOUNDS.y + WALL_THICKNESS, 1.0), ..default() }, sprite: Sprite { - color: wall_color, + color: WALL_COLOR, ..default() }, ..default() @@ -143,12 +178,12 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn_bundle(SpriteBundle { transform: Transform { - translation: Vec3::new(bounds.x / 2.0, 0.0, 0.0), - scale: Vec3::new(wall_thickness, bounds.y + wall_thickness, 1.0), + translation: Vec3::new(PLAY_AREA_BOUNDS.x / 2.0, 0.0, 0.0), + scale: Vec3::new(WALL_THICKNESS, PLAY_AREA_BOUNDS.y + WALL_THICKNESS, 1.0), ..default() }, sprite: Sprite { - color: wall_color, + color: WALL_COLOR, ..default() }, ..default() @@ -158,12 +193,12 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn_bundle(SpriteBundle { transform: Transform { - translation: Vec3::new(0.0, -bounds.y / 2.0, 0.0), - scale: Vec3::new(bounds.x + wall_thickness, wall_thickness, 1.0), + translation: Vec3::new(0.0, -PLAY_AREA_BOUNDS.y / 2.0, 0.0), + scale: Vec3::new(PLAY_AREA_BOUNDS.x + WALL_THICKNESS, WALL_THICKNESS, 1.0), ..default() }, sprite: Sprite { - color: wall_color, + color: WALL_COLOR, ..default() }, ..default() @@ -173,12 +208,12 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn_bundle(SpriteBundle { transform: Transform { - translation: Vec3::new(0.0, bounds.y / 2.0, 0.0), - scale: Vec3::new(bounds.x + wall_thickness, wall_thickness, 1.0), + translation: Vec3::new(0.0, PLAY_AREA_BOUNDS.y / 2.0, 0.0), + scale: Vec3::new(PLAY_AREA_BOUNDS.x + WALL_THICKNESS, WALL_THICKNESS, 1.0), ..default() }, sprite: Sprite { - color: wall_color, + color: WALL_COLOR, ..default() }, ..default() @@ -186,19 +221,14 @@ fn setup(mut commands: Commands, asset_server: Res) { .insert(Collider::Solid); // Add bricks - let brick_rows = 4; - let brick_columns = 5; - let brick_spacing = 20.0; - let brick_size = Vec3::new(150.0, 30.0, 1.0); - 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; // center the bricks and move them up a bit - let bricks_offset = Vec3::new(-(bricks_width - brick_size.x) / 2.0, 100.0, 0.0); - let brick_color = Color::rgb(0.5, 0.5, 1.0); - for row in 0..brick_rows { - let y_position = row as f32 * (brick_size.y + brick_spacing); - for column in 0..brick_columns { + let bricks_offset = Vec3::new(-(bricks_width - BRICK_SIZE.x) / 2.0, 100.0, 0.0); + for row in 0..BRICK_ROWS { + let y_position = row as f32 * (BRICK_SIZE.y + BRICK_SPACING); + for column in 0..BRICK_COLUMNS { let brick_position = Vec3::new( - column as f32 * (brick_size.x + brick_spacing), + column as f32 * (BRICK_SIZE.x + BRICK_SPACING), y_position, 0.0, ) + bricks_offset; @@ -206,12 +236,12 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn_bundle(SpriteBundle { sprite: Sprite { - color: brick_color, + color: BRICK_COLOR, ..default() }, transform: Transform { translation: brick_position, - scale: brick_size, + scale: BRICK_SIZE, ..default() }, ..default() @@ -239,7 +269,7 @@ fn paddle_movement_system( // move the paddle horizontally translation.x += direction * paddle.speed * TIME_STEP; // bound the paddle within the walls - translation.x = translation.x.min(380.0).max(-380.0); + translation.x = translation.x.min(PADDLE_BOUNDS).max(-PADDLE_BOUNDS); } fn ball_movement_system(mut ball_query: Query<(&Ball, &mut Transform)>) {