关于本教程
本教程是免费且开源的,所有代码均使用 MIT 许可证 - 因此您可以随意使用。我希望您会喜欢本教程,并制作出色的游戏!
如果您喜欢这个教程并希望我继续写作,请考虑支持我的 Patreon 。
到目前为止,我们已经将地图尺寸牢牢地绑定到终端分辨率。您有一个 80x50 的屏幕,并使用几行来显示用户界面 - 所以我们制作的所有东西都是 80 个瓦片宽和 43 个瓦片高。正如您在之前的章节中看到的,您可以使用 3,440 个瓦片做 很多 事情 - 但有时您想要更多(有时您想要更少)。您可能还想要一个大的、开放的世界设定 - 但我们现在还不打算涉及那里!本章将首先将相机 与地图 解耦,然后使地图尺寸和屏幕尺寸能够不同。用户界面大小调整这个难题将留待未来开发。
游戏中常见的抽象概念是将您正在查看的内容 (地图和实体)与您如何 查看它(相机)分离开来。相机通常跟随您勇敢的冒险家在地图上移动,从他们的 角度向您展示世界。在 3D 游戏中,相机可能非常复杂;在自上而下的 Roguelike 游戏中(从上方查看地图),它通常将视图中心对准玩家的 @
。
可以预见的是,我们将从创建一个新文件开始:camera.rs
。为了启用它,在 main.rs
的顶部附近添加 pub mod camera
(与其他模块访问方式相同)。
我们将从创建一个函数 render_camera
开始,并进行一些我们需要的计算:
#![allow(unused)]
fn main () {
use specs::prelude::*;
use super::{Map,TileType,Position,Renderable,Hidden};
use rltk::{Point, Rltk, RGB};
const SHOW_BOUNDARIES : bool = true ;
pub fn render_camera (ecs: &World, ctx : &mut Rltk) {
let map = ecs.fetch::<Map>();
let player_pos = ecs.fetch::<Point>();
let (x_chars, y_chars) = ctx.get_char_size();
let center_x = (x_chars / 2 ) as i32 ;
let center_y = (y_chars / 2 ) as i32 ;
let min_x = player_pos.x - center_x;
let max_x = min_x + x_chars as i32 ;
let min_y = player_pos.y - center_y;
let max_y = min_.y + y_chars as i32 ;
...
}
我已将其分解为几个步骤,以清楚地说明正在发生的事情:
我们创建一个常量 SHOW_BOUNDARIES
。如果为 true,我们将为超出边界的瓦片渲染一个标记,以便我们知道地图的边缘在哪里。大多数时候,这将是 false
(不需要玩家获得该信息),但它对于调试非常方便。
我们首先从 ECS World 中检索地图。
然后,我们从 ECS World 中检索玩家的位置。
我们向 RLTK 请求当前的控制台尺寸,以字符空间为单位(因此对于 8x8 字体,为 80x50
)。
我们计算控制台的中心。
我们将 min_x
设置为最左边的瓦片,相对于玩家 。所以玩家的 x
位置,减去控制台的中心。这将使 x
轴以玩家为中心。
我们将 max_x
设置为 min_x
加上控制台宽度 - 再次,确保玩家居中。
我们对 min_y
和 max_y
执行相同的操作。
所以我们已经确定了相机在世界空间 中的位置 - 即地图本身的坐标。我们还确定了使用我们的相机视图 ,这应该是渲染区域的中心。
现在我们将渲染实际的地图:
#![allow(unused)]
fn main () {
let map_width = map.width-1 ;
let map_height = map.height-1 ;
let mut y = 0 ;
for ty in min_y .. max_y {
let mut x = 0 ;
for tx in min_x .. max_x {
if tx > 0 && tx < map_width && ty > 0 && ty < map_height {
let idx = map.xy_idx(tx, ty);
if map.revealed_tiles[idx] {
let (glyph, fg, bg) = get_tile_glyph(idx, &*map);
ctx.set(x, y, fg, bg, glyph);
}
} else if SHOW_BOUNDARIES {
ctx.set(x, y, RGB::named(rltk::GRAY), RGB::named(rltk::BLACK), rltk::to_cp437('·' ));
}
x += 1 ;
}
y += 1 ;
}
}
这与我们旧的 draw_map
代码类似,但稍微复杂一些。让我们逐步了解它:
我们将 y
设置为 0;我们使用 x
和 y
来表示实际的屏幕 坐标。
我们从 min_y
到 max_y
循环 ty
。我们使用 tx
和 ty
表示地图 坐标 - 或“瓦片空间”坐标(因此为 t
)。
我们将 x
设置为零,因为我们要在屏幕上开始新的一行。
我们在变量 tx
中从 min_x
到 max_x
循环 - 因此我们在 tx
中覆盖了可见的瓦片空间 。
我们进行裁剪 检查。我们检查 tx
和 ty
是否真的在地图 边界内。玩家很可能会访问地图的边缘,您不希望因为他们可以看到不在地图区域内的瓦片而崩溃!
我们计算 tx/ty
位置的 idx
(索引),告诉我们屏幕上的这个位置在地图上的哪里。
如果它是已显示的,我们为这个索引调用神秘的 get_tile_glyph
函数(稍后会详细介绍),并将结果设置在屏幕上。
如果瓦片超出地图范围且 SHOW_BOUNDARIES
为 true
- 我们绘制一个点。
无论是否裁剪,我们都将 x
加 1 - 我们正在移动到下一列。
我们将 y
加一,因为我们现在正在向下移动屏幕。
我们渲染了一张地图!
这实际上非常简单 - 我们渲染的实际上是一个窗口,它查看地图的一部分,而不是整个地图 - 并将窗口中心对准玩家。
接下来,我们需要渲染我们的实体:
#![allow(unused)]
fn main () {
let positions = ecs.read_storage::<Position>();
let renderables = ecs.read_storage::<Renderable>();
let hidden = ecs.read_storage::<Hidden>();
let map = ecs.fetch::<Map>();
let mut data = (&positions, &renderables, !&hidden).join().collect::<Vec <_>>();
data.sort_by(|&a, &b| b.1 .render_order.cmp(&a.1 .render_order) );
for (pos, render, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] {
let entity_screen_x = pos.x - min_x;
let entity_screen_y = pos.y - min_y;
if entity_screen_x > 0 && entity_screen_x < map_width && entity_screen_y > 0 && entity_screen_y < map_height {
ctx.set(entity_screen_x, entity_screen_y, render.fg, render.bg, render.glyph);
}
}
}
}
如果这看起来很眼熟,那是因为它与曾经存在于 main.rs
中的渲染代码相同 。有两个主要的区别:我们从 x
和 y
坐标中减去 min_x
和 min_y
,以使实体与我们的相机视图对齐。我们还对坐标执行裁剪 - 我们不会尝试渲染任何不在屏幕上的东西。
我们之前提到了 get_tile_glyph
,所以这里是它:
#![allow(unused)]
fn main () {
fn get_tile_glyph (idx: usize , map : &Map) -> (rltk::FontCharType, RGB, RGB) {
let glyph;
let mut fg;
let mut bg = RGB::from_f32(0 ., 0 ., 0 .);
match map.tiles[idx] {
TileType::Floor => {
glyph = rltk::to_cp437('.' );
fg = RGB::from_f32(0.0 , 0.5 , 0.5 );
}
TileType::Wall => {
let x = idx as i32 % map.width;
let y = idx as i32 / map.width;
glyph = wall_glyph(&*map, x, y);
fg = RGB::from_f32(0 ., 1.0 , 0 .);
}
TileType::DownStairs => {
glyph = rltk::to_cp437('>' );
fg = RGB::from_f32(0 ., 1.0 , 1.0 );
}
}
if map.bloodstains.contains(&idx) { bg = RGB::from_f32(0.75 , 0 ., 0 .); }
if !map.visible_tiles[idx] {
fg = fg.to_greyscale();
bg = RGB::from_f32(0 ., 0 ., 0 .);
}
(glyph, fg, bg)
}
}
这与我们很久以前编写的 draw_map
中的代码非常相似,但它不是绘制到地图,而是返回字形、前景色和背景色。它仍然处理血迹,灰化您看不到的区域,并为漂亮的墙壁调用 wall_glyph
。我们只是从 map.rs
中复制了 wall_glyph
:
#![allow(unused)]
fn main () {
fn wall_glyph (map : &Map, x: i32 , y:i32 ) -> rltk::FontCharType {
if x < 1 || x > map.width-2 || y < 1 || y > map.height-2 as i32 { return 35 ; }
let mut mask : u8 = 0 ;
if is_revealed_and_wall(map, x, y - 1 ) { mask +=1 ; }
if is_revealed_and_wall(map, x, y + 1 ) { mask +=2 ; }
if is_revealed_and_wall(map, x - 1 , y) { mask +=4 ; }
if is_revealed_and_wall(map, x + 1 , y) { mask +=8 ; }
match mask {
0 => { 9 }
1 => { 186 }
2 => { 186 }
3 => { 186 }
4 => { 205 }
5 => { 188 }
6 => { 187 }
7 => { 185 }
8 => { 205 }
9 => { 200 }
10 => { 201 }
11 => { 204 }
12 => { 205 }
13 => { 202 }
14 => { 203 }
15 => { 206 }
_ => { 35 }
}
}
fn is_revealed_and_wall (map: &Map, x: i32 , y: i32 ) -> bool {
let idx = map.xy_idx(x, y);
map.tiles[idx] == TileType::Wall && map.revealed_tiles[idx]
}
}
最后,在 main.rs
中找到以下代码:
#![allow(unused)]
fn main () {
...
RunState::GameOver{..} => {}
_ => {
draw_map(&self .ecs.fetch::<Map>(), ctx);
let positions = self .ecs.read_storage::<Position>();
let renderables = self .ecs.read_storage::<Renderable>();
let hidden = self .ecs.read_storage::<Hidden>();
let map = self .ecs.fetch::<Map>();
let mut data = (&positions, &renderables, !&hidden).join().collect::<Vec <_>>();
data.sort_by(|&a, &b| b.1 .render_order.cmp(&a.1 .render_order) );
for (pos, render, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] { ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) }
}
gui::draw_ui(&self .ecs, ctx);
}
...
}
我们现在可以用一段更短 的代码替换它:
#![allow(unused)]
fn main () {
RunState::GameOver{..} => {}
_ => {
camera::render_camera(&self .ecs, ctx);
gui::draw_ui(&self .ecs, ctx);
}
}
如果您现在 cargo run
项目,您将看到我们仍然可以玩 - 并且相机以玩家为中心:
。
如果您玩一会儿,您可能会注意到工具提示不起作用(它们仍然绑定到地图坐标)。我们应该修复它!首先,屏幕边界显然是我们不仅在绘制代码中需要的东西,所以让我们把它分解成 camera.rs
中的一个单独的函数:
#![allow(unused)]
fn main () {
pub fn get_screen_bounds (ecs: &World, ctx : &mut Rltk) -> (i32 , i32 , i32 , i32 ) {
let player_pos = ecs.fetch::<Point>();
let (x_chars, y_chars) = ctx.get_char_size();
let center_x = (x_chars / 2 ) as i32 ;
let center_y = (y_chars / 2 ) as i32 ;
let min_x = player_pos.x - center_x;
let max_x = min_x + x_chars as i32 ;
let min_y = player_pos.y - center_y;
let max_y = min_y + y_chars as i32 ;
(min_x, max_x, min_y, max_y)
}
pub fn render_camera (ecs: &World, ctx : &mut Rltk) {
let map = ecs.fetch::<Map>();
let (min_x, max_x, min_y, max_y) = get_screen_bounds(ecs, ctx);
}
它是 render_camera
中的相同 代码 - 只是移动到一个函数中。我们还扩展了 render_camera
以使用该函数,而不是重复我们自己。现在我们可以进入 gui.rs
并轻松编辑 draw_tooltips
以使用相机位置:
#![allow(unused)]
fn main () {
fn draw_tooltips (ecs: &World, ctx : &mut Rltk) {
let (min_x, _max_x, min_y, _max_y) = camera::get_screen_bounds(ecs, ctx);
let map = ecs.fetch::<Map>();
let names = ecs.read_storage::<Name>();
let positions = ecs.read_storage::<Position>();
let hidden = ecs.read_storage::<Hidden>();
let mouse_pos = ctx.mouse_pos();
let mut mouse_map_pos = mouse_pos;
mouse_map_pos.0 += min_x;
mouse_map_pos.1 += min_y;
if mouse_map_pos.0 >= map.width-1 || mouse_map_pos.1 >= map.height-1 || mouse_map_pos.0 < 1 || mouse_map_pos.1 < 1
{
return ;
}
if !map.visible_tiles[map.xy_idx(mouse_map_pos.0 , mouse_map_pos.1 )] { return ; }
let mut tooltip : Vec <String > = Vec ::new();
for (name, position, _hidden) in (&names, &positions, !&hidden).join() {
if position.x == mouse_map_pos.0 && position.y == mouse_map_pos.1 {
tooltip.push(name.name.to_string());
}
}
...
}
所以我们的更改是:
在开始时,我们使用 camera::get_screen_bounds
检索屏幕边界。我们不打算使用 max
变量,所以我们在它们前面加上下划线,让 Rust 知道我们有意忽略它们。
在获取 mouse_pos
后,我们创建一个新的 mouse_map_pos
变量。它等于 mouse_pos
,但我们添加 了 min_x
和 min_y
值 - 将其偏移以匹配可见坐标。
我们扩展了裁剪以检查所有方向,因此当您查看实际地图之外的区域时,工具提示不会使游戏崩溃,因为视口位于地图的极端末端。
我们对 position
的比较现在与 mouse_map_pos
而不是 mouse_pos
进行比较。
就这样 - 其余的可以保持不变。
如果您现在 cargo run
,工具提示将起作用:
。
如果您玩一会儿,您还会注意到,如果您尝试使用火球 或类似效果 - 目标系统完全失灵了。它仍然引用屏幕/地图位置,因为它们曾经直接链接。所以您看到了可用 的瓦片,但它们的位置完全错误!我们也应该修复它。
在 gui.rs
中,我们将编辑函数 ranged_target
:
#![allow(unused)]
fn main () {
pub fn ranged_target (gs : &mut State, ctx : &mut Rltk, range : i32 ) -> (ItemMenuResult, Option <Point>) {
let (min_x, max_x, min_y, max_y) = camera::get_screen_bounds(&gs.ecs, ctx);
let player_entity = gs.ecs.fetch::<Entity>();
let player_pos = gs.ecs.fetch::<Point>();
let viewsheds = gs.ecs.read_storage::<Viewshed>();
ctx.print_color(5 , 0 , RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Select Target:" );
let mut available_cells = Vec ::new();
let visible = viewsheds.get(*player_entity);
if let Some (visible) = visible {
for idx in visible.visible_tiles.iter() {
let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx);
if distance <= range as f32 {
let screen_x = idx.x - min_x;
let screen_y = idx.y - min_y;
if screen_x > 1 && screen_x < (max_x - min_x)-1 && screen_y > 1 && screen_y < (max_y - min_y)-1 {
ctx.set_bg(screen_x, screen_y, RGB::named(rltk::BLUE));
available_cells.push(idx);
}
}
}
} else {
return (ItemMenuResult::Cancel, None );
}
let mouse_pos = ctx.mouse_pos();
let mut mouse_map_pos = mouse_pos;
mouse_map_pos.0 += min_x;
mouse_map_pos.1 += min_y;
let mut valid_target = false ;
for idx in available_cells.iter() { if idx.x == mouse_map_pos.0 && idx.y == mouse_map_pos.1 { valid_target = true ; } }
if valid_target {
ctx.set_bg(mouse_pos.0 , mouse_pos.1 , RGB::named(rltk::CYAN));
if ctx.left_click {
return (ItemMenuResult::Selected, Some (Point::new(mouse_map_pos.0 , mouse_map_pos.1 )));
}
} else {
ctx.set_bg(mouse_pos.0 , mouse_pos.1 , RGB::named(rltk::RED));
if ctx.left_click {
return (ItemMenuResult::Cancel, None );
}
}
(ItemMenuResult::NoResponse, None )
}
}
这基本上是我们以前拥有的,但做了一些更改:
我们在开始时获取边界,再次使用 camera::get_screen_bounds
。
在我们的可见目标瓦片部分中,我们通过获取地图索引并添加 我们的 min_x
和 min_y
值来计算 screen_x
和 screen_y
。然后,我们检查它是否在屏幕上,然后在这些位置绘制目标高亮。
在计算 mouse_pos
后,我们使用相同的 mouse_map_pos
计算。
然后,我们在检查目标是否在鼠标下方或被选中时引用 mouse_map_pos
。
如果您现在 cargo run
,目标将起作用:
。
现在我们的地图没有直接链接到我们的屏幕,我们可以拥有任何我们想要的尺寸的地图!温馨提示:如果您使用巨大 的地图,您的玩家将需要很长时间 才能探索完所有地图 - 并且越来越难以确保所有地图都足够有趣,值得访问。
让我们从最简单的例子开始:全局更改地图的大小。转到 map.rs
,找到常量 MAPWIDTH
、MAPHEIGHT
和 MAPCOUNT
。让我们将它们更改为方形地图:
#![allow(unused)]
fn main () {
pub const MAPWIDTH : usize = 64 ;
pub const MAPHEIGHT : usize = 64 ;
pub const MAPCOUNT : usize = MAPHEIGHT * MAPWIDTH;
}
如果您 cargo run
该项目,它应该可以工作 - 我们在整个程序中都很好地使用了 map.width
/map.height
或这些常量。这些算法运行,并尝试为您制作一张地图。这是我们的玩家在 64x64 地图上漫游 - 请注意地图的侧面是如何显示为超出边界的:
。
现在从 map.rs
中删除 这三个常量,并观察您的 IDE 将世界涂成红色。在我们开始修复问题之前,我们将添加更多的红色:
#![allow(unused)]
fn main () {
pub fn new (new_depth : i32 , width: i32 , height: i32 ) -> Map {
Map{
tiles : vec! [TileType::Wall; MAPCOUNT],
width,
height,
revealed_tiles : vec! [false ; MAPCOUNT],
visible_tiles : vec! [false ; MAPCOUNT],
blocked : vec! [false ; MAPCOUNT],
tile_content : vec! [Vec ::new(); MAPCOUNT],
depth: new_depth,
bloodstains: HashSet::new(),
view_blocked : HashSet::new()
}
}
}
现在创建地图需要您指定大小和深度。我们可以通过再次更改构造函数以在创建各种向量时使用 指定的大小,来开始修复一些错误:
#![allow(unused)]
fn main () {
pub fn new (new_depth : i32 , width: i32 , height: i32 ) -> Map {
let map_tile_count = (width*height) as usize ;
Map{
tiles : vec! [TileType::Wall; map_tile_count],
width,
height,
revealed_tiles : vec! [false ; map_tile_count],
visible_tiles : vec! [false ; map_tile_count],
blocked : vec! [false ; map_tile_count],
tile_content : vec! [Vec ::new(); map_tile_count],
depth: new_depth,
bloodstains: HashSet::new(),
view_blocked : HashSet::new()
}
}
}
map.rs
在 draw_map
中也有一个错误。幸运的是,这是一个简单的修复:
#![allow(unused)]
fn main () {
...
x += 1 ;
if x > (map.width * map.height) as i32 -1 {
x = 0 ;
y += 1 ;
}
...
}
spawner.rs
也是一个同样容易修复的问题。从开头的 use
导入列表中删除 map::MAPWIDTH
,并找到 spawn_entity
函数。我们可以直接从 ECS 获取地图宽度:
#![allow(unused)]
fn main () {
pub fn spawn_entity (ecs: &mut World, spawn : &(&usize , &String )) {
let map = ecs.fetch::<Map>();
let width = map.width as usize ;
let x = (*spawn.0 % width) as i32 ;
let y = (*spawn.0 / width) as i32 ;
std::mem::drop (map);
...
}
saveload_system.rs
中的问题也很容易修复。在第 102 行左右,您可以将 MAPCOUNT
替换为 (worldmap.width * worldmap.height) as usize
:
#![allow(unused)]
fn main () {
...
let mut deleteme : Option <Entity> = None ;
{
let entities = ecs.entities();
let helper = ecs.read_storage::<SerializationHelper>();
let player = ecs.read_storage::<Player>();
let position = ecs.read_storage::<Position>();
for (e,h) in (&entities, &helper).join() {
let mut worldmap = ecs.write_resource::<super::map::Map>();
*worldmap = h.map.clone();
worldmap.tile_content = vec! [Vec ::new(); (worldmap.height * worldmap.width) as usize ];
deleteme = Some (e);
}
...
}
main.rs
也需要一些帮助。在 tick
中,MagicMapReveal
代码是一个简单的修复:
#![allow(unused)]
fn main () {
RunState::MagicMapReveal{row} => {
let mut map = self .ecs.fetch_mut::<Map>();
for x in 0 ..map.width {
let idx = map.xy_idx(x as i32 ,row);
map.revealed_tiles[idx] = true ;
}
if row == map.height-1 {
newrunstate = RunState::MonsterTurn;
} else {
newrunstate = RunState::MagicMapReveal{ row: row+1 };
}
}
}
在第 451 行附近,我们也在用 map::new(1)
创建地图。我们想在这里引入地图尺寸,所以我们使用 map::new(1, 64, 64)
(尺寸并不重要,因为我们无论如何都会用来自构建器的地图替换它)。
打开 player.rs
,您会发现我们犯了一个真正的编程罪过。我们硬编码了 79
和 49
作为玩家移动的地图边界!让我们修复它:
#![allow(unused)]
fn main () {
if !map.blocked[destination_idx] {
pos.x = min(map.width-1 , max(0 , pos.x + delta_x));
pos.y = min(map.height-1 , max(0 , pos.y + delta_y));
}
最后,展开我们的 map_builders
文件夹会显示一些错误。我们将在修复它们之前再引入几个错误!在 map_builders/mod.rs
中,我们将存储请求的地图大小:
#![allow(unused)]
fn main () {
pub struct BuilderMap {
pub spawn_list : Vec <(usize , String )>,
pub map : Map,
pub starting_position : Option <Position>,
pub rooms: Option <Vec <Rect>>,
pub corridors: Option <Vec <Vec <usize >>>,
pub history : Vec <Map>,
pub width: i32 ,
pub height: i32
}
}
然后我们将更新构造函数以使用它:
#![allow(unused)]
fn main () {
impl BuilderChain {
pub fn new (new_depth : i32 , width: i32 , height: i32 ) -> BuilderChain {
BuilderChain{
starter: None ,
builders: Vec ::new(),
build_data : BuilderMap {
spawn_list: Vec ::new(),
map: Map::new(new_depth, width, height),
starting_position: None ,
rooms: None ,
corridors: None ,
history : Vec ::new(),
width,
height
}
}
}
}
我们还需要调整 random_builder
的签名以接受地图大小:
#![allow(unused)]
fn main () {
pub fn random_builder (new_depth: i32 , rng: &mut rltk::RandomNumberGenerator, width: i32 , height: i32 ) -> BuilderChain {
let mut builder = BuilderChain::new(new_depth, width, height);
...
}
我们还将访问 map_builders/waveform_collapse/mod.rs
并进行一些修复。基本上,我们对 Map::new
的所有引用都需要包括新的大小。
最后,回到 main.rs
,在第 370 行左右,您会找到我们对 random_builder
的调用。我们需要向其中添加宽度和高度;现在,我们将使用 64x64:
#![allow(unused)]
fn main () {
let mut builder = map_builders::random_builder(new_depth, &mut rng, 64 , 64 );
}
就是这样!如果您现在 cargo run
该项目,您可以漫游 64x64 的地图:
。
如果您将该行更改为不同的尺寸,则可以漫游巨大 的地图:
#![allow(unused)]
fn main () {
let mut builder = map_builders::random_builder(new_depth, &mut rng, 128 , 128 );
}
瞧 - 您正在漫游一张巨大的地图!巨大地图的明显缺点,以及滚动一个大部分开放区域的缺点是,有时它可能真的 很难生存:
。
如果您保留巨大的地图,打开 main.rs
并将 const SHOW_MAPGEN_VISUALIZER : bool = false;
设置为 true
- 恭喜您,您刚刚使游戏崩溃了!这是因为我们从未调整我们用于验证地图创建的 draw_map
函数来处理原始尺寸以外的任何尺寸的地图。哎呀。这确实提出了一个问题:在 ASCII 终端上,我们不能简单地渲染整个地图并将其缩小以适应。因此,我们将满足于渲染地图的一部分。
我们将在 camera.rs
中添加一个新函数:
#![allow(unused)]
fn main () {
pub fn render_debug_map (map : &Map, ctx : &mut Rltk) {
let player_pos = Point::new(map.width / 2 , map.height / 2 );
let (x_chars, y_chars) = ctx.get_char_size();
let center_x = (x_chars / 2 ) as i32 ;
let center_y = (y_chars / 2 ) as i32 ;
let min_x = player_pos.x - center_x;
let max_x = min_x + x_chars as i32 ;
let min_y = player_pos.y - center_y;
let max_y = min_y + y_chars as i32 ;
let map_width = map.width-1 ;
let map_height = map.height-1 ;
let mut y = 0 ;
for ty in min_y .. max_y {
let mut x = 0 ;
for tx in min_x .. max_x {
if tx > 0 && tx < map_width && ty > 0 && ty < map_height {
let idx = map.xy_idx(tx, ty);
if map.revealed_tiles[idx] {
let (glyph, fg, bg) = get_tile_glyph(idx, &*map);
ctx.set(x, y, fg, bg, glyph);
}
} else if SHOW_BOUNDARIES {
ctx.set(x, y, RGB::named(rltk::GRAY), RGB::named(rltk::BLACK), rltk::to_cp437('·' ));
}
x += 1 ;
}
y += 1 ;
}
}
}
这很像我们常规的地图绘制,但我们将相机锁定在地图的中间 - 并且不渲染实体。
在 main.rs
中,将对 draw_map
的调用替换为:
#![allow(unused)]
fn main () {
if self .mapgen_index < self .mapgen_history.len() { camera::render_debug_map(&self .mapgen_history[self .mapgen_index], ctx); }
}
现在您可以进入 map.rs
并完全删除 draw_map
、wall_glyph
和 is_revealed_and_wall
。
我们将在 main.rs
中将地图尺寸设置回合理的尺寸:
#![allow(unused)]
fn main () {
let mut builder = map_builders::random_builder(new_depth, &mut rng, 80 , 50 );
}
并且 - 我们完成了!在本章中,我们使拥有任何您喜欢的地图尺寸成为可能。我们最终恢复为“正常”尺寸 - 但我们将在未来发现此功能非常 有用。我们可以放大或缩小地图 - 系统一点也不介意。
...
本章的源代码可以在这里 找到
版权所有 (C) 2019, Herbert Wolverson。