项目搭建

建议使用rustup安装管理Rust。安装好Rust后可以在命令行输入以下俩条命令,检查确认是否安装成功:

$ rustc --version
rustc 1.40.0
$ cargo --version
cargo 1.40.0

输出的版本信息未必都是这样的,但建议使用比较新的Rust版本。

创建项目

Cargo是Rust的包管理工具,可以使用它创建我们的游戏项目。首先切换到游戏项目存储路径,然后再输入以下命令:

$ cargo init rust-sokoban

命令执行成功后,会在当前目录下创建一个名称为rust-sokoban的文件夹。文件夹内部是这个样子的:

├── src
│   └── main.rs
└── Cargo.toml

切换到文件夹rust-sokoban并运行命令 cargo run ,你会看到类似下面的输出信息:

$ cargo run
   Compiling rust-sokoban v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 1.30s
     Running `../rust-sokoban/target/debug/rust-sokoban`
Hello, world!

添加游戏开发依赖

接下来让我们一起把默认生成的项目修改成一个游戏项目! 我们使用当前最受欢迎的2D游戏引擎之一的ggez

还记得我们刚才在项目目录里看到的Cargo.toml文件吧?这个文件是用来管理项目依赖的,所以需要把我们需要使用到的crate添加到这个文件中。就像这样添加 ggez 依赖:

[dependencies]
ggez = "0.7"

MORE: 更多关于Cargo.toml的信息可以看 这里.

接下来再次执行cargo run.这次执行的会长一点,因为需要从crates.io下载我们配置的依赖库并编译链接到我们库中。

cargo run
    Updating crates.io index
    Downloaded ....
    ....
    Compiling ....
    ....
    Finished dev [unoptimized + debuginfo] target(s) in 2m 15s
    Running `.../rust-sokoban/target/debug/rust-sokoban`
    Hello, world!

NOTE: 如果你是使用的Ubuntu操作系统,在执行命令的时候可能会报错,如果报错信息有提到alsalibudev可以通过执行下面的命令安装解决: sudo apt-get install libudev-dev libasound2-dev.

接下来我们在main.rs文件中使用ggez创建一个窗口。只是创建一个空的窗口,代码比较简单:

use ggez::{conf, event, Context, GameResult};
use std::path;

// This struct will hold all our game state
// For now there is nothing to be held, but we'll add
// things shortly.
struct Game {}

// This is the main event loop. ggez tells us to implement
// two things:
// - updating
// - rendering
impl event::EventHandler<ggez::GameError> for Game {
    fn update(&mut self, _context: &mut Context) -> GameResult {
        // TODO: update game logic here
        Ok(())
    }

    fn draw(&mut self, _context: &mut Context) -> GameResult {
        // TODO: update draw here
        Ok(())
    }
}

pub fn main() -> GameResult {
    // 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 {};
    // Run the main event loop
    event::run(context, event_loop, game)
}

可以把代码复制到main.rs文件中,并再次执行cargo run,你会看到:

Screenshot

基本概念和语法

现在我们有了个窗口,我们创建了个窗口耶!接下来我们一起分析下代码并解释下使用到的Rust概念和语法。

引入

您应该在其它编程语言中也接触过这个概念,就是把我们需要用到的依赖包(或crate)里的类型和命名空间引入到当前的代码作用域中。在Rust中,使用use实现引入功能:


#![allow(unused)]
fn main() {
// 从ggez命名空间引入conf, event, Context 和 GameResult 
use ggez::{conf, event, Context, GameResult};
}

结构体声明


#![allow(unused)]
fn main() {
// This struct will hold all our game state
// For now there is nothing to be held, but we'll add
// things shortly.
struct Game {}
}

MORE: 查看更多结构体相关信息可以点 这里.

实现特征

特征类似其它语言中的接口,就是用来表示具备某些行为的特定类型。在我们的示例中需要结构体Game实现EventHandler特征。


#![allow(unused)]
fn main() {
// This is the main event loop. ggez tells us to implement
// two things:
// - updating
// - rendering
impl event::EventHandler<ggez::GameError> for Game {
    fn update(&mut self, _context: &mut Context) -> GameResult {
        // TODO: update game logic here
        Ok(())
    }

    fn draw(&mut self, _context: &mut Context) -> GameResult {
        // TODO: update draw here
        Ok(())
    }
}
}

MORE: 想更深入的了解特征可以点 这里.

函数

我们还需要学习下怎么使用Rust编写函数:


#![allow(unused)]
fn main() {
    fn update(&mut self, _context: &mut Context) -> GameResult {
        // TODO: update game logic here
        Ok(())
    }
}

你可能会疑惑这里的self是几个意思呢?这里使用self代表函数update是属于结构体的实例化对象而不是静态的。

MORE: 想深入了解函数可以点 这里.

可变语法

你可能更疑惑&mut self这里的&mut是做什么的? 这个主要用来声明一个对象(比如这里的self)是否可以被改变的。再来看个例子:


#![allow(unused)]
fn main() {
let a = 10; // a是不可变的,因为没有使用`mut`声明它是可变的
let mut b = 20; // b是可变的,因为使用了`mut`声明了它是可变的
}

再回头看update函数,我们使用了&mut 声明self是实例对象的可变引用。有没有点感觉了, 要不我们再看一个例子:


#![allow(unused)]
fn main() {
// 一个简单的结构体X
struct X {
    num: u32
}

//结构体X的实现代码块
impl X {
    fn a(&self) { self.num = 5 } 
    // 在函数a中不能修改`self`,这会编译失败的,因为是使用的`&self`

    fn b(&mut self) { self.num = 5 } 
    // 在函数b中可以修改`self`,因为使用的是`&mut self`
}
}

MORE: 想更多的了解可变性可以看 这里 (虽然是使用的Java作为演示语言讲解的,但对于理解可变性还是很有帮助地), 另外还可以看 这里.

对代码和Rust语法的简单介绍就先到这里,让我们继续前进吧,下一节见!

CODELINK: 要获取本节的完整代码可以点 这里.