更棒的墙壁


关于本教程

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

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

Hands-On Rust


到目前为止,我们对地图使用了非常传统的渲染风格。井号 (#) 代表墙壁,句点 (.) 代表地板。 看起来很不错,但是像《矮人要塞》(Dwarf Fortress) 这样的游戏在使用 codepage 437 的线条绘制字符来使地牢的墙壁看起来平滑方面做得非常出色。 这个简短的章节将展示如何使用 bitmask 来计算合适的墙壁并适当地渲染它们。 和往常一样,我们将从第 1 节末尾的先前代码开始。

计算邻居以构建我们的位掩码 (bitset)

我们在 map.rs 中有一个像样的地图渲染系统,特别是函数 draw_map。 如果您找到按类型匹配 tile 的部分,我们可以从扩展 Wall 选择开始:

#![allow(unused)]
fn main() {
TileType::Wall => {
    glyph = wall_glyph(&*map, x, y);
    fg = RGB::from_f32(0., 1.0, 0.);
}
}

这需要 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 } // 我们遗漏了一种情况?
    }
}
}

让我们逐步了解这个函数:

  1. 如果我们位于地图边界,我们不希望冒险超出边界 - 所以我们返回一个 # 符号 (ASCII 35)。
  2. 现在我们创建一个 8 位无符号整数作为我们的 bitmask。 我们有兴趣设置单个位,并且只需要四个位 - 所以 8 位数字是完美的。
  3. 接下来,我们检查 4 个方向中的每一个方向,并添加到掩码 (mask)。 我们添加的数字对应于二进制的前四个位 - 即 1,2,4,8。 这意味着我们的最终数字将存储我们是否拥有四个可能的邻居中的每一个。 例如,值 3 表示我们在南北方向都有邻居。
  4. 然后我们匹配生成的掩码位,并从 codepage 437 字符集 返回适当的线条绘制字符。

这个函数反过来又调用了 is_revealed_and_wall,所以我们也来编写它! 它非常简单:

#![allow(unused)]
fn main() {
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]
}
}

它只是检查一个瓦片 (tile) 是否被显示 (revealed) 并且它是否是墙壁。 如果两者都为真,则返回 true - 否则返回 false。

如果您现在 cargo run 运行项目,您将获得一组看起来更漂亮的墙壁:

Screenshot

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

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


版权 (Copyright) (C) 2019, Herbert Wolverson。