Hands-on Rust 学习之旅(5)—— Module

有了上一篇文章的小鸟游戏,对游戏入门有点了解了。

今天我们开始新游戏 (Build a Dungeon Crawler) 开发学习。

今天的任务,就是设计游戏地图 (Map) 和 Player。

例行,看看执行效果:

dc_background

Modules

在建 Map 之前,我们必须先要解决一个棘手问题,在之前的游戏 demo 中,我们把代码都写在一个 main.rs 里,这不符合代码逻辑和规范,我们需要把代码拆解到其它文件中 —— 即根据业务将代码分模块 (Modules)。

就犹如图上所示,我们需要创建两个模块组件:crate::map 和 crate::player

按照惯例,我们新建一个 dungeoncrawl 项目:

1
arduino复制代码cargo new dungeoncrawl

Map

src 文件夹创建文件:map.rs

在地图上,主要有两种类型的东西:

1
2
3
4
5
rust复制代码#[derive(Copy, Clone, PartialEq)]
pub enum TileType {
Wall,
Floor,
}

其中,Wall 利用符号 # 表示,Floor.平铺:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rust复制代码// 地图类型平铺,使用 Vec 类型
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('#'));
}
}
}
}
}

Create the Player Structure

在之前的小鸟游戏已经定义 Player,这里基本一样,同样的,创建一个文件 player.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
rust复制代码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;
}
}
}
}

道理也简单,Player 使用字符 @ 表示,通过键盘「上下左右」控制 Player 走向,每按一次,走一格,同时,更新自己的位置。

但是,这里有一个前提,更新的点位,必须是 Floor 类型,就是说不能是墙:

1
2
3
4
5
6
7
8
rust复制代码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
}

有了 MapPlayer,这就可以在 main.rs 上引用这两个模块:

1
2
3
4
5
6
7
8
9
10
11
12
rust复制代码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::*;
}

通过 mod 关键引用,同样的,借助 mod prelude 把引用的几个模块和 bracket_lib 放在一起。

同样的,我们创建 State,把 Map, Player 初始化,放入屏幕中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
rust复制代码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);
}
}

剩下的就是和小鸟 game 一样了:

1
2
3
4
5
6
7
8
css复制代码fn main() -> BError {
let context = BTermBuilder::simple80x50()
.with_title("叶梅树学习 Dungeon Crawler")
.with_fps_cap(30.0)
.build()?;

main_loop(context, State::new())
}

总结

结果就如同一开始的截图效果一致了,有了 MapPlayer 接下来就好弄了。

今天主要是学习了 Module 开发,把代码分模块独立出去。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%