@@ -13,30 +13,39 @@ const TIME_STEP: f32 = 1.0 / 60.0;
13
13
// These constants are defined in `Transform` units.
14
14
// Using the default 2D camera they correspond 1:1 with screen pixels.
15
15
// The `const_vec3!` macros are needed as functions that operate on floats cannot be constant in Rust.
16
- const PADDLE_HEIGHT : f32 = - 215.0 ;
17
- const PADDLE_SIZE : Vec3 = const_vec3 ! ( [ 120.0 , 30.0 , 0.0 ] ) ;
16
+ const PADDLE_SIZE : Vec3 = const_vec3 ! ( [ 120.0 , 20.0 , 0.0 ] ) ;
17
+ const GAP_BETWEEN_PADDLE_AND_FLOOR : f32 = 60.0 ;
18
18
const PADDLE_SPEED : f32 = 500.0 ;
19
- const PADDLE_BOUNDS : f32 = 380.0 ;
19
+ // How close can the paddle get to the wall
20
+ const PADDLE_PADDING : f32 = 10.0 ;
20
21
21
22
// We set the z-value of the ball to 1 so it renders on top in the case of overlapping sprites.
22
23
const BALL_STARTING_POSITION : Vec3 = const_vec3 ! ( [ 0.0 , -50.0 , 1.0 ] ) ;
23
24
const BALL_SIZE : Vec3 = const_vec3 ! ( [ 30.0 , 30.0 , 0.0 ] ) ;
24
25
const BALL_SPEED : f32 = 400.0 ;
25
26
const INITIAL_BALL_DIRECTION : Vec2 = const_vec2 ! ( [ 0.5 , -0.5 ] ) ;
26
27
27
- const PLAY_AREA_BOUNDS : Vec2 = const_vec2 ! ( [ 900.0 , 600.0 ] ) ;
28
28
const WALL_THICKNESS : f32 = 10.0 ;
29
+ // x coordinates
30
+ const LEFT_WALL : f32 = -450. ;
31
+ const RIGHT_WALL : f32 = 450. ;
32
+ // y coordinates
33
+ const BOTTOM_WALL : f32 = -300. ;
34
+ const TOP_WALL : f32 = 300. ;
29
35
30
- const BRICK_ROWS : u8 = 4 ;
31
- const BRICK_COLUMNS : u8 = 5 ;
32
- const BRICK_SPACING : f32 = 20.0 ;
33
- const BRICK_SIZE : Vec3 = const_vec3 ! ( [ 150.0 , 30.0 , 1.0 ] ) ;
36
+ const BRICK_SIZE : Vec2 = const_vec2 ! ( [ 100. , 30. ] ) ;
37
+ // These values are exact
38
+ const GAP_BETWEEN_PADDLE_AND_BRICKS : f32 = 270.0 ;
39
+ const GAP_BETWEEN_BRICKS : f32 = 5.0 ;
40
+ // These values are lower bounds, as the number of bricks is computed
41
+ const GAP_BETWEEN_BRICKS_AND_CEILING : f32 = 20.0 ;
42
+ const GAP_BETWEEN_BRICKS_AND_SIDES : f32 = 20.0 ;
34
43
35
44
const SCOREBOARD_FONT_SIZE : f32 = 40.0 ;
36
45
const SCOREBOARD_TEXT_PADDING : Val = Val :: Px ( 5.0 ) ;
37
46
38
47
const BACKGROUND_COLOR : Color = Color :: rgb ( 0.9 , 0.9 , 0.9 ) ;
39
- const PADDLE_COLOR : Color = Color :: rgb ( 0.5 , 0.5 , 1.0 ) ;
48
+ const PADDLE_COLOR : Color = Color :: rgb ( 0.3 , 0.3 , 0.7 ) ;
40
49
const BALL_COLOR : Color = Color :: rgb ( 1.0 , 0.5 , 0.5 ) ;
41
50
const BRICK_COLOR : Color = Color :: rgb ( 0.5 , 0.5 , 1.0 ) ;
42
51
const WALL_COLOR : Color = Color :: rgb ( 0.8 , 0.8 , 0.8 ) ;
@@ -52,9 +61,9 @@ fn main() {
52
61
. add_system_set (
53
62
SystemSet :: new ( )
54
63
. with_run_criteria ( FixedTimestep :: step ( TIME_STEP as f64 ) )
55
- . with_system ( move_paddle)
56
64
. with_system ( check_for_collisions)
57
- . with_system ( apply_velocity) ,
65
+ . with_system ( move_paddle. before ( check_for_collisions) )
66
+ . with_system ( apply_velocity. before ( check_for_collisions) ) ,
58
67
)
59
68
. add_system ( update_scoreboard)
60
69
. add_system ( bevy:: input:: system:: exit_on_esc_system)
@@ -76,24 +85,97 @@ struct Collider;
76
85
#[ derive( Component ) ]
77
86
struct Brick ;
78
87
88
+ // This bundle is a collection of the components that define a "wall" in our game
89
+ #[ derive( Bundle ) ]
90
+ struct WallBundle {
91
+ // You can nest bundles inside of other bundles like this
92
+ // Allowing you to compose their functionality
93
+ #[ bundle]
94
+ sprite_bundle : SpriteBundle ,
95
+ collider : Collider ,
96
+ }
97
+
98
+ /// Which side of the arena is this wall located on?
99
+ enum WallLocation {
100
+ Left ,
101
+ Right ,
102
+ Bottom ,
103
+ Top ,
104
+ }
105
+
106
+ impl WallLocation {
107
+ fn position ( & self ) -> Vec2 {
108
+ match self {
109
+ WallLocation :: Left => Vec2 :: new ( LEFT_WALL , 0. ) ,
110
+ WallLocation :: Right => Vec2 :: new ( RIGHT_WALL , 0. ) ,
111
+ WallLocation :: Bottom => Vec2 :: new ( 0. , BOTTOM_WALL ) ,
112
+ WallLocation :: Top => Vec2 :: new ( 0. , TOP_WALL ) ,
113
+ }
114
+ }
115
+
116
+ fn size ( & self ) -> Vec2 {
117
+ let arena_height = TOP_WALL - BOTTOM_WALL ;
118
+ let arena_width = RIGHT_WALL - LEFT_WALL ;
119
+ // Make sure we haven't messed up our constants
120
+ assert ! ( arena_height > 0.0 ) ;
121
+ assert ! ( arena_width > 0.0 ) ;
122
+
123
+ match self {
124
+ WallLocation :: Left => Vec2 :: new ( WALL_THICKNESS , arena_height + WALL_THICKNESS ) ,
125
+ WallLocation :: Right => Vec2 :: new ( WALL_THICKNESS , arena_height + WALL_THICKNESS ) ,
126
+ WallLocation :: Bottom => Vec2 :: new ( arena_width + WALL_THICKNESS , WALL_THICKNESS ) ,
127
+ WallLocation :: Top => Vec2 :: new ( arena_width + WALL_THICKNESS , WALL_THICKNESS ) ,
128
+ }
129
+ }
130
+ }
131
+
132
+ impl WallBundle {
133
+ // This "builder method" allows us to reuse logic across our wall entities,
134
+ // making our code easier to read and less prone to bugs when we change the logic
135
+ fn new ( location : WallLocation ) -> WallBundle {
136
+ WallBundle {
137
+ sprite_bundle : SpriteBundle {
138
+ transform : Transform {
139
+ // We need to convert our Vec2 into a Vec3, by giving it a z-coordinate
140
+ // This is used to determine the order of our sprites
141
+ translation : location. position ( ) . extend ( 0.0 ) ,
142
+ // The z-scale of 2D objects must always be 1.0,
143
+ // or their ordering will be affected in surprising ways.
144
+ // See https://github.com/bevyengine/bevy/issues/4149
145
+ scale : location. size ( ) . extend ( 1.0 ) ,
146
+ ..default ( )
147
+ } ,
148
+ sprite : Sprite {
149
+ color : WALL_COLOR ,
150
+ ..default ( )
151
+ } ,
152
+ ..default ( )
153
+ } ,
154
+ collider : Collider ,
155
+ }
156
+ }
157
+ }
158
+
79
159
// This resource tracks the game's score
80
160
struct Scoreboard {
81
161
score : usize ,
82
162
}
83
163
164
+ // Add the game's entities to our world
84
165
fn setup ( mut commands : Commands , asset_server : Res < AssetServer > ) {
85
- // Add the game's entities to our world
86
-
87
- // cameras
166
+ // Cameras
88
167
commands. spawn_bundle ( OrthographicCameraBundle :: new_2d ( ) ) ;
89
168
commands. spawn_bundle ( UiCameraBundle :: default ( ) ) ;
90
- // paddle
169
+
170
+ // Paddle
171
+ let paddle_y = BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR ;
172
+
91
173
commands
92
174
. spawn ( )
93
175
. insert ( Paddle )
94
176
. insert_bundle ( SpriteBundle {
95
177
transform : Transform {
96
- translation : Vec3 :: new ( 0.0 , PADDLE_HEIGHT , 0.0 ) ,
178
+ translation : Vec3 :: new ( 0.0 , paddle_y , 0.0 ) ,
97
179
scale : PADDLE_SIZE ,
98
180
..default ( )
99
181
} ,
@@ -104,9 +186,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
104
186
..default ( )
105
187
} )
106
188
. insert ( Collider ) ;
107
- // ball
108
- let ball_velocity = INITIAL_BALL_DIRECTION . normalize ( ) * BALL_SPEED ;
109
189
190
+ // Ball
110
191
commands
111
192
. spawn ( )
112
193
. insert ( Ball )
@@ -122,8 +203,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
122
203
} ,
123
204
..default ( )
124
205
} )
125
- . insert ( Velocity ( ball_velocity) ) ;
126
- // scoreboard
206
+ . insert ( Velocity ( INITIAL_BALL_DIRECTION . normalize ( ) * BALL_SPEED ) ) ;
207
+
208
+ // Scoreboard
127
209
commands. spawn_bundle ( TextBundle {
128
210
text : Text {
129
211
sections : vec ! [
@@ -158,79 +240,51 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
158
240
..default ( )
159
241
} ) ;
160
242
161
- // left
162
- commands
163
- . spawn_bundle ( SpriteBundle {
164
- transform : Transform {
165
- translation : Vec3 :: new ( -PLAY_AREA_BOUNDS . x / 2.0 , 0.0 , 0.0 ) ,
166
- scale : Vec3 :: new ( WALL_THICKNESS , PLAY_AREA_BOUNDS . y + WALL_THICKNESS , 1.0 ) ,
167
- ..default ( )
168
- } ,
169
- sprite : Sprite {
170
- color : WALL_COLOR ,
171
- ..default ( )
172
- } ,
173
- ..default ( )
174
- } )
175
- . insert ( Collider ) ;
176
- // right
177
- commands
178
- . spawn_bundle ( SpriteBundle {
179
- transform : Transform {
180
- translation : Vec3 :: new ( PLAY_AREA_BOUNDS . x / 2.0 , 0.0 , 0.0 ) ,
181
- scale : Vec3 :: new ( WALL_THICKNESS , PLAY_AREA_BOUNDS . y + WALL_THICKNESS , 1.0 ) ,
182
- ..default ( )
183
- } ,
184
- sprite : Sprite {
185
- color : WALL_COLOR ,
186
- ..default ( )
187
- } ,
188
- ..default ( )
189
- } )
190
- . insert ( Collider ) ;
191
- // bottom
192
- commands
193
- . spawn_bundle ( SpriteBundle {
194
- transform : Transform {
195
- translation : Vec3 :: new ( 0.0 , -PLAY_AREA_BOUNDS . y / 2.0 , 0.0 ) ,
196
- scale : Vec3 :: new ( PLAY_AREA_BOUNDS . x + WALL_THICKNESS , WALL_THICKNESS , 1.0 ) ,
197
- ..default ( )
198
- } ,
199
- sprite : Sprite {
200
- color : WALL_COLOR ,
201
- ..default ( )
202
- } ,
203
- ..default ( )
204
- } )
205
- . insert ( Collider ) ;
206
- // top
207
- commands
208
- . spawn_bundle ( SpriteBundle {
209
- transform : Transform {
210
- translation : Vec3 :: new ( 0.0 , PLAY_AREA_BOUNDS . y / 2.0 , 0.0 ) ,
211
- scale : Vec3 :: new ( PLAY_AREA_BOUNDS . x + WALL_THICKNESS , WALL_THICKNESS , 1.0 ) ,
212
- ..default ( )
213
- } ,
214
- sprite : Sprite {
215
- color : WALL_COLOR ,
216
- ..default ( )
217
- } ,
218
- ..default ( )
219
- } )
220
- . insert ( Collider ) ;
243
+ // Walls
244
+ commands. spawn_bundle ( WallBundle :: new ( WallLocation :: Left ) ) ;
245
+ commands. spawn_bundle ( WallBundle :: new ( WallLocation :: Right ) ) ;
246
+ commands. spawn_bundle ( WallBundle :: new ( WallLocation :: Bottom ) ) ;
247
+ commands. spawn_bundle ( WallBundle :: new ( WallLocation :: Top ) ) ;
248
+
249
+ // Bricks
250
+ // Negative scales result in flipped sprites / meshes,
251
+ // which is definitely not what we want here
252
+ assert ! ( BRICK_SIZE . x > 0.0 ) ;
253
+ assert ! ( BRICK_SIZE . y > 0.0 ) ;
254
+
255
+ let total_width_of_bricks = ( RIGHT_WALL - LEFT_WALL ) - 2. * GAP_BETWEEN_BRICKS_AND_SIDES ;
256
+ let bottom_edge_of_bricks = paddle_y + GAP_BETWEEN_PADDLE_AND_BRICKS ;
257
+ let total_height_of_bricks = TOP_WALL - bottom_edge_of_bricks - GAP_BETWEEN_BRICKS_AND_CEILING ;
258
+
259
+ assert ! ( total_width_of_bricks > 0.0 ) ;
260
+ assert ! ( total_height_of_bricks > 0.0 ) ;
261
+
262
+ // Given the space available, compute how many rows and columns of bricks we can fit
263
+ let n_columns = ( total_width_of_bricks / ( BRICK_SIZE . x + GAP_BETWEEN_BRICKS ) ) . floor ( ) as usize ;
264
+ let n_rows = ( total_height_of_bricks / ( BRICK_SIZE . y + GAP_BETWEEN_BRICKS ) ) . floor ( ) as usize ;
265
+ let n_vertical_gaps = n_columns - 1 ;
266
+
267
+ // Because we need to round the number of columns,
268
+ // the space on the top and sides of the bricks only captures a lower bound, not an exact value
269
+ let center_of_bricks = ( LEFT_WALL + RIGHT_WALL ) / 2.0 ;
270
+ let left_edge_of_bricks = center_of_bricks
271
+ // Space taken up by the bricks
272
+ - ( n_columns as f32 / 2.0 * BRICK_SIZE . x )
273
+ // Space taken up by the gaps
274
+ - n_vertical_gaps as f32 / 2.0 * GAP_BETWEEN_BRICKS ;
275
+
276
+ // In Bevy, the `translation` of an entity describes the center point,
277
+ // not its bottom-left corner
278
+ let offset_x = left_edge_of_bricks + BRICK_SIZE . x / 2. ;
279
+ let offset_y = bottom_edge_of_bricks + BRICK_SIZE . y / 2. ;
280
+
281
+ for row in 0 ..n_rows {
282
+ for column in 0 ..n_columns {
283
+ let brick_position = Vec2 :: new (
284
+ offset_x + column as f32 * ( BRICK_SIZE . x + GAP_BETWEEN_BRICKS ) ,
285
+ offset_y + row as f32 * ( BRICK_SIZE . y + GAP_BETWEEN_BRICKS ) ,
286
+ ) ;
221
287
222
- // Add bricks
223
- let bricks_width = BRICK_COLUMNS as f32 * ( BRICK_SIZE . x + BRICK_SPACING ) - BRICK_SPACING ;
224
- // center the bricks and move them up a bit
225
- let bricks_offset = Vec3 :: new ( -( bricks_width - BRICK_SIZE . x ) / 2.0 , 100.0 , 0.0 ) ;
226
- for row in 0 ..BRICK_ROWS {
227
- let y_position = row as f32 * ( BRICK_SIZE . y + BRICK_SPACING ) ;
228
- for column in 0 ..BRICK_COLUMNS {
229
- let brick_position = Vec3 :: new (
230
- column as f32 * ( BRICK_SIZE . x + BRICK_SPACING ) ,
231
- y_position,
232
- 0.0 ,
233
- ) + bricks_offset;
234
288
// brick
235
289
commands
236
290
. spawn ( )
@@ -241,8 +295,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
241
295
..default ( )
242
296
} ,
243
297
transform : Transform {
244
- translation : brick_position,
245
- scale : BRICK_SIZE ,
298
+ translation : brick_position. extend ( 0.0 ) ,
299
+ scale : Vec3 :: new ( BRICK_SIZE . x , BRICK_SIZE . y , 1.0 ) ,
246
300
..default ( )
247
301
} ,
248
302
..default ( )
@@ -256,8 +310,9 @@ fn move_paddle(
256
310
keyboard_input : Res < Input < KeyCode > > ,
257
311
mut query : Query < & mut Transform , With < Paddle > > ,
258
312
) {
259
- let mut transform = query. single_mut ( ) ;
313
+ let mut paddle_transform = query. single_mut ( ) ;
260
314
let mut direction = 0.0 ;
315
+
261
316
if keyboard_input. pressed ( KeyCode :: Left ) {
262
317
direction -= 1.0 ;
263
318
}
@@ -266,11 +321,15 @@ fn move_paddle(
266
321
direction += 1.0 ;
267
322
}
268
323
269
- let translation = & mut transform. translation ;
270
- // move the paddle horizontally
271
- translation. x += direction * PADDLE_SPEED * TIME_STEP ;
272
- // bound the paddle within the walls
273
- translation. x = translation. x . min ( PADDLE_BOUNDS ) . max ( -PADDLE_BOUNDS ) ;
324
+ // Calculate the new horizontal paddle position based on player input
325
+ let new_paddle_position = paddle_transform. translation . x + direction * PADDLE_SPEED * TIME_STEP ;
326
+
327
+ // Update the paddle position,
328
+ // making sure it doesn't cause the paddle to leave the arena
329
+ let left_bound = LEFT_WALL + WALL_THICKNESS / 2.0 + PADDLE_SIZE . x / 2.0 + PADDLE_PADDING ;
330
+ let right_bound = RIGHT_WALL - WALL_THICKNESS / 2.0 - PADDLE_SIZE . x / 2.0 - PADDLE_PADDING ;
331
+
332
+ paddle_transform. translation . x = new_paddle_position. clamp ( left_bound, right_bound) ;
274
333
}
275
334
276
335
fn apply_velocity ( mut query : Query < ( & mut Transform , & Velocity ) > ) {
0 commit comments