With the bird games in the last article, you have a little bit of an idea of how to get started.

Today we are going to learn about building a Dungeon Crawler.

Today’s task is to design the Map and Player.

Routine, see the implementation effect:

Modules

Before building the Map, we had to solve a tricky problem. In the previous game demo, we wrote all the code in a single main.rs file, which was not consistent with the code logic and specification. We had to break the code into other files — Modules based on the business.

As shown, we need to create two module components: Crate :: Map and Crate :: Player

As usual, we’ll create a new DungeonCrawl project:

cargo new dungeoncrawl
Copy the code

Map

Create the file: map.rs in the SRC folder.

On maps, there are two main types of things:

#[derive(Copy, Clone, PartialEq)]
pub enum TileType {
    Wall,
    Floor,
}
Copy the code

Where Wall is denoted with the symbol # and Floor is tiled with. :

Pub struct Map {pub tiles: Vec<TileType>,} // pub fn render(&self, CTX: &mut BTerm) {for y in 0.. SCREEN_HEIGHT { for x in 0.. SCREEN_WIDTH { let idx = map_idx(x, y); match self.tiles[idx] { TileType::Floor => { ctx.set(x, y, YELLOW, BLACK, to_cp437('.')); } TileType::Wall => { ctx.set(x, y, GREEN, BLACK, to_cp437('#')); } } } } }Copy the code

Create the Player Structure

In the previous Bird game we defined Player. Here it is basically the same. Again, create a file player.rs:

use crate::prelude::*; pub struct Player { pub position: Point } impl Player { pub fn new(position: Point) -> Self { Self { position } } pub fn render(&self, ctx: &mut BTerm) { ctx.set( self.position.x, self.position.y, WHITE, BLACK, to_cp437('@'), ) } pub fn update(&mut self, ctx: &mut BTerm, map: &Map) { if let Some(key) = ctx.key { let delta = match key { VirtualKeyCode::Left => Point::new(-1, 0), VirtualKeyCode::Right => Point::new(1, 0), VirtualKeyCode::Up => Point::new(0, -1), VirtualKeyCode::Down => Point::new(0, 1), _ => Point::zero() }; let new_position = self.position + delta; if map.can_enter_tile(new_position) { self.position = new_position; }}}}Copy the code

The reason is also simple. The Player is represented by the @ character, and the direction of the Player is controlled by the keyboard “up, down, left and right”. Every time you press it, the Player moves one space and updates its position at the same time.

The update point must be of type Floor, that is, not a wall:

pub fn in_bounds(&self, point: Point) -> bool {
    point.x >= 0 && point.x < SCREEN_WIDTH
    && point.y >= 0 && point.y < SCREEN_HEIGHT
}
    
pub fn can_enter_tile(&self, point: Point) -> bool {
    self.in_bounds(point) && self.tiles[map_idx(point.x, point.y)] == TileType::Floor
}
Copy the code

With Map and Player, it is possible to reference these two modules on main.rs:

use prelude::*;

mod map;
mod player;

mod prelude {
    pub use bracket_lib::prelude::*;
    pub const SCREEN_WIDTH: i32 = 80;
    pub const SCREEN_HEIGHT: i32 = 50;
    pub use crate::map::*;
    pub use crate::player::*;
}
Copy the code

With mod key references, and also with mod Prelude, the referenced modules and bracket_lib are grouped together.

Similarly, we create State, initialize Map and Player, and place them on screen:

struct State { map: Map, player: Player, } impl State { fn new() -> Self { Self { map: Map::new(), player: Player::new(Point::new(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)), } } } impl GameState for State { fn tick(&mut self, ctx: &mut BTerm) { ctx.cls(); self.player.update(ctx, &self.map); self.map.render(ctx); self.player.render(ctx); }}Copy the code

The rest is the same as bird Game:

Fn the main () - > BError {let context = BTermBuilder: : simple80x50 () with_title (" plum tree leaf learning Dungeon Crawler "). The with_fps_cap (30.0)  .build()? ; main_loop(context, State::new()) }Copy the code

conclusion

The result is the same as the original screenshot, but with Map and Player, it will be easier.

Today is mainly to learn Module development, the code Module independent out.