使用 Rust 开发一个微型游戏

Posted 小乔的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Rust 开发一个微型游戏相关的知识,希望对你有一定的参考价值。

使用 Rust 构建微型游戏 -- 用于理解游戏开发

一、 创建游戏

Agenda

  • 建立项目
  • 实现 Game loop
  • 不同的游戏模式
  • 添加玩家
  • 添加障碍和计分
  • 汇总

理解 Game loop

为了让游戏流畅、顺滑的运行,需要使用 Game loop

Game loop:

  • 初始化窗口、图形和其它资源
  • 每当屏幕刷新(通常是每秒 30、60或更多次),它都会运行
  • 每次通过循环,它都会调用游戏的 tick() 函数

Game loop

开始 -> 配置 App、Window 和图形 -> Poll (轮询 OS 监听输入状态 -> 调用 tick() 函数 -> 更新屏幕 -> 停止? -> 退出

游戏引擎

  • 游戏引擎用来处理平台特定的部分
  • 以便开发者专心开发游戏

Bracket-Lib (Amethyst Foundation)

Bracket-Lib 是一个 Rust 游戏编程库:

  • 作为简单的教学工具
  • 抽象了游戏开发很多复杂的东西
  • 但保留了相关的概念

Bracket-Lib 包括很多库:

  • 随机数生成、几何、路径寻找、颜色处理、常用算法等

Bracket-terminal

bracket-terminal 是 Bracket-Lib 中负责显示部分

  • 它提供了模拟控制台
  • 可与多种渲染平台配合:
    • 从文本控制台到 Web Assembly
    • 例如:OpenGL、Vulkan、Metal
  • 支持 sprites 和原生 OpenGL 开发

Codepage 437:IBM 扩展 ASCII 字符集

Codepage 437:

  • 来自 Dos PC 上的字符,用于终端输出,除了字母和数字,还提供了一些符号
  • Bracket-lib 会把字符翻译成图形 sprites 并提供一个有限的字符集,字符所展示的是相应的图片
~ via 

#yyds干货盘点#通过猜数游戏来学习RUST中最基础的知识

一、概述

使用猜数游戏的程序,来学习以下内容:

  • let、match等的方法
  • 相关的函数
  • 外部crate

游戏目标:

  • 生成1-100之间的随机数
  • 提示玩家做一个猜测
  • 猜测完成之后,程序会提示太大或者太小
  • 如果猜测正确,打印一个庆祝信息,并退出程序

二、实战过程

2.1 生成随机数

新建名为 guessing_game 的项目,如下命令

cargo new guessing_game

我们编辑main.rs文件,修改为以下内容

use std::io;

fn main() 
    println!("猜数!");

    println!("猜一个数字");

    // 使用let声明变量
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("无法读取行");

    println!("你猜的数字是: ", guess);

相关知识点:

2.1.1 use

我们在上面的第一行代码中使用了use关键字,该关键字是用于导入库。在main函数中我们使用到了read_line函数,该函数用于读取终端输入,该函数在io库中,而io库属于rust的标准库,所以我们使用了use std::io;进行引入。

2.1.2 let

在rust中使用let关键字声明变量,但是变量默认不可变,属于immutable状态,如果我们在声明变量的时候希望能修改它,那么还需加上mut关键字

2.1.3 关联函数

在以上示例代码中,包含以下的一行

let mut guess = String::new();

String是一个类,::符号用于调用关联函数new(),这样就生成了一个空的字符串。rust里的关联函数类似于Java的中静态方法。在Rust里,很多地方会出现new()函数,用于初始化对象。

2.1.4 取地址符号

在上面的io::stdin().read_line(&mut guess)这部分代码中,read_line函数需要传入了一个可变的变量,并且我们在变量前面加了&符号,代表传入的是这个变量的引用,变量的饮用指向变量的原始内存地址。把引用传到read_line函数里面,让函数能修改指针地址所存储的值。引用在rust里也是默认不可变的,需要添加mut关键字让变量可变。

2.1.5 io::Result

read_line这个方法会返回一个io::Result的结果,在rust的标准库里都有一个叫Result的类型,有泛型Result,也有子模块Result,如上面io::Result就属于io这个子模块的Result

这里的Result是一个枚举(enum)类型,一个枚举类型通常有几个固定的值,这些值称作这个枚举类型的变体。io::Result这个枚举类型一共有两个变体,一个是OK,另一个是ErrResult这个类型通常定义了一些方法,比如我们在代码里调用的expect方法。

假如返回的io.Result值是Err,那么expect就会中断当前的程序,并将传入的字符串信息写出来;如果返回的值是OK,那么expect就会提取OK中附加的值,并将这个值作为结果返回给用户。

2.1.6 占位符

在以下的这个行代码中,我们使用到了,在rust的双引号内,是一个占位符,在编译的时候会替换成后面的变量。如果双引号中里有多个花括号,在编译时会替换成后面的多个变量值。

println!("你猜的数字是: ", guess);

2.2 生成神秘数字

2.2.1 下载依赖包

我们首先需要生成1到100之间的随机数,但是rust标准里并不包含生成随机数字的功能,不过官方提供了一个crate用于生成随机数,crate名称叫做randcrate仓库的官方网站是:https://crates.io/,我们可以在crate官方网站中找到相关的crate

在rust里, crate我们可以叫做“库”或者“包”,crate可以分为两种,一是我们构建好的二进制可运行文件,另一种是library,library的功能就是给其他程序使用。

我们在项目的Cargo.toml文件中引入rand库,如下

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.3.14"

在上面的配置文件中,我们在dependencies中新加了一行rand = "0.3.14",左边代表库的名称,右边代表库的版本号,表示的是我们项目需要依赖这个库。当我们执行cargo build之后,cargo会去crates.io下载我们定义的rand库到本地,同时rand库依赖的库也会被下载到本地。下载完成之后会执行编译,如果本地存在了对应的库,则直接执行编译。

cargo第一次执行cargo build命令的时候,会生成Cargo.lock文件,该文件保存了当前项目依赖的所有库以及对应的库版本,如果下次继续执行build指令,cargo将直接从Cargo.lock中直接读取依赖库信息,并加载到我们的项目中。

当我们希望cargo读取的是Cargo.toml文件获取版本信息,而不是Cargo.lock获取版本信息时,我们可以通过cargo update命令更新依赖,cargo会根据Cargo.toml提供的版本信息更新依赖,并且再次写入到Cargo.lock中。

2.2.2 引入trait

trait类似于Java中的接口,但trait不是用于继承,而是引用,引用trait之后,可以使用trait定义的相关方法。下面我们引入rand库中的Rng,如下

use rand::Rng;

引入之后我们就可以使用生成随机数的函数了,我们先将生成的随机数打印出来,如下代码

let secret_number = rand::thread_rng().gen_range(1, 101);
println!("神秘数字是", secret_number);

在上面代码中,我们调用rand::thread_rng()生成了一个随机数生成器,再调用gen_range()方法实现了随机数的生成,这个方法的第一个参数是最小值闭区间,第二个参数是最大值开区间。

2.3 比较猜测的数字与神秘数字

我们需要比较guessecret_number这两个变量,首先我们需要从标准库中引入如下cmp::Ordering,如下代码

use std::cmp::Ordering;

这里的Ordering是个枚举类型,包含三个值,如下

  • Less: 小于
  • Greater: 大于
  • Equal: 等于

比较的代码如下

// 先将guess变量转换为整数类型
let guess: u32 = guess.trim().parse().expect("Please type a number")


match guess.cmp(&secret_number)
    Ordering::Less => println!("Too small!"),
    Ordering::Greater => print!("To big!"),
    Ordering::Equal => println!("You win"),

在上面的代码中,我们使用一个同名的u32类型的变量guess,这样,在该行代码之上定义的String类型的guess变量就会被隐藏(shadow)。在该行代码以后,遇到的guess则是u32类型的guess。这个特性通常使用在我们需要类型转换的场景中,这样我们无需新起一个变量名,而是直接使用之前的变量名。

trim: trim函数会把字符串两边的空白内容去掉 parse: parse函数会把将字符串解析成u32类型

实际上,我们生成的随机数secret_number默认是i32类型,但是在往下的代码中,secret_number被用于与guess做对比,所以根据rust类型解析与推倒的机制,secret_number在编译的时候转变为了u32。

2.4 多次猜测

在以上的程序中,用户只能猜测一次数据是否正确,为了能实现多次猜测,我们需要做一个无线循环。我们吧才猜测的逻辑放在loop中,即可实现无限循环。

loop 
    println!("猜一个数字");
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("无法读取行");
    println!("你猜的数字是: ", guess);

    // 先将guess变量转换为整数类型
    let guess: u32 = guess.trim().parse().expect("Please type a number");

    match guess.cmp(&secret_number) 
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => print!("To big!"),
        Ordering::Equal => println!("You win"),
    

但上面的程序中,即使我们猜对了,也不会停止。我们应该将逻辑改为,让输入正确之后,我们应当跳出循环,改为如下代码

loop 
    println!("猜一个数字");
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("无法读取行");
    println!("你猜的数字是: ", guess);

    // 先将guess变量转换为整数类型
    let guess: u32 = guess.trim().parse().expect("Please type a number");

    match guess.cmp(&secret_number) 
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => print!("To big!"),
        Ordering::Equal => 
            println!("You win");
            break;
        
    

2.5 完善程序

2.5.1 完善健壮性

如果我们在在猜数的时候,输入的是一个非数字的字符,程序将被异常退出,我们在解析数字的时候使用match表达式,表达式里针对不同的枚举结果进行不同的处理,如下代码

let guess: u32 = match guess.trim().parse()
    // 解析正确时将数字返回
    Ok(num) => num,
    // 解析错误时,使用 continue 跳出本次循环,进行下一次循环
    Err(_) => continue
;

2.5.2 取消提示

还需注意的是,如果我们的目的是让用户玩这个游戏,那么就不应该把结果打印出来,所以去掉下面这行代码

println!("神秘数字是", secret_number);

三、运行程序

最终main.rs文件编写的程序代码如下

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() 
    println!("猜数游戏!");
    let secret_number = rand::thread_rng().gen_range(1, 101);

    loop 
        println!("猜一个数字");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("无法读取行");
        println!("你猜的数字是: ", guess);

        // 先将guess变量转换为整数类型
        let guess: u32 = match guess.trim().parse() 
            // 解析正确时将数字返回
            Ok(num) => num,
            // 解析错误时,使用 continue 跳出本次循环,进行下一次循环
            Err(_) => continue,
        ;

        match guess.cmp(&secret_number) 
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => print!("To big!"),
            Ordering::Equal => 
                println!("You win");
                break;
            
        
    

编译

cargo build

运行

target/debug/guessing_game

以上是关于使用 Rust 开发一个微型游戏的主要内容,如果未能解决你的问题,请参考以下文章

一个简单的 rust 项目 飞机大战

为啥我的rust进不去

rust怎么查看ip

为啥 Rust 的示例猜谜游戏允许具有不同返回类型的 match 语句?

小游戏系列——微型整蛊恶作剧

#yyds干货盘点#通过猜数游戏来学习RUST中最基础的知识