增加互動性
我們將透過為康威的生命遊戲實作中新增一些互動功能,進一步探索 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();
重新整理 https://127.0.0.1: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>
元素上聆聽 click 事件,將 click 事件的頁面相對座標轉換為畫布相對座標,然後轉換為行和欄、呼叫 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
重新建置,接著再重新整理 https://127.0.0.1:8080/,現在我們可以按一下儲存格並切換其狀態,就能繪製自己的模式了。
練習
-
引入一個
<input type="range">
小工具,用以控制每個動畫畫面的刻度數量。 -
新增一個按鈕,在按一下按鈕時將宇宙重設為隨機的初始狀態。再新增一個按鈕,在按一下按鈕時將宇宙重設為所有的死儲存格。
-
在
Ctrl + 按一下
,插入以目標單元格為中心的 滑翔機。在Shift + 按一下
,插入脈衝星。