《电子商务网站建设 》,淘宝联盟 网站建设 内容少,北京做网站哪个好,跨境电商网站建设方案书回顾
前面的章节中#xff0c;我们已经完成了下面的工作#xff1a;
准备所需的音频文件设置游戏所需的屏幕和键盘配置创建了游戏主循环并在其中实现按键Esc或q退出游戏的功能游戏退出前还原配置
目前游戏的界面还是一片空白#xff0c;本章我们开始为游戏在终端上绘制并…回顾
前面的章节中我们已经完成了下面的工作
准备所需的音频文件设置游戏所需的屏幕和键盘配置创建了游戏主循环并在其中实现按键Esc或q退出游戏的功能游戏退出前还原配置
目前游戏的界面还是一片空白本章我们开始为游戏在终端上绘制并渲染界面这真是一个激动人心的工作。
模块化
对于一个真正是实战项目而已应该没人会把所有的代码都放在main.rs文件中。 为了使项目的结构 功能和逻辑更加清楚明了我们通常会将项目进行适当的模块化。本章中我们将创建几个新文件用于存放不同功能的代码
lib.rs: 在基础篇中提到了是项目的根库文件我们将项目中其他的库和一些全局使用的常量放入其中以便我们可以在本项目的其他文件中可以直接使用frame.rs: 我们的游戏界面需要划定一块框架然后在这个范围内进行绘制其中的代码实现相关功能render.rs: 在游戏运行过程中需要根据实时的信息对界面进行渲染其中的代码实现相关的功能
当前的目录结构如下
invaders
├── Cargo.lock
├── Cargo.toml
├── explode.wav
├── lose.wav
├── move.wav
├── pew.wav
├── src
│ ├── frame.rs
│ ├── lib.rs
│ ├── main.rs
│ └── render.rs
├── startup.wav
└── win.wavlib.rs
pub mod frame;
pub mod render;pub const NUM_ROWS: usize 20;
pub const NUM_COLS: usize 40;在这里面我们定义了两个常量 分别是行数20和列数40也就是说我们游戏界面的大小是(20 * 40)。
frame.rs
先理解一下Frame是什么 Frame就是我们的游戏界面在终端中例如我们的游戏界面是20行 40列的一块范围那么我们的Frame就是由20 * 40个像素单元组成每个单元就是屏幕上显示的一个字符。
// frame.rs
use crate::{NUM_COLS, NUM_ROWS}; // 定义在本项目的lib.rs文件中的pub常量或模块可以用 crate::名称 这种格式导入
pub type Frame VecVecstatic str;pub fn new_frame() - Frame {let mut cols Vec::with_capacity(NUM_COLS);for _ in 0..NUM_COLS {let mut col Vec::with_capacity(NUM_ROWS);for _ in 0..NUM_ROWS {col.push( );}cols.push(col);}cols
}
pub trait Drawable {fn draw(self, frame: mut Frame);
}在上面的代码中 有一个新的语法type Frame VecVecstatic str, 这相当于是给VecVecstatic str这个数据结构起了一个别名叫做Frame, 在别的语言中也比较常见。这说明Frame是一个二维数组其中的每个元素都是一个字符串切片的引用 static这个标记是表示这个变量的声明周期存在于整个程序运行期间不会被销毁。
new_frame这个函数的内容是初始化我们的界面框架 将界面中每个位置都初始设置为一个 空字符串。
我们的界面在游戏运行中一定是动态变化的而且可能由多种不同的情况或主体去进行界面的绘制因此我们定义了一个Trait叫做Drawable 只要实现了它规定的接口都可以对界面进行绘制 其中的draw函数返回一个Frame。
render.rs
渲染(render)的主要目的是根据Frame的数据对标准输出进行操作从而使得屏幕显示出来我们想要的界面效果。
use std::io::{Stdout, Write};use crate:: frame::Frame;
use crossterm::QueueableCommand;
use crossterm::style::{SetBackgroundColor, Color}; use crossterm::terminal::{Clear, ClearType};
use crossterm::cursor::MoveTo;
pub fn render(stdout: mut Stdout, last_frame: Frame, curr_frame: Frame, force: bool) {if force {stdout.queue(SetBackgroundColor(Color::Blue)).unwrap();stdout.queue(Clear(ClearType::All)).unwrap();stdout.queue(SetBackgroundColor(Color::Black)).unwrap();}for (x, col) in curr_frame.iter().enumerate() {for (y, s) in col.iter().enumerate() {if *s ! last_frame[x][y] || force {stdout. queue(MoveTo(x as u16, y as u16)).unwrap();print!({}, *s);}}}stdout.flush().unwrap();
}现在来看一下文件中的reder()函数 首先是参数
stdout: 是一个标准输出类型因为我们需要对终端进行操作因此是一个对终端的可变引用类型last_frame: 上次的界面状态curr_frame: 当前的界面状态;force是否强制进行渲染例如刚开始游戏时需要全部重新渲染。
首先看函数开始判断了是否强制渲染如果强制渲染的话会清除屏幕上的所有文字内容并重置屏幕的背景色(染色 - 清除 - 再染色是比较通用的清除屏幕的步骤)。
然后会比较当前Frame和上次刷新的Frame的状态对其中的每个像素单元进行比较看是否有变化(如果是强制渲染则不比较, 直接对每个单元都进行操作)。 如果发现有变化则移动到当前判断的位置(MoveTo)然后在这里填入当前Frame信息中该位置的值(print!({}, *s);)。
注意stdout.queue()将对标准输出的操作都先放入了缓冲区 当在最后调用stdout.flush()时才会被一起执行在屏幕上产生效果。
小结
本章完成了界面绘制和渲染相关功能的函数和模块的编写接下来我们要做的就是把这些单个的功能串联起来并使用多线程的方式来使界面正常工作起来。
下面是各个文件的完整代码
// lib.rs
pub mod frame;
pub mod render;pub const NUM_ROWS: usize 20;
pub const NUM_COLS: usize 40;// frame.rs
use crate::{NUM_COLS, NUM_ROWS};
pub type Frame VecVecstatic str;pub fn new_frame() - Frame {let mut cols Vec::with_capacity(NUM_COLS);for _ in 0..NUM_COLS {let mut col Vec::with_capacity(NUM_ROWS);for _ in 0..NUM_ROWS {col.push( );}cols.push(col);}cols
}
pub trait Drawable {fn draw(self, frame: mut Frame);
}
// render.rs
use std::io::{Stdout, Write};use crate:: frame::Frame;
use crossterm::QueueableCommand;
use crossterm::style::{SetBackgroundColor, Color}; use crossterm::terminal::{Clear, ClearType};
use crossterm::cursor::MoveTo;
pub fn render(stdout: mut Stdout, last_frame: Frame, curr_frame: Frame, force: bool) {if force {stdout.queue(SetBackgroundColor(Color::Blue)).unwrap();stdout.queue(Clear(ClearType::All)).unwrap();stdout.queue(SetBackgroundColor(Color::Black)).unwrap();}for (x, col) in curr_frame.iter().enumerate() {for (y, s) in col.iter().enumerate() {if *s ! last_frame[x][y] || force {stdout.queue(MoveTo(x as u16, y as u16)).unwrap();print!({}, *s);}}}stdout.flush().unwrap();
}