To place a board on the screen, we'll add a new startup system to handle spawning it. This looks very similar to the setup
system we added to handle the camera.
The big difference is that since we now have multiple startup systems, we can add them all at once with add_startup_systems
(note the s at the end there), which will accept a tuple of multiple functions, including the new one named spawn_board
.
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "2048".to_string(),
..default()
}),
..default()
}))
.add_startup_systems((setup, spawn_board))
.run()
}
We'll create a function (spawn_board
) that takes a mutable argument of type Commands
so that we can spawn a board in. The type signature for spawn
indicates that it must be able to mutate commands
, so the commands
argument must be specified as mut
.
const TILE_SIZE: f32 = 40.0;
#[derive(Component)]
struct Board {
size: u8,
}
fn spawn_board(mut commands: Commands) {
let board = Board { size: 4 };
let physical_board_size =
f32::from(board.size) * TILE_SIZE;
commands
.spawn(SpriteBundle {
sprite: Sprite {
custom_size: Some(Vec2::new(
physical_board_size,
physical_board_size,
)),
..default()
},
..default()
})
.insert(board);
}
First we instantiate a new Board
. The Board
struct is defined higher up in our file and has a field called size
that is a u8
. Our board never gets bigger than 4x4, so a u8 (which is an unsigned integer that can hold a maximum value of 255) is fine for our use case.
While our board is a 4x4 grid and we can represent that with integers, when we try to render that our into the game we have to translate the u8
into f32
. For various reasons, f32
is a representation of numbers that Bevy likes so we often will have to convert from what numbers we have into f32
s.
The physical_board_size
here is the board.size
(4) multiplied by the TILE_SIZE
. This gives us how big the tile should be when rendered to the screen, almost like how many pixels it should occupy. If the board is a 4x4 grid of squares and each square is 10 pixels wide, then the board is 40px by 40px total.
We can convert from u8
to f32
using f32::from()
. u8
fits neatly into an f32
and will never fail, so using from
is appropriate here as it is guaranteed to never fail.
commands.spawn
commands.spawn
creates an entity for us and attaches some sprite related components to that entity using a SpriteBundle
. We can continue to add more components using .insert
. In this case, we're adding the Board struct as a Component to the entity we just created. This will allow us to query for the Board in our Systems. To do this, we need to derive Component
on Board
, which allows us to use the Board struct as a component.
SpriteBundle, Sprite, and Vec2
SpriteBundle {
sprite: Sprite {
custom_size: Some(Vec2::new(
physical_board_size,
physical_board_size,
)),
..default()
},
..default()
}
The SpriteBundle
is constructed by filling out the various fields in the struct. One of these is sprite
, which we care about because it is how we'll specify the size of our Sprite
. All of the rest of the fields we don't really care about and can be their default values.
SpriteBundle
implements the Default
trait, which is a standard Rust trait that anyone can implement. This means we can use struct update syntax, which is basically the JavaScript spread operator for objects. Note that there are only two dots, instead of three like in JavaScript.
The default()
method is a convenience function provided by Bevy’s prelude which will call out to SpriteBundle
's and Sprite
’s implementation of the Default
trait to fill out the rest of the fields for us.
With the rest of the fields we don't care about handled, we can focus on creating a new Sprite
. The custom_size
field takes an Option<Vec2>
, which we can create using the physical_board_size
since our board is a square.
Sprite {
custom_size: Some(Vec2::new(
physical_board_size,
physical_board_size,
)),
..default()
}
Bevy provides a couple different Vec*
variants. Since we're building a 2d game we'll be working with Vec2
, a 2-dimensional vector that includes x
and y
values. There are also Vec3
and Vec4
for 3-dimensional and 4-dimensional vectors.
Rust has a Vec
type already, so how are these different? The Vec
type in Rust is like an array in JavaScript. You can add arbitrarily many items to it and it'll just keep working. Bevy's Vec2
, Vec3
, and Vec4
are similar, but restricted to 2, 3 and 4 items in them and optimized for those use cases.
sidenote: Bevy uses the glam crate to power its Vec* types.
Running cargo run
at this point should show a white box in the middle of the screen.