改进的房间构建
关于本教程
本教程是免费且开源的,所有代码均使用 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); 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
并享受新的房间类型:
.
随机选择形状
如果圆形房间成为一种偶尔出现的特性就好了。因此,我们将修改我们的 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()) } ... }
您现在可以获得完整的随机房间创建 - 但偶尔会有圆形房间而不是矩形房间。这为组合增加了一些多样性。
...
本章的源代码可以在这里找到
使用 WebAssembly 在您的浏览器中运行本章的示例 (需要 WebGL2)
版权 (C) 2019, Herbert Wolverson。