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.