有了上一篇文章的小鸟游戏,对游戏入门有点了解了。
今天我们开始新游戏 (Build a Dungeon Crawler) 开发学习。
今天的任务,就是设计游戏地图 (Map) 和 Player。
例行,看看执行效果:
Modules
在建 Map 之前,我们必须先要解决一个棘手问题,在之前的游戏 demo 中,我们把代码都写在一个 main.rs 里,这不符合代码逻辑和规范,我们需要把代码拆解到其它文件中 —— 即根据业务将代码分模块 (Modules)。
就犹如图上所示,我们需要创建两个模块组件:crate::map 和 crate::player
按照惯例,我们新建一个 dungeoncrawl 项目:
cargo new dungeoncrawl
Map
在 src 文件夹创建文件:map.rs。
在地图上,主要有两种类型的东西:
#[derive(Copy, Clone, PartialEq)]
pub enum TileType {
Wall,
Floor,
}
其中,Wall 利用符号 # 表示,Floor 用 .平铺:
// 地图类型平铺,使用 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:
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 类型,就是说不能是墙:
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
}
有了 Map 和 Player,这就可以在 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::*;
}
通过 mod 关键引用,同样的,借助 mod prelude 把引用的几个模块和 bracket_lib 放在一起。
同样的,我们创建 State,把 Map, Player 初始化,放入屏幕中:
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 一样了:
fn main() -> BError {
let context = BTermBuilder::simple80x50()
.with_title("叶梅树学习 Dungeon Crawler")
.with_fps_cap(30.0)
.build()?;
main_loop(context, State::new())
}
总结
结果就如同一开始的截图效果一致了,有了 Map 和 Player 接下来就好弄了。
今天主要是学习了 Module 开发,把代码分模块独立出去。