改进的房间构建


关于本教程

本教程是免费且开源的,所有代码均使用 MIT 许可证 - 因此您可以随意使用。我希望您会喜欢本教程,并制作出色的游戏!

如果您喜欢这个教程并希望我继续写作,请考虑支持我的 Patreon

Hands-On Rust


在上一章中,我们抽象出了房间的布局 - 但保持了房间的实际位置不变:它们始终是矩形,尽管可以通过房间炸裂和圆角来缓解这种情况。本章将增加使用不同形状房间的能力。

矩形房间构建器

首先,我们将创建一个构建器,它接受一组房间作为输入,并将这些房间作为地图上的矩形输出 - 与之前的版本完全一样。我们还将修改 SimpleMapBuilderBspDungeonBuilder 以避免重复功能。

我们将创建一个新文件 map_builders/room_draw.rs

#![allow(unused)]
fn main() {
use super::{MetaMapBuilder, BuilderMap, TileType, Rect};
use rltk::RandomNumberGenerator;

pub struct RoomDrawer {}

impl MetaMapBuilder for RoomDrawer {
    fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data : &mut BuilderMap)  {
        self.build(rng, build_data);
    }
}

impl RoomDrawer {
    #[allow(dead_code)]
    pub fn new() -> Box<RoomDrawer> {
        Box::new(RoomDrawer{})
    }

    fn build(&mut self, _rng : &mut RandomNumberGenerator, build_data : &mut BuilderMap) {
        let rooms : Vec<Rect>;
        if let Some(rooms_builder) = &build_data.rooms {
            rooms = rooms_builder.clone();
        } else {
            panic!("Room Rounding require a builder with room structures");
        }

        for room in rooms.iter() {
            for y in room.y1 +1 ..= room.y2 {
                for x in room.x1 + 1 ..= room.x2 {
                    let idx = build_data.map.xy_idx(x, y);
                    if idx > 0 && idx < ((build_data.map.width * build_data.map.height)-1) as usize {
                        build_data.map.tiles[idx] = TileType::Floor;
                    }
                }
            }
            build_data.take_snapshot();
        }
    }
}
}

这是在 common.rsapply_room_to_map 中找到的相同绘制功能 - 包装在我们前几章中使用的相同的元构建器 (meta-builder) 功能中。这里没有什么太令人惊讶的!

bsp_dungeon.rs 中,只需删除引用 apply_room_to_map 的行。您也可以删除 take_snapshot - 因为我们尚未将任何内容应用到地图:

#![allow(unused)]
fn main() {
if self.is_possible(candidate, &build_data.map, &rooms) {
    rooms.push(candidate);
    self.add_subrects(rect);
}
}

我们还需要更新 is_possible 以检查房间列表,而不是读取实时地图(我们尚未向其中写入任何内容):

#![allow(unused)]
fn main() {
fn is_possible(&self, rect : Rect, build_data : &BuilderMap, rooms: &Vec<Rect>) -> bool {
    let mut expanded = rect;
    expanded.x1 -= 2;
    expanded.x2 += 2;
    expanded.y1 -= 2;
    expanded.y2 += 2;

    let mut can_build = true;

    for r in rooms.iter() {
        if r.intersect(&rect) { can_build = false; }
    }

    for y in expanded.y1 ..= expanded.y2 {
        for x in expanded.x1 ..= expanded.x2 {
            if x > build_data.map.width-2 { can_build = false; }
            if y > build_data.map.height-2 { can_build = false; }
            if x < 1 { can_build = false; }
            if y < 1 { can_build = false; }
            if can_build {
                let idx = build_data.map.xy_idx(x, y);
                if build_data.map.tiles[idx] != TileType::Wall {
                    can_build = false;
                }
            }
        }
    }

    can_build
}
}

同样地,在 simple_map.rs 中 - 只需删除 apply_room_to_maptake_snapshot 调用:

#![allow(unused)]
fn main() {
if ok {
    rooms.push(new_room);
}
}

common.rs 中没有任何地方再使用 apply_room_to_map 了 - 所以我们也可以删除它!

最后,修改 map_builders/mod.rs 中的 random_builder 来测试我们的代码:

#![allow(unused)]
fn main() {
pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain {
    /*let mut builder = BuilderChain::new(new_depth);
    let type_roll = rng.roll_dice(1, 2);
    match type_roll {
        1 => random_room_builder(rng, &mut builder),
        _ => random_shape_builder(rng, &mut builder)
    }

    if rng.roll_dice(1, 3)==1 {
        builder.with(WaveformCollapseBuilder::new());
    }

    if rng.roll_dice(1, 20)==1 {
        builder.with(PrefabBuilder::sectional(prefab_builder::prefab_sections::UNDERGROUND_FORT));
    }

    builder.with(PrefabBuilder::vaults());

    builder*/

    let mut builder = BuilderChain::new(new_depth);
    builder.start_with(SimpleMapBuilder::new());
    builder.with(RoomDrawer::new());
    builder.with(RoomSorter::new(RoomSort::LEFTMOST));
    builder.with(BspCorridors::new());
    builder.with(RoomBasedSpawner::new());
    builder.with(RoomBasedStairs::new());
    builder.with(RoomBasedStartingPosition::new());
    builder
}
}

如果您 cargo run 项目,您将看到我们的简单地图构建器运行 - 就像以前一样。

圆形房间

简单地将绘制代码移出算法可以使事情更清晰,但并没有为我们带来任何新的东西。因此,我们将研究为房间添加一些形状选项。我们将首先将绘制代码移出主循环并放入其自己的函数中。修改 room_draw.rs 如下:

#![allow(unused)]
fn main() {
fn rectangle(&mut self, build_data : &mut BuilderMap, room : &Rect) {
    for y in room.y1 +1 ..= room.y2 {
        for x in room.x1 + 1 ..= room.x2 {
            let idx = build_data.map.xy_idx(x, y);
            if idx > 0 && idx < ((build_data.map.width * build_data.map.height)-1) as usize {
                build_data.map.tiles[idx] = TileType::Floor;
            }
        }
    }
}

fn build(&mut self, _rng : &mut RandomNumberGenerator, build_data : &mut BuilderMap) {
    let rooms : Vec<Rect>;
    if let Some(rooms_builder) = &build_data.rooms {
        rooms = rooms_builder.clone();
    } else {
        panic!("Room Drawing require a builder with room structures");
    }

    for room in rooms.iter() {
        self.rectangle(build_data, room);
        build_data.take_snapshot();
    }
}
}

再一次,如果您想测试它 - cargo run 将给您与上次类似的结果。 让我们添加第二个房间形状 - 圆形房间:

#![allow(unused)]
fn main() {
fn circle(&mut self, build_data : &mut BuilderMap, room : &Rect) {
    let radius = i32::min(room.x2 - room.x1, room.y2 - room.y1) as f32 / 2.0;
    let center = room.center();
    let center_pt = rltk::Point::new(center.0, center.1);
    for y in room.y1 ..= room.y2 {
        for x in room.x1 ..= room.x2 {
            let idx = build_data.map.xy_idx(x, y);
            let distance = rltk::DistanceAlg::Pythagoras.distance2d(center_pt, rltk::Point::new(x, y));
            if idx > 0
                && idx < ((build_data.map.width * build_data.map.height)-1) as usize
                && distance <= radius
            {
                build_data.map.tiles[idx] = TileType::Floor;
            }
        }
    }
}
}

现在将您对 rectangle 的调用替换为 circle,输入 cargo run 并享受新的房间类型:

Screenshot.

随机选择形状

如果圆形房间成为一种偶尔出现的特性就好了。因此,我们将修改我们的 build 函数,使大约四分之一的房间是圆形的:

#![allow(unused)]
fn main() {
fn build(&mut self, rng : &mut RandomNumberGenerator, build_data : &mut BuilderMap) {
    let rooms : Vec<Rect>;
    if let Some(rooms_builder) = &build_data.rooms {
        rooms = rooms_builder.clone();
    } else {
        panic!("Room Drawing require a builder with room structures");
    }

    for room in rooms.iter() {
        let room_type = rng.roll_dice(1,4);
        match room_type {
            1 => self.circle(build_data, room),
            _ => self.rectangle(build_data, room)
        }
        build_data.take_snapshot();
    }
}
}

如果您现在 cargo run 项目,您将看到类似这样的内容:

Screenshot.

恢复随机性

map_builders/mod.rs 中,取消注释代码并删除测试工具:

#![allow(unused)]
fn main() {
pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain {
    let mut builder = BuilderChain::new(new_depth);
    let type_roll = rng.roll_dice(1, 2);
    match type_roll {
        1 => random_room_builder(rng, &mut builder),
        _ => random_shape_builder(rng, &mut builder)
    }

    if rng.roll_dice(1, 3)==1 {
        builder.with(WaveformCollapseBuilder::new());
    }

    if rng.roll_dice(1, 20)==1 {
        builder.with(PrefabBuilder::sectional(prefab_builder::prefab_sections::UNDERGROUND_FORT));
    }

    builder.with(PrefabBuilder::vaults());

    builder
}
}

random_room_builder 中,我们添加房间绘制:

#![allow(unused)]
fn main() {
...
let sort_roll = rng.roll_dice(1, 5);
match sort_roll {
    1 => builder.with(RoomSorter::new(RoomSort::LEFTMOST)),
    2 => builder.with(RoomSorter::new(RoomSort::RIGHTMOST)),
    3 => builder.with(RoomSorter::new(RoomSort::TOPMOST)),
    4 => builder.with(RoomSorter::new(RoomSort::BOTTOMMOST)),
    _ => builder.with(RoomSorter::new(RoomSort::CENTRAL)),
}

builder.with(RoomDrawer::new());

let corridor_roll = rng.roll_dice(1, 2);
match corridor_roll {
    1 => builder.with(DoglegCorridors::new()),
    _ => builder.with(BspCorridors::new())
}
...
}

您现在可以获得完整的随机房间创建 - 但偶尔会有圆形房间而不是矩形房间。这为组合增加了一些多样性。

...

本章的源代码可以在这里找到

使用 WebAssembly 在您的浏览器中运行本章的示例 (需要 WebGL2)

版权 (C) 2019, Herbert Wolverson。