使用 Rust 与 WebAssembly 进行图形开发

项目初始化

要求需要先安装好 Node.js,和 Rust 环境。

  1. 建一个新的文件夹,将工作目录切换到该目录下
npm init rust-webpack

如果能看到下面的图案就是成功了:

 Rust +  WebAssembly + Webpack = ❤
  1. 安装依赖
npm install
  1. 安装 wasm-pack

Linux 和 Mac OSX 的操作系统可以使用 cURL 进行安装:

curl https://rustwasm.github.io/wasm-pack/installer/init . sh -sSf | sh

Windows 可以下载单独的 exe,进行安装:下载地址

  1. 运行服务器
npm run start

会自动安装 Rust 所需的依赖包,如果成功的话,开发者工具中终端界面可以看到 Hello, World

使用 Rust 与 WebAssembly 进行图形开发_第1张图片

  1. 更新 Rust 版本

目前模板的 Rust 版本为 2018(2022年7月5日时),在 cargo.toml 将版本改成 2021

edition = "2021"
  1. 更新依赖的版本

cargo.toml 中的依赖也不是最新的,可以更新到新的版本。

使用 Rust 与 WebAssembly 进行图形开发_第2张图片

Visusal Studio Code 中有 Crates 插件,可以获取依赖的版本信息。

[dev-dependencies]
wasm-bindgen-test = "0.3.31"
futures = "0.3.21"
js-sys = "0.3.22"
wasm-bindgen-futures = "0.4.31"
  1. 更新 console_error_panic_hook

这是个非常有用的库,一看名字就知道是用来 Debug 的,目前最新是 0.1.7

[target."cfg(debug_assertions)".dependencies]
console_error_panic_hook = "0.1.7"

绘制图形到 Canvas

可以使用 将图形绘制到浏览器窗口中,在 static\index.html 后添加

<body>
  <canvas id="canvas" tabindex="0" height="600" width="600">Your browser does not support the canvas.canvas>
  

终于到了写 Rust 代码的时候了!

lib.rs 引入依赖

use wasm_bindgen::JsCast;

原有代码里的 #[cfg(debug_assertions)] 可以删除。

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::console;

// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.

    console_error_panic_hook::set_once();

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document
        .get_element_by_id("canvas")
        .unwrap()
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .unwrap();

    let context = canvas
        .get_context("2d")
        .unwrap()
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();

    context.move_to(300.0, 0.0);
    context.begin_path();
    context.line_to(0.0, 600.0);
    context.line_to(600.0, 600.0);
    context.line_to(300.0, 0.0);
    context.close_path();
    context.stroke();
    context.fill();

    Ok(())
}

需要注意的是,web_sys 使用了 features 来减小大小。所以使用的时候,必须查看文档,看属于哪个 features

console 后添加所需要的 features

[dependencies.web-sys]
version = "0.3.22"
features = ["console", "Window", "Document", "HtmlCanvasElement", "CanvasRenderingContext2d"]

使用 Rust 与 WebAssembly 进行图形开发_第3张图片
Rust 作为静态语言,确实要比 JavaScript 更加繁杂。如果这个图形纯粹使用 JavaScript 的话,看起来就就简单多了:

  • JavaScript 中,window 有可能是 null 或者 undefined,对于 Rust 来讲,就是 Option,可以使用 unwrap 来获取 window

  • 使用 get_element_by_id 获取 canvas 后,得到的是 Option,但 Element 并没有函数关联到 canvas。JavaScript 中,可以使用 get_context 去尝试获取,如果没有的话,会抛出异常。对于 Rust,则需要使用 dyn_into 强制转换到 HtmlCanvasElement

  • get_context("2d") 之后使用了两个 unwrap,这是因为它返回的是 Result, JsValue>

绘制谢尔宾斯基三角形

谢尔宾斯基(Sierpiński)三角形是分形中的经典图形之一,用递归来实现其实非常简单的。
使用 Rust 与 WebAssembly 进行图形开发_第4张图片
先把绘制三角形的代码抽象为一个函数,便于多次调用:

fn draw_triangle(context: &web_sys::CanvasRenderingContext2d, points: [(f64, f64); 3]) {
    let [top, left, right] = points;
    context.move_to(top.0, top.1);
    context.begin_path();
    context.line_to(left.0, left.1);
    context.line_to(right.0, right.1);
    context.line_to(top.0, top.1);
    context.close_path();
    context.stroke();
}

那么就只需要调用函数进行三角形的绘制,

draw_triangle(&context, [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)]);

使用 Rust 与 WebAssembly 进行图形开发_第5张图片

由于函数没有加上 context.fill(),所以图案并没有填充成为黑色。

谢尔宾斯基三角形非常简单,只要 取边长中点 (两个坐标之和除以 2)继续进行相同的绘制过程。

draw_triangle(&context, [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)]);
draw_triangle(&context, [(300.0, 0.0), (150.00, 300.0), (450.0, 300.0)]);
draw_triangle(&context, [(150.0, 300.0), (0.0, 600.0), (300.0, 600.0)]);
draw_triangle(&context, [(450.0, 300.0), (300.0, 600.0), (600.0, 600.0)]);

使用 Rust 与 WebAssembly 进行图形开发_第6张图片

我们将上述过程作为谢尔宾斯基三角形绘制的基础,使用递归绘制图案:

fn midpoint(point_1: (f64, f64), point_2: (f64, f64)) -> (f64, f64) {
    ((point_1.0 + point_2.0) / 2.0, (point_1.1 + point_2.1) / 2.0)
}

fn sierpinski(context: &web_sys::CanvasRenderingContext2d, points: [(f64, f64); 3], depth: u8) {
    draw_triangle(&context, points);

    let depth = depth - 1;
    let [top, left, right] = points;

    if depth > 0 {
        // 计算中点
        let left_middle = midpoint(top, left);
        let right_middle = midpoint(top, right);
        let bottom_middle = midpoint(left, right);

        sierpinski(&context, [top, left_middle, right_middle], depth);
        sierpinski(&context, [left_middle, left, bottom_middle], depth);
        sierpinski(&context, [right_middle, bottom_middle, right], depth);
    }
}

调用这个函数,并且设置 depth 为 4:

sierpinski(&context, [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)], 4);

使用 Rust 与 WebAssembly 进行图形开发_第7张图片

如果想要跟最开始图案一样填充黑色,只需要在最后一层进行填充颜色。可以考虑加入一个 bool 值作为是否填充的判断:

fn draw_triangle(context: &web_sys::CanvasRenderingContext2d, points: [(f64, f64); 3], fill: bool) {
    let [top, left, right] = points;

    // ... 

    if fill {
        context.fill();
    }
}

之后判断是否是最后一层,

let depth = depth - 1;
let fill = if depth == 0 { true } else { false };

使用 Rust 与 WebAssembly 进行图形开发_第8张图片

小结

通过本文的铺垫,我们就可以考虑使用 Rust 开发 Web 游戏了(思考)。

你可能感兴趣的:(Rust,rust,开发语言,后端,WebAssembly)