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.
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_startup_system(setup.system())
.add_startup_system(spawn_board.system())
.run()
}
We'll create a function that takes a mutable argument of type Commands so that we can spawn a board in. The type signature for spawn_bundle indicates that it must be able to mutate commands, so the commands argument must be mut.
const TILE_SIZE: f32 = 40.0;
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_bundle(SpriteBundle {
sprite: Sprite::new(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 into f32. For various reasons, f32 is a representation Bevy likes so we often will have to convert from what we have into f32s.
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_bundle
commands.spawn_bundle creates an entity for us and attaches some sprite related components to that entity. 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.
SpriteBundle, Sprite, and Vec2
SpriteBundle {
sprite: Sprite::new(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::default() method will call out to SpriteBundle'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. Sprite::new takes a Vec2, which we can create using the physical_board_size since our board is a square.
Sprite::new(Vec2::new(
physical_board_size,
physical_board_size,
)
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.
