添加交互性
我们将继续探索 JavaScript 和 WebAssembly 接口,为生命游戏实现添加一些交互功能。我们将允许用户通过点击来切换细胞的生死状态,并允许暂停游戏,这使得绘制细胞图案变得更容易。
暂停和恢复游戏
让我们添加一个按钮来切换游戏是否正在运行或暂停。在 wasm-game-of-life/www/index.html
中,将按钮添加到 <canvas>
上方
<button id="play-pause"></button>
在 wasm-game-of-life/www/index.js
JavaScript 中,我们将进行以下更改
-
跟踪最新调用
requestAnimationFrame
返回的标识符,以便我们可以通过使用该标识符调用cancelAnimationFrame
来取消动画。 -
当点击播放/暂停按钮时,检查我们是否拥有排队动画帧的标识符。如果我们有,那么游戏目前正在运行,我们希望取消动画帧,以便不再调用
renderLoop
,从而有效地暂停游戏。如果我们没有排队动画帧的标识符,那么我们目前处于暂停状态,我们希望调用requestAnimationFrame
来恢复游戏。
由于 JavaScript 驱动 Rust 和 WebAssembly,因此这是我们唯一需要做的事情,我们不需要更改 Rust 源代码。
我们引入 animationId
变量来跟踪 requestAnimationFrame
返回的标识符。当没有排队动画帧时,我们将此变量设置为 null
。
let animationId = null;
// This function is the same as before, except the
// result of `requestAnimationFrame` is assigned to
// `animationId`.
const renderLoop = () => {
drawGrid();
drawCells();
universe.tick();
animationId = requestAnimationFrame(renderLoop);
};
在任何时间点,我们可以通过检查 animationId
的值来判断游戏是否暂停。
const isPaused = () => {
return animationId === null;
};
现在,当点击播放/暂停按钮时,我们检查游戏当前是否暂停或正在运行,并分别恢复 renderLoop
动画或取消下一个动画帧。此外,我们更新按钮的文本图标以反映按钮在下次点击时将执行的操作。
const playPauseButton = document.getElementById("play-pause");
const play = () => {
playPauseButton.textContent = "⏸";
renderLoop();
};
const pause = () => {
playPauseButton.textContent = "▶";
cancelAnimationFrame(animationId);
animationId = null;
};
playPauseButton.addEventListener("click", event => {
if (isPaused()) {
play();
} else {
pause();
}
});
最后,我们之前通过直接调用 requestAnimationFrame(renderLoop)
来启动游戏及其动画,但我们希望将其替换为调用 play
,以便按钮获得正确的初始文本图标。
// This used to be `requestAnimationFrame(renderLoop)`.
play();
刷新 http://localhost:8080/,我们现在应该可以通过点击按钮来暂停和恢复游戏!
在 "click"
事件上切换细胞的状态
现在我们可以暂停游戏了,是时候添加通过点击来改变细胞的能力了。
切换细胞是指将它的状态从存活变为死亡,或从死亡变为存活。在 wasm-game-of-life/src/lib.rs
中的 Cell
中添加一个 toggle
方法
# #![allow(unused_variables)] #fn main() { impl Cell { fn toggle(&mut self) { *self = match *self { Cell::Dead => Cell::Alive, Cell::Alive => Cell::Dead, }; } } #}
要切换给定行和列的细胞状态,我们将行和列对转换为细胞向量中的索引,并调用该索引处细胞的 toggle
方法
# #![allow(unused_variables)] #fn main() { /// Public methods, exported to JavaScript. #[wasm_bindgen] impl Universe { // ... pub fn toggle_cell(&mut self, row: u32, column: u32) { let idx = self.get_index(row, column); self.cells[idx].toggle(); } } #}
此方法定义在用 #[wasm_bindgen]
注释的 impl
块中,以便 JavaScript 可以调用它。
在 wasm-game-of-life/www/index.js
中,我们监听 <canvas>
元素的点击事件,将点击事件的页面相对坐标转换为画布相对坐标,然后转换为行和列,调用 toggle_cell
方法,最后重新绘制场景。
canvas.addEventListener("click", event => {
const boundingRect = canvas.getBoundingClientRect();
const scaleX = canvas.width / boundingRect.width;
const scaleY = canvas.height / boundingRect.height;
const canvasLeft = (event.clientX - boundingRect.left) * scaleX;
const canvasTop = (event.clientY - boundingRect.top) * scaleY;
const row = Math.min(Math.floor(canvasTop / (CELL_SIZE + 1)), height - 1);
const col = Math.min(Math.floor(canvasLeft / (CELL_SIZE + 1)), width - 1);
universe.toggle_cell(row, col);
drawGrid();
drawCells();
});
在 wasm-game-of-life
中使用 wasm-pack build
重新构建,然后再次刷新 http://localhost:8080/,我们现在可以通过点击细胞并切换其状态来绘制自己的图案。
练习
-
引入一个
<input type="range">
小部件来控制每个动画帧发生的滴答次数。 -
添加一个按钮,当点击时将宇宙重置为随机初始状态。另一个按钮将宇宙重置为所有死亡细胞。
-
在
Ctrl + 点击
时,在目标细胞的中心插入一个 滑翔机。在Shift + 点击
时,插入一个脉冲星。