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 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
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.