Home Posts Post Search Tag Search

Hands on Rust - 12 - Map Themes
Published on: 2026-07-21 Tags: Systems, Room, Rust, Hands on Rust, Rusty Roguelike, Map Builder

12. Map Themes

There is even more that we can do with Themes for how the dungeon looks. We might want a forest, a mine, or much more.




Theming Your Dungeon

Okay so let’s get started we will need to add an new trait to the map_builder/mod.rs:

pub trait MapTheme: Sync + Send {
    fn tile_to_render(&self, tile_type: TileType) -> FontCharType;
}

So the actual function seems very normal take a tiletype and give you a font character. We need to talk about the _Sync + Send. This is to help the Rust compiler know when you can safely access something across threads:

• If an object implements Sync, you can safely access it from multiple threads.
• If an object implements Send, you can safely share it between threads.

Implement a Dungeon Theme

Okay so let’s create an other file map_builder/themes.rs this will hold the functions for dealing with different themes.

use crate::prelude::*;

pub struct DungeonTheme {}

impl DungeonTheme {
    pub fn new() -> Box<dyn MapTheme> {
        Box::new(Self{})
    }
}

impl MapTheme for DungeonTheme {
    fn tile_to_render(&self, tile_type: TileType) -> FontCharType {
        match tile_type {
            TileType::Floor => to_cp437('.'),
            TileType::Wall => to_cp437('#')
        }
    }
}

This is the normal dungeon theme. We can make it look different for a different style.


Build a Forest

Let’s create an other struct and then an other implementation.

pub struct ForestTheme {}

impl ForestTheme {
    pub fn new() -> Box<dyn MapTheme> {
        Box::new(Self {})
    }
}

impl MapTheme for ForestTheme {
    fn tile_to_render(&self, tile_type: TileType) -> FontCharType {
        match tile_type {
            TileType::Floor => to_cp437(';'),
            TileType::Wall => to_cp437('"'),
        }
    }
}

Look at that we now have a way to turn a wall into a different character, you could then try and look at the font sheet and match the “ and ; to see what they will look like.




Rendering with Themes

Okay so now that we have a theme for 2 dungeon types we want to add them to the mod.rs so that we can add the them to the struct for MapBuilder.

pub struct MapBuilder {
    pub map: Map,
    pub rooms: Vec<Rect>,
    pub player_start: Point,
    pub monster_spawns: Vec<Point>,
    pub amulet_start: Point,
    pub theme: Box<dyn MapTheme>,
}

Okay so we will now get a bunch of errors because we have a field for the struct that is not being used or set in all the other code we wrote for the MapBuilder we will need to add theme: super::themes::DungeonTheme::new() to each of those files.


Picking a Theme

Right now we have the standard theme being set for each dungeon type as we are setting a standard theme when we create a dungeon map. Let’s add some functionality to let if be random. Go back to the mod.rs and add the following code.

mod themes;
use themes::*;

...

impl MapBuilder {
    pub fn new(rng: &mut RandomNumberGenerator) -> Self {
        let mut architect: Box<dyn MapArchitect> = match rng.range(0, 3) {
            0 => Box::new(DrunkardsWalkArchitect {}),
            1 => Box::new(RoomsArchitect {}),
            _ => Box::new(CellularAutomataArchitect {}),
        };
        let mut mb = architect.new(rng);
        apply_prefab(&mut mb, rng);

        mb.theme = match rng.range(0, 2) {
            0 => DungeonTheme::new(),
            _ => ForestTheme::new()
        };
    }
}

We now have the theme but we are not setting the values, so we need to add the theme to the game resources. So head back to the main.rs and add them to the game-start and game-reset.

impl State {
    fn new() -> Self {
        let mut ecs = World::default();
        let mut resources = Resources::default();
        let mut rng = RandomNumberGenerator::new();
        let map_builder = MapBuilder::new(&mut rng);
        spawn_player(&mut ecs, map_builder.player_start);
        spawn_amulet_of_yala(&mut ecs, map_builder.amulet_start);
        map_builder
            .monster_spawns
            .iter()
            .for_each(|pos| spawn_monster(&mut ecs, &mut rng, *pos));
        map_builder
            .rooms
            .iter()
            .skip(1)
            .map(|r| r.center())
            .for_each(|pos| spawn_monster(&mut ecs, &mut rng, pos));
        resources.insert(map_builder.map);
        resources.insert(Camera::new(map_builder.player_start));
        resources.insert(TurnState::AwaitingInput);
        resources.insert(map_builder.theme);
    }
    ...
}

Make sure you added this to the reset as well. Now we just need to add in the render part so we can see the new values.


Render the Map with Your Theme

We need to head to the map_render.rs so we can start to leverage the new theme we have built in.

use crate::prelude::*;

#[system]
#[read_component(FieldOfView)]
#[read_component(Player)]
pub fn map_render(
    ecs: &SubWorld,
    #[resource] map: &Map,
    #[resource] camera: &Camera,
    #[resource] theme: &Box<dyn MapTheme>,
) {
    let mut fov = <&FieldOfView>::query().filter(component::<Player>());
    let mut draw_batch = DrawBatch::new();

    let player_fov = fov.iter(ecs).nth(0).unwrap();

    draw_batch.target(0);
    for y in camera.top_y..=camera.bottom_y {
        for x in camera.left_x..camera.right_x {
            let pt = Point::new(x, y);
            let offset = Point::new(camera.left_x, camera.top_y);
            let idx = map_idx(x, y);
            if map.in_bounds(pt)
                && (player_fov.visible_tiles.contains(&pt) | map.revealed_tiles[idx])
            {
                let tint = if player_fov.visible_tiles.contains(&pt) {
                    WHITE
                } else {
                    DARK_GRAY
                };
                let glyph = theme.tile_to_render(map.tiles[idx]);
                draw_batch.set(pt - offset, ColorPair::new(tint, BLACK), glyph);
            }
        }
    }
    draw_batch.submit(0).expect("Batch error");
}

Okay so we now have a 50% chance to be in the forest. Give it a play and see what happens.




Unleashing Your Imagination

Okay so take some time and build a few more themes, check out the given font sheet and see if there is anything else within the sheet to use. Then you can also head to OpenGameArt to see if there is an other theme you want to use.




Wrap-Up

Okay so next time we can build a set of items for the dungeon. Great job so far.