Log in to access Rust Adventure videos!

Lesson Details

We want to show the values from the Points component on each tile, so we are going to start with showing any text at all on each tile.

We’ll create a unit struct, that is: a struct with no fields, to tag the text so we can find it later.

#[derive(Component)]
struct TileText;

We’ll put .with_children in between spawning our new tiles and inserting the Points component.

The tile sprite children will be a Text2dBundle to display the value of each tile. Text uses sections to define updateable areas of content, so we use from_section. We initialize the displayed string to "2" because that will always be the starting value, although later on this value will update so fast we won’t even notice the initial value.

We also insert the TileText label component on the entity we got from spawning the Text2dBundle so we can find it later for all tiles.

.with_children(|child_builder| {
    child_builder
        .spawn(Text2dBundle {
            text: Text::from_section(
                "2",
                TextStyle {
                    font_size: 40.0,
                    color: Color::BLACK,
                    ..default()
                },
            )
            .with_alignment(
                TextAlignment::Center,
            ),
            transform: Transform::from_xyz(
                0.0, 0.0, 1.0,
            ),
            ..default()
        })
        .insert(TileText);
})

The Text for the point values on the tiles is set up, but it isn't showing in the tiles yet. This is because we haven't loaded a font using the asset server.

We'll create a new struct to load a font and keep it around for us.

Create a new struct called FontSpec with a field named family that will hold a Handle to a Bevy Font.

#[derive(Resource)]
struct FontSpec {
    family: Handle<Font>,
}

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 FontSpec struct as the type argument to init_resource using the turbofish syntax. In Rust sometimes functions can handle many different types and we need to tell it which one we want. For example in this case, where we could be instantiating any type of resource.

.add_plugins(DefaultPlugins.set(WindowPlugin {
    primary_window: Some(Window {
        title: "2048".to_string(),
        ..default()
    }),
    ..default()
}))
.init_resource::<FontSpec>()

init_resource tries a couple of different approaches to instantiating the type we're asking for. One of those approaches is calling the FromWorld implementation for the type.

The implementation of the FromWorld trait for FontSpec is how we'll initialize our font loading.

The FromWorld trait requires that we implement the from_world function which accepts an argument of &mut World and returns Self, or FontSpec to be more specific.

Remember that the World is where everything in our game is, so having exclusive access to our world is how we know we can insert new resources into it.

We need access to the AssetServer which controls loading files from the filesystem and we can use get_resource_mut on World to get it.

Finally, we can use the asset_server.load() to load a font file.


impl FromWorld for FontSpec {
    fn from_world(world: &mut World) -> Self {
        let asset_server = world
            .get_resource_mut::<AssetServer>()
            .unwrap();
        FontSpec {
            family: asset_server
                .load("fonts/FiraSans-Bold.ttf"),
        }
    }
}

To make it easy, we'll follow what Bevy does in their own examples and use the Fira Sans font family. We can grab the .ttf from the bevy repo and put it in the assets/fonts/ folder. The assets folder sits at the root of our project and the AssetServer looks here for the fonts folder.

The spawn_tiles system will need to request the FontSpec resource to access it after it is loaded.

fn spawn_tiles(
    mut commands: Commands,
    query_board: Query<&Board>,
    font_spec: Res<FontSpec>,
) {...}

at which point we can use it by cloning the FontSpec.family Handle. We can also remove the ..default() here since we’ve specified all of the fields.

TextStyle {
    font: font_spec
        .family
        .clone(),
    font_size: 40.0,
    color: Color::BLACK,
},

The will display 2 in each box for us.

end result