Falling squares
To make sure there is something happening in our game, it’s time to create some action. Since the hero in our game is a brave circle, our opponents will be squares falling down from the top of the window.
Implementation
Struct for shapes
To keep track of our circle and all the squares, we’ll create a struct that we
can name Shape
, which will contain the size and speed, as well as x
and y
coordinates.
struct Shape {
size: f32,
speed: f32,
x: f32,
y: f32,
}
Initialize random number generator
We’ll use a random number generator to determine when new squares should
appear on the screen, how big they should be and how fast they will move.
Therefore, we need to seed the random generator so that it doesn’t produce the
same random numbers every time. This is done at the beginning of the main
function using the rand::srand()
method, to which we pass the current time
as the seed.
rand::srand(miniquad::date::now() as u64);
We are using the function miniquad::date::now()
from the
graphics library Miniquad
to get the current time.
Vector of squares
At the beginning of the main
function we create a vector called squares
that will contain all the squares to be displayed on the screen. The new
variable circle
will represent our hero, the amazing circle. The speed
uses the constant MOVEMENT_SPEED
, and the x
and y
fields are set to the
center of the screen.
let mut squares = vec![];
let mut circle = Shape {
size: 32.0,
speed: MOVEMENT_SPEED,
x: screen_width() / 2.0,
y: screen_height() / 2.0,
};
Start by modifying the program so that circle
is used instead of the
variables x
and y
and confirm that everything works as it did before
adding the enemy squares.
The Rust compiler might warn about “type annotations needed” on the Vector. Once we add an enemy square in the next section that warning should disappear.
Add enemy squares
It’s time to start the invasion of evil squares. Here, just like before, we split updating the movement and drawing the squares. This way, the movement does not depend on the screen’s refresh rate, ensuring that all changes are done before we start drawing anything to the screen.
First, we use the function rand::gen_range()
to determine whether to add a
new square. It takes two arguments, a minimum value and a maximum value, and
returns a random number between those two values. We generate a random number
between 0 and 99, and if the value is 95 or higher, a new Shape
is created
and added to the squares
vector. To add some variation, we also use
rand::gen_range()
to get different size, speed, and starting position of
every square.
if rand::gen_range(0, 99) >= 95 {
let size = rand::gen_range(16.0, 64.0);
squares.push(Shape {
size,
speed: rand::gen_range(50.0, 150.0),
x: rand::gen_range(size / 2.0, screen_width() - size / 2.0),
y: -size,
});
}
Rectangles are drawn starting from their upper left corner. Therefore, we
subtract half of the square’s size when calculating the x
position. The
y
position starts at a negative value of the square’s size, so it starts
completely outside the screen.
Update square positions
Now we can iterate through the vector using a for loop and update the
Y position using the square’s speed and the variable delta_time
. This will
make the squares move downwards across the screen.
for square in &mut squares {
square.y += square.speed * delta_time;
}
Remove invisible squares
Next, we need to clean up all the squares that have moved off the bottom of
the screen since it’s unnecessary to draw things that are not visible. We’ll
use the retain()
method on the vector, which takes a function that determines
whether elements should be kept. We’ll check if the square’s y
value is still
less than the height of the window plus the size of the square.
squares.retain(|square| square.y < screen_height() + square.size);
Draw the squares
Finally, we add a for
loop that iterates over the squares
vector and uses
the function draw_rectangle()
to draw a rectangle at the updated position
and with the correct size. Since rectangles are drawn with x and y from the
top-left corner and our coordinates are based on the center of the square, we
use some mathematics to calculate where they should be placed. The size is
used twice, once for the width of the square and once for the height. We set
the color to GREEN
so that all squares will have a green color.
It’s also possible to use the function
draw_rectangle_ex()
that uses the struct
DrawTextureParams
instead of a color. In addition to setting color, it can be used to set
rotation
and offset
of the rectangle.
for square in &squares {
draw_rectangle(
square.x - square.size / 2.0,
square.y - square.size / 2.0,
square.size,
square.size,
GREEN,
);
}
Try setting a different color for each square by using the method choose()
on vectors from Macroquad’s
ChooseRandom trait,
which returns a random element from the vector.