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);
}