A white board is nice, but we'd like to be able to change what the board looks like, especially as we add tiles on top.
struct Materials {
board: Handle<ColorMaterial>,
}
We'll start off with creating a Materials struct that has one field called board. board is going to be a Handle<ColorMaterial>. A Handle holds an id that points into Bevy's Asset system. In this case, it is an id that points to a ColorMaterial.
We still need to instantiate this struct and insert it into the Asset system to use as a Resource.
To do this we can use the init_resource function. Here, we're using the Materials struct as the type using turbofish syntax. In Rust sometimes functions can handle so many different types that we need to tell it which one we mean. This is one way to do that.
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.init_resource::<Materials>()
.add_startup_system(setup.system())
.add_startup_system(spawn_board.system())
.run()
}
If you're thinking that Default::default() could help us here, you're right. init_resource tries a couple of different approaches to instantiating the type we're asking for, and Default is the last one. In our case we're already seen the default color for a ColorMaterial though: That's what our board already is.
So to let Bevy instantiate our Materials resource for us, and to customize the ColorMaterial values when it does, we can implement the FromWorld trait for Materials.
A trait in Rust is a way of sharing different functionality among different types. In this case, the FromWorld trait requires us to implement a from_world function that accepts the world as an argument and returns a Self. Self in our case is the Materials struct because that's the type we're implementing the trait for.
materials.add takes a ColorMaterial which is a struct that we could construct directly, but instead of doing that we'll use some convenience functions from Color that allows us to set a color as rgb. Luckily Bevy hands us the ability to convert a Color into a ColorMaterial using into().
impl FromWorld for Materials {
fn from_world(world: &mut World) -> Self {
let mut materials = world
.get_resource_mut::<Assets<ColorMaterial>>()
.unwrap();
Materials {
board: materials
.add(Color::rgb(0.7, 0.7, 0.8).into()),
}
}
}
The Assets type is basically a database of assets that tracks changes. We can get a mutable reference to this database by asking the world for it, again using the turbofish syntax. If the world doesn't have what we're asking for, it will return None from this call. We're unwrapping here because if Assets<ColorMaterial> doesn't exist, then we have a major bug in our program and we can't recover from it.
let mut materials = world
.get_resource_mut::<Assets<ColorMaterial>>()
.unwrap();
Once we get a mutable reference to the Assets<ColorMaterial> database, we can add a new Color to the database and use the Handle id that is returned as the value of the board field. We return the constructed Materials struct from the from_world function.
Once we've written the trait implementation, actually using the resource is less code. The spawn_board system needs to ask for the Materials resource (spelled Res) which Bevy will take care of getting to us.
Then to apply the color to the board's sprite, we add the material field and pass in the Handle from our Materials struct. We have to clone this Handle, which is cheap because it's only an id, because Handle doesn't implement the Copy trait and we can not move the value out of a Res.
fn spawn_board(
mut commands: Commands,
materials: Res<Materials>,
) {
let board = Board { size: 4 };
let physical_board_size =
f32::from(board.size) * TILE_SIZE;
commands
.spawn_bundle(SpriteBundle {
material: materials.board.clone(),
sprite: Sprite::new(Vec2::new(
physical_board_size,
physical_board_size,
)),
..Default::default()
})
.insert(board);
}
