We need to create a new Bevy system to move the pipes from the right to the left at a constant speed. Let's create a new system in the systems.rs file and call it pipes:
We need the pipes to move at a constant speed based on the time, so we'll need to bring in the Time from the arguments, we also need to query the upper and lower pipes.
This will make the pipes to move constantly, but when they go out of screen, they go out forever. We need to reset the pipes to the right side of the screen when they go out of the screen. We can do this by checking the x position of the pipes and resetting them if they go out of the screen.
Learn Rust by Practice
Master Rust through hands-on coding exercises and real-world examples.
We need to first find the utmost right pipe's position. We can do this by querying the pipes and finding the pipe with the highest x position. After we find that position, we'll reset the pipe that is out of screen to that position + 200.0 which 200.0 is the distance between the pipes.
Query Conversion: upper_pipe_query.iter() converts the query results into an iterator, enabling the use of iterator methods like max_by.
Finding the Maximum: .max_by(|(_, a), (_, b)| a.translation.x.partial_cmp(&b.translation.x).unwrap()) iterates through the pipes, comparing their x positions to find the pipe with the highest x value.
Unwrapping the Result: .unwrap() ensures that we safely extract the result of the max_by method. This is safe because we assume there is at least one pipe in the query.
Accessing the Transform: .1 accesses the second element of the tuple, which is the transform component containing the position data.
Getting the x Position: .translation.x retrieves the x position of the rightmost pipe, which will be used to reset any pipe that moves out of the screen.
After we found out the rightmost pipe, we can reset the pipes that are out of the screen:
We can check if the pipe is out of screen by checking if the x position of the pipe is less than -(WINDOW_WIDTH / 2.) - 26.. The 26. is the half of the width of the pipe. If the pipe is out of the screen, we reset the pipe to the right side of the screen.
We then re-use the random_pipe_position function to get the new y position of the pipes, and reset the position once the pipe is out of the screen.
Now that we have the pipes moving, we need to check for collisions between the bird and the pipes. We can do this by checking if the bird's position is inside the pipe's position. To do that we can check if the bird's x position is between the pipe's x position and the pipe's x position + the pipe's width and the bird's y position is between the pipe's y position and the pipe's y position + the pipe's height.
Since this logic goes beyond the scope of learning Rust and Bevy, we'll not explain the code in detail. You can read the code and try to understand it if you want.
Here, we defined a closure is_collision that takes the reference of the bird's and pipe's Transform and returns a boolean value. The closure calculates the positions and dimensions of the bird and the pipe, then checks if the bird is colliding with the pipe based on the conditions provided.
If true, that means the collision happened and we'll reset the game.
As you can see, there is nothing new we used here, we used the commands to play the hit sound and set the GameState to GameOver if a collision is detected.
Let's run the code and see if the pipes move and the collision detection works.
Pipes moving and collision detection
Now, pipe movements and collision detection are implemented. In the next part, we'll implement the score counting logic and update the score state when the bird passes the pipes, we'll also render the score on the top-right corner of the screen. See you in the next part!