要求需要先安装好 Node.js,和 Rust 环境。
npm init rust-webpack
如果能看到下面的图案就是成功了:
Rust + WebAssembly + Webpack = ❤
npm install
Linux 和 Mac OSX 的操作系统可以使用 cURL 进行安装:
curl https://rustwasm.github.io/wasm-pack/installer/init . sh -sSf | sh
Windows 可以下载单独的 exe,进行安装:下载地址
npm run start
会自动安装 Rust 所需的依赖包,如果成功的话,开发者工具中终端界面可以看到 Hello, World
目前模板的 Rust 版本为 2018
(2022年7月5日时),在 cargo.toml
将版本改成 2021
:
edition = "2021"
cargo.toml
中的依赖也不是最新的,可以更新到新的版本。
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"
console_error_panic_hook
这是个非常有用的库,一看名字就知道是用来 Debug 的,目前最新是 0.1.7
。
[target."cfg(debug_assertions)".dependencies]
console_error_panic_hook = "0.1.7"
可以使用 将图形绘制到浏览器窗口中,在
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 作为静态语言,确实要比 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
。
谢尔宾斯基(Sierpiński)三角形是分形中的经典图形之一,用递归来实现其实非常简单的。
先把绘制三角形的代码抽象为一个函数,便于多次调用:
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)]);
由于函数没有加上 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)]);
我们将上述过程作为谢尔宾斯基三角形绘制的基础,使用递归绘制图案:
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);
如果想要跟最开始图案一样填充黑色,只需要在最后一层进行填充颜色。可以考虑加入一个 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 开发 Web 游戏了(思考)。