实现游戏基本功能
现在角色可以推动箱子在区域内移动了.有些(并不是全部)游戏还会设定些目标让玩家去完成.比如有些推箱子类的游戏会让玩家把箱子推到特定的点才算赢.目前我们还没实现类似的功能,还没有检查什么时候玩家赢了并停止游戏,有可能玩家已经把箱子推到目标点了,但我们的游戏并没意识到.接下来就让我们完成这些功能吧!
首先我们需要想一下要检查是否赢了并通知玩家需要添加那些功能. 当玩家闯关时:
- 需要一个用于保存游戏状态的
resource
- 游戏是在进行中还是已经完成了?
- 玩家目前一共走了多少步了?
- 需要一个用于检查玩家是否完成任务的
system
- 需要一个用于更新移动步数的
system
- 需要一个用于展示游戏状态的界面(UI )
游戏状态资源
我们之所以选择使用资源(resource)
保存游戏状态,是因为游戏状态信息不跟任何一个实体绑定.接下来我们就开始定义一个Gameplay
资源.
Gameplay
有俩个属性: state
和 moves_count
. 分别用于保存当前游戏状态(当前游戏正在进行还是已经有了赢家)和玩家操作的步数. state
是枚举(enum)
类型, 可以这样定义:
细心的读者会注意到,我们使用了一个宏来为 Gameplay
派生 Default
特性,并为 GameplayState
枚举使用了 #[default]
注解。这个注解的作用是告诉编译器,如果我们调用 GameplayState::default()
,我们应该得到 GameplayState::Playing
,这是合理的。
现在,当游戏启动时,Gameplay
资源将如下所示:
计步System
我们可以通过增加Gameplay
的moves_count
属性值来记录玩家操作的步数.
可以在先前定义的处理用户输入的InputSystem
中实现计步的功能.因为我们需要在InputSystem
中修改Gameplay
的属性值,所以需要在InputSystem
中定义SystemData
类型时使用Write<'a, Gameplay>
.
我们先前已经编写过根据玩家按键移动角色的代码,在此基础上再添加增加操作步骤计数的代码就可以了.
Gameplay System
接下来是添加一个GamePlayStateSystem
用于检查所有的箱子是否已经推到了目标点,如果已经推到了就赢了.除了 Gameplay
, 要完成这个功能还需要对Position
, Box
, 和 BoxSpot
进行只读访问.这里使用 Join
结合Box(箱子)
和 Position(位置)
创建一个包含每个箱子位置信息的Vector
(集合).我们只需要通过遍历这个集合来判断每个箱子是否在目标点上,如果在就胜利了,如果不在,则游戏继续.
最后还需要在渲染循环中执行我们的代码:
// main.rs
/* ANCHOR: all */
// Rust sokoban
// main.rs
use ggez::{conf, event, Context, GameResult};
use hecs::World;
use std::path;
mod components;
mod constants;
mod entities;
mod map;
mod systems;
// ANCHOR: game
// This struct will hold all our game state
// For now there is nothing to be held, but we'll add
// things shortly.
struct Game {
world: World,
}
// ANCHOR_END: game
// ANCHOR: handler
impl event::EventHandler<ggez::GameError> for Game {
fn update(&mut self, context: &mut Context) -> GameResult {
// Run input system
{
systems::input::run_input(&self.world, context);
}
// Run gameplay state
{
systems::gameplay::run_gameplay_state(&self.world);
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult {
// Render game entities
{
systems::rendering::run_rendering(&self.world, context);
}
Ok(())
}
}
// ANCHOR_END: handler
// ANCHOR: main
pub fn main() -> GameResult {
let mut world = World::new();
map::initialize_level(&mut world);
entities::create_gameplay(&mut world);
// Create a game context and event loop
let context_builder = ggez::ContextBuilder::new("rust_sokoban", "sokoban")
.window_setup(conf::WindowSetup::default().title("Rust Sokoban!"))
.window_mode(conf::WindowMode::default().dimensions(800.0, 600.0))
.add_resource_path(path::PathBuf::from("./resources"));
let (context, event_loop) = context_builder.build()?;
// Create the game state
let game = Game { world };
// Run the main event loop
event::run(context, event_loop, game)
}
// ANCHOR_END: main
/* ANCHOR_END: all */
游戏信息界面
最后一步是需要提供一个向玩家展示当前游戏状态的界面.我们需要一个用于记录游戏状态的资源和一个更新状态信息的System.可以把这些放到资源GameplayState
和RenderingSystem
中.
首先需要为GameplayState
实现Display特征,这样才能以文本的形式展示游戏状态.这里又用到了模式匹配,根据游戏的状态显示"Playing(进行中)"或"Won(赢了)".
接下来我们需要在RenderingSystem
中添加一个方法draw_text
,这样它就可以把游戏状态信息GameplayState
显示到屏幕上了.
...为了调用draw_text
我们还需要把资源 Gameplay
添加 RenderingSystem
中,这样 RenderingSystem
才能获取到资源 Gameplay
.
至此我们编写的推箱子游戏已经可以向玩家展示基本的信息了:
- 当前的操作步数
- 当玩家胜利时告诉他们
看起来就像这个样子:
还有很多可以改进增强的!
CODELINK: 点 这里获取目前的完整代码.