ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Rust语言项目实战(三) - 创建主循环

2026/9/12 0:33:46 拓冰建站 浏览量
Rust语言项目实战(三) - 创建主循环

回顾

在前面的章节中,我们大致已经完成了如下的工作:

  • 为游戏添加了音频文件
  • 为游戏准备了备用屏幕及设置
  • 为游戏准备了键盘的即时捕获输入的设置
  • 在退出游戏前恢复上述的设置

众所周知,游戏在不手动退出的情况下应该一直运行下去,因此所有的游戏机会都应该有一个主循环来保证游戏不会自动退出。

创建主循环

那么游戏主循环应该从哪里开始呢?当然是在我们准备好所有的配置之后了。 因此,我们将在// Terminal// Cleanup 两部分之间加入我们的游戏主循环:

fn main() -> Result<(), Box<dyn Error>>{snip[..]// Terminallet mut stdout = io::stdout();terminal::enable_raw_mode()?;stdout.execute(EnterAlternateScreen)?;stdout.execute(Hide)?;// Game Loop'gameloop: loop {}// Cleanupaudio.wait();   // 等待播放结束stdout.execute(Show)?;stdout.execute(LeaveAlternateScreen)?;terminal::disable_raw_mode()?;Ok(())
}

在前面的章节中,我们提到了可以为循环打标签,上面的代码中,我们给主循环打了一个名为'gameloop的标签, 以便我们可以在游戏进行的任意位置跳出主循环,以结束游戏。

监听事件

在游戏中,我们主要是通过键盘的操作来控制玩家的行为,因此第一步我们需要定期的监控键盘事件,根据键盘事件来决定我们的行为:

snip[..]// Game Loop'gameloop: loop {// Inputwhile event::poll(Duration::default())? {  				// 监听所有事件if let Event::Key(key_event) = event::read()? {  	// 检测到事件属于一个键盘事件match key_event.code {KeyCode::Esc | KeyCode::Char('q') => {   	// 检测到`Esc`或`q`键被按下audio.play("lose");  					// 播放游戏失败的音乐break 'gameloop;    					// 退出游戏主循环}_ => {}										// 其他键被按下不做操作}}}}
snip[..]

上面的代码中, 用到了event::poll, 这是一个事件监听器,他会定期的监听事件,间隔事件在参数中指定, 这个例子中使用Duration::default()作为参数,表示使用默认的间隔时间。

由于我们只监听键盘事件, 因此使用了if let这种模式匹配语法,当监听到键盘事件时,再用match去匹配判断具体是什么键盘事件。

这个例子中, 第一条匹配规则是Escq键被按下,则播放游戏失败的音乐,并退出游戏主循环。注意_ => {}这一行是不可或缺的,因为在前面的基础篇中提到了, match后面的分支必须是穷举的,如果不想一个一个列出来,就要用_代表一个默认分支。

测试退出功能

上面的代码实际上是实现了一个退出游戏的功能,即当游戏开始后, 如果玩家按下了Escq键,则会播放失败音乐并退出,现在让我们来测试一下, 先运行cargo run, 会看到代码的编译构建消息:
请添加图片描述
紧接着程序开始运行,这时会进入备用屏幕,我们会看到一块空白的终端屏幕:请添加图片描述
这时, 程序会一直监听系统重发生的事件, 这时我们按下任意键, 例如k, 这时主循环内监听到了键盘事件并对其进行匹配, 但是这个键并不是Esc或者q, 因此会进入match语句的默认分支,也就是不进行任何操作, 所以我们在界面上也看不到任何的反应。

此时我们再尝试按下q键, 会听到游戏播放失败的音乐“You lose”, 然后退出空白界面,回到正常的命令行终端中,如下:
请添加图片描述

小结

本章在前面的基础上加入了游戏主循环,并且实现了按指定键退出游戏的功能,到此为止,我们的游戏界面还是一片空白,没有任何的显示,下一章中,我们开始进行界面的绘制与渲染,并且将对我们的代码结构进行优化,使得代码结构更加的模块化。目前,我们的main.rs文件的完整内容如下:

// main.rs
use std::{error::Error, io, time::Duration};use crossterm::{terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, cursor::{Hide, Show}, event::{KeyCode, self, Event}};
use rusty_audio::Audio;fn main() -> Result<(), Box<dyn Error>>{let mut audio = Audio::new();audio.add("explode", "./explode.wav");audio.add("lose", "./lose.wav");audio.add("move", "./move.wav");audio.add("pew", "./pew.wav");audio.add("startup", "./startup.wav");audio.add("win", "./win.wav");audio.play("startup");// Terminallet mut stdout = io::stdout();terminal::enable_raw_mode()?;stdout.execute(EnterAlternateScreen)?;stdout.execute(Hide)?;// Game Loop'gameloop: loop {// Inputwhile event::poll(Duration::default())? {if let Event::Key(key_event) = event::read()? {match key_event.code {KeyCode::Esc | KeyCode::Char('q') => {break 'gameloop;}_ => {}}}}}// Cleanupaudio.wait();   // 等待播放结束stdout.execute(Show)?;stdout.execute(LeaveAlternateScreen)?;terminal::disable_raw_mode()?;Ok(())
}