关于本教程
本教程是免费且开源的,所有代码均使用 MIT 许可证 - 因此您可以随意使用。我希望您会喜欢本教程,并制作出色的游戏!
如果您喜欢这个教程并希望我继续写作,请考虑支持我的 Patreon。

在上一章中,我们抽象出了房间的布局 - 但保持了房间的实际位置不变:它们始终是矩形,尽管可以通过房间炸裂和圆角来缓解这种情况。本章将增加使用不同形状房间的能力。
首先,我们将创建一个构建器,它接受一组房间作为输入,并将这些房间作为地图上的矩形输出 - 与之前的版本完全一样。我们还将修改 SimpleMapBuilder
和 BspDungeonBuilder
以避免重复功能。
我们将创建一个新文件 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.rs
的 apply_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_map
和 take_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);
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
并享受新的房间类型:
.
如果圆形房间成为一种偶尔出现的特性就好了。因此,我们将修改我们的 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
项目,您将看到类似这样的内容:
.
在 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())
}
...
}
您现在可以获得完整的随机房间创建 - 但偶尔会有圆形房间而不是矩形房间。这为组合增加了一些多样性。
...
本章的源代码可以在这里找到
版权 (C) 2019, Herbert Wolverson。